diff --git a/conf/__init_conf__.php b/conf/__init_conf__.php
index 941efd1b09..3dd66b2ddd 100644
--- a/conf/__init_conf__.php
+++ b/conf/__init_conf__.php
@@ -1,65 +1,69 @@
 <?php
 
 function phabricator_read_config_file($original_config) {
-
   $root = dirname(dirname(__FILE__));
 
   // Accept either "myconfig" (preferred) or "myconfig.conf.php".
   $config = preg_replace('/\.conf\.php$/', '', $original_config);
   $full_config_path = $root.'/conf/'.$config.'.conf.php';
 
   if (!Filesystem::pathExists($full_config_path)) {
-
     // These are very old configuration files which we used to ship with
     // by default. File based configuration was de-emphasized once web-based
     // configuration was built. The actual files were removed to reduce
     // user confusion over how to configure Phabricator.
 
     switch ($config) {
       case 'default':
       case 'production':
         return array();
       case 'development':
         return array(
           'phabricator.developer-mode'      => true,
           'darkconsole.enabled'             => true,
           'celerity.minify'                 => false,
         );
     }
 
     $files = id(new FileFinder($root.'/conf/'))
       ->withType('f')
       ->withSuffix('conf.php')
       ->withFollowSymlinks(true)
       ->find();
 
     foreach ($files as $key => $file) {
       $file = trim($file, './');
       $files[$key] = preg_replace('/\.conf\.php$/', '', $file);
     }
-    $files = "    ".implode("\n    ", $files);
+    $files = '    '.implode("\n    ", $files);
 
     throw new Exception(
-      "CONFIGURATION ERROR\n".
-      "Config file '{$original_config}' does not exist. Valid config files ".
-      "are:\n\n".$files);
+      pht(
+        "CONFIGURATION ERROR\n".
+        "Config file '%s' does not exist. Valid config files are:\n\n%s",
+        $original_config,
+        $files));
   }
 
   // Make sure config file errors are reported.
   $old_error_level = error_reporting(E_ALL | E_STRICT);
   $old_display_errors = ini_get('display_errors');
   ini_set('display_errors', 1);
 
     ob_start();
     $conf = include $full_config_path;
     $errors = ob_get_clean();
 
   error_reporting($old_error_level);
   ini_set('display_errors', $old_display_errors);
 
   if ($conf === false) {
-    throw new Exception("Failed to read config file '{$config}': {$errors}");
+    throw new Exception(
+      pht(
+        "Failed to read config file '%s': %s",
+        $config,
+        $errors));
   }
 
   return $conf;
 }
diff --git a/resources/sql/patches/20130508.releephtransactionsmig.php b/resources/sql/patches/20130508.releephtransactionsmig.php
index ede09993d2..c5203cf9b6 100644
--- a/resources/sql/patches/20130508.releephtransactionsmig.php
+++ b/resources/sql/patches/20130508.releephtransactionsmig.php
@@ -1,8 +1,8 @@
 <?php
 
 // Previously, this migrated ReleephRequestEvents into ApplicationTransactions.
 // Only Facebook ever generated meaningful ReleephRequestEvent data, and has
 // already migrated, so this was cleaned up when ReleephRequestEvent was
 // removed.
 
-echo "(This migration is obsolete.)\n";
+echo pht('(This migration is obsolete.)')."\n";
diff --git a/src/aphront/sink/__tests__/AphrontHTTPSinkTestCase.php b/src/aphront/sink/__tests__/AphrontHTTPSinkTestCase.php
index 05235a77ba..7858aeea31 100644
--- a/src/aphront/sink/__tests__/AphrontHTTPSinkTestCase.php
+++ b/src/aphront/sink/__tests__/AphrontHTTPSinkTestCase.php
@@ -1,82 +1,84 @@
 <?php
 
 final class AphrontHTTPSinkTestCase extends PhabricatorTestCase {
 
   public function testHTTPSinkBasics() {
     $sink = new AphrontIsolatedHTTPSink();
     $sink->writeHTTPStatus(200);
     $sink->writeHeaders(array(array('X-Test', 'test')));
     $sink->writeData('test');
 
     $this->assertEqual(200, $sink->getEmittedHTTPStatus());
     $this->assertEqual(
       array(array('X-Test', 'test')),
       $sink->getEmittedHeaders());
     $this->assertEqual('test', $sink->getEmittedData());
   }
 
   public function testHTTPSinkStatusCode() {
     $input = $this->tryTestCaseMap(
       array(
         200     => true,
         '201'   => true,
         1       => false,
         1000    => false,
         'apple' => false,
         ''      => false,
       ),
       array($this, 'tryHTTPSinkStatusCode'));
   }
 
   protected function tryHTTPSinkStatusCode($input) {
     $sink = new AphrontIsolatedHTTPSink();
     $sink->writeHTTPStatus($input);
   }
 
   public function testHTTPSinkResponseSplitting() {
     $input = $this->tryTestCaseMap(
       array(
         'test'      => true,
         "test\nx"   => false,
         "test\rx"   => false,
         "test\0x"   => false,
       ),
       array($this, 'tryHTTPSinkResponseSplitting'));
   }
 
   protected function tryHTTPSinkResponseSplitting($input) {
     $sink = new AphrontIsolatedHTTPSink();
     $sink->writeHeaders(array(array('X-Test', $input)));
   }
 
   public function testHTTPHeaderNames() {
     $input = $this->tryTestCaseMap(
       array(
         'test'  => true,
         'test:' => false,
       ),
       array($this, 'tryHTTPHeaderNames'));
   }
 
   protected function tryHTTPHeaderNames($input) {
     $sink = new AphrontIsolatedHTTPSink();
     $sink->writeHeaders(array(array($input, 'value')));
   }
 
   public function testJSONContentSniff() {
     $response = id(new AphrontJSONResponse())
       ->setContent(
         array(
           'x' => '<iframe>',
         ));
     $sink = new AphrontIsolatedHTTPSink();
     $sink->writeResponse($response);
 
     $this->assertEqual(
       'for (;;);{"x":"\u003ciframe\u003e"}',
       $sink->getEmittedData(),
-      'JSONResponse should prevent content-sniffing attacks.');
+      pht(
+        '%s should prevent content-sniffing attacks.',
+        'JSONResponse'));
   }
 
 
 }
diff --git a/src/applications/almanac/controller/AlmanacBindingEditController.php b/src/applications/almanac/controller/AlmanacBindingEditController.php
index ad6936fa70..7fc5abc395 100644
--- a/src/applications/almanac/controller/AlmanacBindingEditController.php
+++ b/src/applications/almanac/controller/AlmanacBindingEditController.php
@@ -1,123 +1,123 @@
 <?php
 
 final class AlmanacBindingEditController
   extends AlmanacServiceController {
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $id = $request->getURIData('id');
     if ($id) {
       $binding = id(new AlmanacBindingQuery())
         ->setViewer($viewer)
         ->withIDs(array($id))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$binding) {
         return new Aphront404Response();
       }
 
       $service = $binding->getService();
       $is_new = false;
 
       $service_uri = $service->getURI();
       $cancel_uri = $binding->getURI();
       $title = pht('Edit Binding');
       $save_button = pht('Save Changes');
     } else {
       $service = id(new AlmanacServiceQuery())
         ->setViewer($viewer)
         ->withIDs(array($request->getStr('serviceID')))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
 
       $binding = AlmanacBinding::initializeNewBinding($service);
       $is_new = true;
 
       $service_uri = $service->getURI();
       $cancel_uri = $service_uri;
       $title = pht('Create Binding');
       $save_button = pht('Create Binding');
     }
 
     $v_interface = array();
     if ($binding->getInterfacePHID()) {
       $v_interface = array($binding->getInterfacePHID());
     }
     $e_interface = true;
 
     $validation_exception = null;
     if ($request->isFormPost()) {
       $v_interface = $request->getArr('interfacePHIDs');
 
       $type_interface = AlmanacBindingTransaction::TYPE_INTERFACE;
 
       $xactions = array();
 
       $xactions[] = id(new AlmanacBindingTransaction())
         ->setTransactionType($type_interface)
         ->setNewValue(head($v_interface));
 
       $editor = id(new AlmanacBindingEditor())
         ->setActor($viewer)
         ->setContentSourceFromRequest($request)
         ->setContinueOnNoEffect(true);
 
       try {
         $editor->applyTransactions($binding, $xactions);
 
         $binding_uri = $binding->getURI();
         return id(new AphrontRedirectResponse())->setURI($binding_uri);
       } catch (PhabricatorApplicationTransactionValidationException $ex) {
         $validation_exception = $ex;
         $e_interface = $ex->getShortMessage($type_interface);
       }
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setName('interfacePHIDs')
-          ->setLabel('Interface')
+          ->setLabel(pht('Interface'))
           ->setLimit(1)
           ->setDatasource(new AlmanacInterfaceDatasource())
           ->setValue($v_interface)
           ->setError($e_interface))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($cancel_uri)
           ->setValue($save_button));
 
     $box = id(new PHUIObjectBoxView())
       ->setValidationException($validation_exception)
       ->setHeaderText($title)
       ->appendChild($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($service->getName(), $service_uri);
     if ($is_new) {
       $crumbs->addTextCrumb(pht('Create Binding'));
     } else {
       $crumbs->addTextCrumb(pht('Edit Binding'));
     }
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $box,
       ),
       array(
         'title' => $title,
       ));
   }
 
 }
diff --git a/src/applications/almanac/controller/AlmanacConsoleController.php b/src/applications/almanac/controller/AlmanacConsoleController.php
index 156bd14b78..faebc51567 100644
--- a/src/applications/almanac/controller/AlmanacConsoleController.php
+++ b/src/applications/almanac/controller/AlmanacConsoleController.php
@@ -1,54 +1,54 @@
 <?php
 
 final class AlmanacConsoleController extends AlmanacController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function handleRequest(AphrontRequest $request) {
     $viewer = $request->getViewer();
 
     $menu = id(new PHUIObjectItemListView())
       ->setUser($viewer)
       ->setStackable(true);
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Services'))
         ->setHref($this->getApplicationURI('service/'))
         ->setFontIcon('fa-plug')
         ->addAttribute(pht('Manage Almanac services.')));
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Devices'))
         ->setHref($this->getApplicationURI('device/'))
         ->setFontIcon('fa-server')
         ->addAttribute(pht('Manage Almanac devices.')));
 
     $menu->addItem(
       id(new PHUIObjectItemView())
         ->setHeader(pht('Networks'))
         ->setHref($this->getApplicationURI('network/'))
         ->setFontIcon('fa-globe')
         ->addAttribute(pht('Manage Almanac networks.')));
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Console'));
 
     $box = id(new PHUIObjectBoxView())
-      ->setHeaderText('Console')
+      ->setHeaderText(pht('Console'))
       ->appendChild($menu);
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $box,
       ),
       array(
         'title'  => pht('Almanac Console'),
       ));
   }
 
 }
diff --git a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php
index 904592f7fa..9f4bd42f29 100644
--- a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php
+++ b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php
@@ -1,51 +1,53 @@
 <?php
 
 final class PhabricatorAuditCommentEditor extends PhabricatorEditor {
 
   /**
    * Load the PHIDs for all objects the user has the authority to act as an
    * audit for. This includes themselves, and any packages they are an owner
    * of.
    */
   public static function loadAuditPHIDsForUser(PhabricatorUser $user) {
     $phids = array();
 
     // TODO: This method doesn't really use the right viewer, but in practice we
     // never issue this query of this type on behalf of another user and are
     // unlikely to do so in the future. This entire method should be refactored
     // into a Query class, however, and then we should use a proper viewer.
 
     // The user can audit on their own behalf.
     $phids[$user->getPHID()] = true;
 
     $owned_packages = id(new PhabricatorOwnersPackageQuery())
       ->setViewer($user)
       ->withOwnerPHIDs(array($user->getPHID()))
       ->execute();
     foreach ($owned_packages as $package) {
       $phids[$package->getPHID()] = true;
     }
 
     // The user can audit on behalf of all projects they are a member of.
     $projects = id(new PhabricatorProjectQuery())
       ->setViewer($user)
       ->withMemberPHIDs(array($user->getPHID()))
       ->execute();
     foreach ($projects as $project) {
       $phids[$project->getPHID()] = true;
     }
 
     return array_keys($phids);
   }
 
   public static function getMailThreading(
     PhabricatorRepository $repository,
     PhabricatorRepositoryCommit $commit) {
 
     return array(
       'diffusion-audit-'.$commit->getPHID(),
-      'Commit r'.$repository->getCallsign().$commit->getCommitIdentifier(),
+      pht(
+        'Commit %s',
+        'r'.$repository->getCallsign().$commit->getCommitIdentifier()),
     );
   }
 
 }
diff --git a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php
index e463c7cf37..4c66a48348 100644
--- a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php
+++ b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php
@@ -1,193 +1,193 @@
 <?php
 
 final class ConpherenceRoomTestCase extends ConpherenceTestCase {
 
   protected function getPhabricatorTestCaseConfiguration() {
     return array(
       self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
     );
   }
 
   public function testOneUserRoomCreate() {
     $creator = $this->generateNewTestUser();
     $participant_phids = array($creator->getPHID());
 
     $conpherence = $this->createRoom($creator, $participant_phids);
 
     $this->assertTrue((bool)$conpherence->getID());
     $this->assertEqual(1, count($conpherence->getParticipants()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
   }
 
   public function testNUserRoomCreate() {
     $creator = $this->generateNewTestUser();
     $friend_1 = $this->generateNewTestUser();
     $friend_2 = $this->generateNewTestUser();
     $friend_3 = $this->generateNewTestUser();
 
     $participant_phids = array(
       $creator->getPHID(),
       $friend_1->getPHID(),
       $friend_2->getPHID(),
       $friend_3->getPHID(),
     );
 
     $conpherence = $this->createRoom($creator, $participant_phids);
 
     $this->assertTrue((bool)$conpherence->getID());
     $this->assertEqual(4, count($conpherence->getParticipants()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
   }
 
   public function testRoomParticipantAddition() {
     $creator = $this->generateNewTestUser();
     $friend_1 = $this->generateNewTestUser();
     $friend_2 = $this->generateNewTestUser();
     $friend_3 = $this->generateNewTestUser();
 
     $participant_phids = array(
       $creator->getPHID(),
       $friend_1->getPHID(),
     );
 
     $conpherence = $this->createRoom($creator, $participant_phids);
 
     $this->assertTrue((bool)$conpherence->getID());
     $this->assertEqual(2, count($conpherence->getParticipants()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
 
     // test add by creator
     $participant_phids[] = $friend_2->getPHID();
     $this->addParticipants($creator, $conpherence, array($friend_2->getPHID()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
 
     // test policy error as another user tries to add
     $caught = null;
     try {
       $this->addParticipants(
         $friend_2,
         $conpherence,
         array($friend_3->getPHID()));
     } catch (PhabricatorPolicyException $ex) {
       $caught = $ex;
     }
     $this->assertTrue($caught instanceof PhabricatorPolicyException);
 
     // update edit policy so user has a chance
     $this->changeEditPolicy($creator, $conpherence, 'users');
     // test add by other participant, so recent participation should
     // meaningfully change
     $participant_phids = array(
       $friend_2->getPHID(),  // actor
       $creator->getPHID(),   // last actor
       $friend_1->getPHID(),
       $friend_3->getPHID(),  // new addition
     );
     $this->addParticipants(
       $friend_2,
       $conpherence,
       array($friend_3->getPHID()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
   }
 
   public function testRoomParticipantDeletion() {
     $creator = $this->generateNewTestUser();
     $friend_1 = $this->generateNewTestUser();
     $friend_2 = $this->generateNewTestUser();
     $friend_3 = $this->generateNewTestUser();
 
     $participant_map = array(
       $creator->getPHID() => $creator,
       $friend_1->getPHID() => $friend_1,
       $friend_2->getPHID() => $friend_2,
       $friend_3->getPHID() => $friend_3,
     );
 
     $conpherence = $this->createRoom(
       $creator,
       array_keys($participant_map));
 
     foreach ($participant_map as $phid => $user) {
       $this->removeParticipants($user, $conpherence, array($phid));
       unset($participant_map[$phid]);
       $this->assertEqual(
         count($participant_map),
         count($conpherence->getParticipants()));
     }
   }
 
   public function testAddMessageWithFileAttachments() {
     $creator = $this->generateNewTestUser();
     $friend_1 = $this->generateNewTestUser();
     $join_via_add = $this->generateNewTestUser();
 
     $participant_map = array(
       $creator->getPHID() => $creator,
       $friend_1->getPHID() => $friend_1,
     );
 
     $conpherence = $this->createRoom(
       $creator,
       array_keys($participant_map));
 
     foreach ($participant_map as $phid => $user) {
       $xactions = $this->addMessageWithFile($user, $conpherence);
       $this->assertEqual(2, count($xactions));
     }
 
     $xactions = $this->addMessageWithFile($join_via_add, $conpherence);
     $this->assertEqual(2, count($xactions));
   }
 
   private function createRoom(
     PhabricatorUser $creator,
     array $participant_phids) {
 
     $conpherence = ConpherenceThread::initializeNewRoom($creator);
 
     $xactions = array();
     $xactions[] = id(new ConpherenceTransaction())
       ->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS)
       ->setNewValue(array('+' => $participant_phids));
     $xactions[] = id(new ConpherenceTransaction())
       ->setTransactionType(ConpherenceTransaction::TYPE_TITLE)
-      ->setNewValue('Test');
+      ->setNewValue(pht('Test'));
 
     id(new ConpherenceEditor())
       ->setActor($creator)
       ->setContentSource(PhabricatorContentSource::newConsoleSource())
       ->setContinueOnNoEffect(true)
       ->applyTransactions($conpherence, $xactions);
 
     return $conpherence;
   }
 
   private function changeEditPolicy(
     PhabricatorUser $actor,
     ConpherenceThread $room,
     $policy) {
 
     $xactions = array();
     $xactions[] = id(new ConpherenceTransaction())
       ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
       ->setNewValue($policy);
 
     id(new ConpherenceEditor())
       ->setActor($actor)
       ->setContentSource(PhabricatorContentSource::newConsoleSource())
       ->setContinueOnNoEffect(true)
       ->applyTransactions($room, $xactions);
   }
 
 
 }
diff --git a/src/applications/conpherence/__tests__/ConpherenceThreadTestCase.php b/src/applications/conpherence/__tests__/ConpherenceThreadTestCase.php
index 1f60f1e149..3013e998fa 100644
--- a/src/applications/conpherence/__tests__/ConpherenceThreadTestCase.php
+++ b/src/applications/conpherence/__tests__/ConpherenceThreadTestCase.php
@@ -1,169 +1,169 @@
 <?php
 
 final class ConpherenceThreadTestCase extends ConpherenceTestCase {
 
   protected function getPhabricatorTestCaseConfiguration() {
     return array(
       self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
     );
   }
 
   public function testOneUserThreadCreate() {
     $creator = $this->generateNewTestUser();
     $participant_phids = array($creator->getPHID());
 
     $conpherence = $this->createThread($creator, $participant_phids);
 
     $this->assertTrue((bool)$conpherence->getID());
     $this->assertEqual(1, count($conpherence->getParticipants()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
   }
 
   public function testNUserThreadCreate() {
     $creator = $this->generateNewTestUser();
     $friend_1 = $this->generateNewTestUser();
     $friend_2 = $this->generateNewTestUser();
     $friend_3 = $this->generateNewTestUser();
 
     $participant_phids = array(
       $creator->getPHID(),
       $friend_1->getPHID(),
       $friend_2->getPHID(),
       $friend_3->getPHID(),
     );
 
     $conpherence = $this->createThread($creator, $participant_phids);
 
     $this->assertTrue((bool)$conpherence->getID());
     $this->assertEqual(4, count($conpherence->getParticipants()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
   }
 
   public function testThreadParticipantAddition() {
     $creator = $this->generateNewTestUser();
     $friend_1 = $this->generateNewTestUser();
     $friend_2 = $this->generateNewTestUser();
     $friend_3 = $this->generateNewTestUser();
 
     $participant_phids = array(
       $creator->getPHID(),
       $friend_1->getPHID(),
     );
 
     $conpherence = $this->createThread($creator, $participant_phids);
 
     $this->assertTrue((bool)$conpherence->getID());
     $this->assertEqual(2, count($conpherence->getParticipants()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
 
     // test add by creator
     $participant_phids[] = $friend_2->getPHID();
     $this->addParticipants($creator, $conpherence, array($friend_2->getPHID()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
 
     // test add by other participant, so recent participation should
     // meaningfully change
     $participant_phids = array(
       $friend_2->getPHID(),  // actor
       $creator->getPHID(),   // last actor
       $friend_1->getPHID(),
       $friend_3->getPHID(),  // new addition
     );
     $this->addParticipants(
       $friend_2,
       $conpherence,
       array($friend_3->getPHID()));
     $this->assertEqual(
       $participant_phids,
       $conpherence->getRecentParticipantPHIDs());
   }
 
   public function testThreadParticipantDeletion() {
     $creator = $this->generateNewTestUser();
     $friend_1 = $this->generateNewTestUser();
     $friend_2 = $this->generateNewTestUser();
     $friend_3 = $this->generateNewTestUser();
 
     $participant_map = array(
       $creator->getPHID() => $creator,
       $friend_1->getPHID() => $friend_1,
       $friend_2->getPHID() => $friend_2,
       $friend_3->getPHID() => $friend_3,
     );
 
     $conpherence = $this->createThread(
       $creator,
       array_keys($participant_map));
 
     foreach ($participant_map as $phid => $user) {
       $this->removeParticipants($user, $conpherence, array($phid));
       unset($participant_map[$phid]);
       $this->assertEqual(
         count($participant_map),
         count($conpherence->getParticipants()));
     }
   }
 
   public function testAddMessageWithFileAttachments() {
     $creator = $this->generateNewTestUser();
     $friend_1 = $this->generateNewTestUser();
     $policy_exception_user = $this->generateNewTestUser();
 
     $participant_map = array(
       $creator->getPHID() => $creator,
       $friend_1->getPHID() => $friend_1,
     );
 
     $conpherence = $this->createThread(
       $creator,
       array_keys($participant_map));
 
     foreach ($participant_map as $phid => $user) {
       $xactions = $this->addMessageWithFile($user, $conpherence);
       $this->assertEqual(2, count($xactions), pht('hi'));
     }
 
     $caught = null;
     try {
       $xactions = $this->addMessageWithFile(
         $policy_exception_user,
         $conpherence);
     } catch (PhabricatorPolicyException $ex) {
       $caught = $ex;
     }
     $this->assertTrue(
       $caught instanceof PhabricatorPolicyException,
       pht(
         'User not participating in thread should get policy exception '.
         'trying to add message.'));
     $this->assertTrue(
       $conpherence->establishConnection('w')->isReadLocking(),
       pht(
         'Conpherence object should still be read locked from policy '.
         'exception.'));
     $conpherence->endReadLocking();
     $conpherence->killTransaction();
   }
 
   private function createThread(
     PhabricatorUser $creator,
     array $participant_phids) {
 
     list($errors, $conpherence) = ConpherenceEditor::createThread(
       $creator,
       $participant_phids,
-      'Test',
-      'Test',
+      pht('Test'),
+      pht('Test'),
       PhabricatorContentSource::newConsoleSource());
     return $conpherence;
   }
 
 }
diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php
index 54f9f560a9..bc52227590 100644
--- a/src/applications/differential/storage/DifferentialTransaction.php
+++ b/src/applications/differential/storage/DifferentialTransaction.php
@@ -1,667 +1,667 @@
 <?php
 
 final class DifferentialTransaction extends PhabricatorApplicationTransaction {
 
   private $isCommandeerSideEffect;
 
   const TYPE_INLINE  = 'differential:inline';
   const TYPE_UPDATE  = 'differential:update';
   const TYPE_ACTION  = 'differential:action';
   const TYPE_STATUS  = 'differential:status';
- 
+
   const MAILTAG_REVIEWERS      = 'differential-reviewers';
   const MAILTAG_CLOSED         = 'differential-committed';
   const MAILTAG_CC             = 'differential-cc';
   const MAILTAG_COMMENT        = 'differential-comment';
   const MAILTAG_UPDATED        = 'differential-updated';
   const MAILTAG_REVIEW_REQUEST = 'differential-review-request';
   const MAILTAG_OTHER          = 'differential-other';
 
 
   public function setIsCommandeerSideEffect($is_side_effect) {
     $this->isCommandeerSideEffect = $is_side_effect;
     return $this;
   }
 
   public function getIsCommandeerSideEffect() {
     return $this->isCommandeerSideEffect;
   }
 
   public function getApplicationName() {
     return 'differential';
   }
 
   public function getApplicationTransactionType() {
     return DifferentialRevisionPHIDType::TYPECONST;
   }
 
   public function getApplicationTransactionCommentObject() {
     return new DifferentialTransactionComment();
   }
 
   public function getApplicationTransactionViewObject() {
     return new DifferentialTransactionView();
   }
 
   public function shouldHide() {
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     switch ($this->getTransactionType()) {
       case self::TYPE_UPDATE:
         // Older versions of this transaction have an ID for the new value,
         // and/or do not record the old value. Only hide the transaction if
         // the new value is a PHID, indicating that this is a newer style
         // transaction.
         if ($old === null) {
           if (phid_get_type($new) == DifferentialDiffPHIDType::TYPECONST) {
             return true;
           }
         }
         break;
 
       case PhabricatorTransactions::TYPE_EDGE:
         $add = array_diff_key($new, $old);
         $rem = array_diff_key($old, $new);
 
         // Hide metadata-only edge transactions. These correspond to users
         // accepting or rejecting revisions, but the change is always explicit
         // because of the TYPE_ACTION transaction. Rendering these transactions
         // just creates clutter.
 
         if (!$add && !$rem) {
           return true;
         }
         break;
     }
 
     return parent::shouldHide();
   }
 
   public function isInlineCommentTransaction() {
     switch ($this->getTransactionType()) {
       case self::TYPE_INLINE:
         return true;
     }
 
     return parent::isInlineCommentTransaction();
   }
 
   public function getRequiredHandlePHIDs() {
     $phids = parent::getRequiredHandlePHIDs();
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     switch ($this->getTransactionType()) {
       case self::TYPE_ACTION:
         if ($new == DifferentialAction::ACTION_CLOSE &&
             $this->getMetadataValue('isCommitClose')) {
           $phids[] = $this->getMetadataValue('commitPHID');
           if ($this->getMetadataValue('committerPHID')) {
             $phids[] = $this->getMetadataValue('committerPHID');
           }
           if ($this->getMetadataValue('authorPHID')) {
             $phids[] = $this->getMetadataValue('authorPHID');
           }
         }
         break;
       case self::TYPE_UPDATE:
         if ($new) {
           $phids[] = $new;
         }
         break;
     }
 
     return $phids;
   }
 
   public function getActionStrength() {
     switch ($this->getTransactionType()) {
       case self::TYPE_ACTION:
         return 3;
       case self::TYPE_UPDATE:
         return 2;
     }
 
     return parent::getActionStrength();
   }
 
 
   public function getActionName() {
     switch ($this->getTransactionType()) {
       case self::TYPE_INLINE:
         return pht('Commented On');
       case self::TYPE_UPDATE:
         $old = $this->getOldValue();
         if ($old === null) {
           return pht('Request');
         } else {
           return pht('Updated');
         }
       case self::TYPE_ACTION:
         $map = array(
           DifferentialAction::ACTION_ACCEPT => pht('Accepted'),
           DifferentialAction::ACTION_REJECT => pht('Requested Changes To'),
           DifferentialAction::ACTION_RETHINK => pht('Planned Changes To'),
           DifferentialAction::ACTION_ABANDON => pht('Abandoned'),
           DifferentialAction::ACTION_CLOSE => pht('Closed'),
           DifferentialAction::ACTION_REQUEST => pht('Requested A Review Of'),
           DifferentialAction::ACTION_RESIGN => pht('Resigned From'),
           DifferentialAction::ACTION_ADDREVIEWERS => pht('Added Reviewers'),
           DifferentialAction::ACTION_CLAIM => pht('Commandeered'),
           DifferentialAction::ACTION_REOPEN => pht('Reopened'),
         );
         $name = idx($map, $this->getNewValue());
         if ($name !== null) {
           return $name;
         }
         break;
     }
 
     return parent::getActionName();
   }
 
   public function getMailTags() {
     $tags = array();
 
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_SUBSCRIBERS;
         $tags[] = self::MAILTAG_CC;
         break;
       case self::TYPE_ACTION:
         switch ($this->getNewValue()) {
           case DifferentialAction::ACTION_CLOSE:
             $tags[] = self::MAILTAG_CLOSED;
             break;
         }
         break;
       case self::TYPE_UPDATE:
         $old = $this->getOldValue();
         if ($old === null) {
           $tags[] = self::MAILTAG_REVIEW_REQUEST;
         } else {
           $tags[] = self::MAILTAG_UPDATED;
         }
         break;
       case PhabricatorTransactions::TYPE_EDGE:
         switch ($this->getMetadataValue('edge:type')) {
           case DifferentialRevisionHasReviewerEdgeType::EDGECONST:
             $tags[] = self::MAILTAG_REVIEWERS;
             break;
         }
         break;
       case PhabricatorTransactions::TYPE_COMMENT:
       case self::TYPE_INLINE:
         $tags[] = self::MAILTAG_COMMENT;
         break;
     }
 
     if (!$tags) {
       $tags[] = self::MAILTAG_OTHER;
     }
 
     return $tags;
   }
 
   public function getTitle() {
     $author_phid = $this->getAuthorPHID();
     $author_handle = $this->renderHandleLink($author_phid);
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     switch ($this->getTransactionType()) {
       case self::TYPE_INLINE:
         return pht(
           '%s added inline comments.',
           $author_handle);
       case self::TYPE_UPDATE:
         if ($this->getMetadataValue('isCommitUpdate')) {
           return pht(
             'This revision was automatically updated to reflect the '.
             'committed changes.');
         } else if ($new) {
           // TODO: Migrate to PHIDs and use handles here?
           if (phid_get_type($new) == DifferentialDiffPHIDType::TYPECONST) {
             return pht(
               '%s updated this revision to %s.',
               $author_handle,
               $this->renderHandleLink($new));
           } else {
             return pht(
               '%s updated this revision.',
               $author_handle);
           }
         } else {
           return pht(
             '%s updated this revision.',
             $author_handle);
         }
       case self::TYPE_ACTION:
         switch ($new) {
           case DifferentialAction::ACTION_CLOSE:
             if (!$this->getMetadataValue('isCommitClose')) {
               return DifferentialAction::getBasicStoryText(
                 $new,
                 $author_handle);
             }
             $commit_name = $this->renderHandleLink(
               $this->getMetadataValue('commitPHID'));
             $committer_phid = $this->getMetadataValue('committerPHID');
             $author_phid = $this->getMetadataValue('authorPHID');
             if ($this->getHandleIfExists($committer_phid)) {
               $committer_name = $this->renderHandleLink($committer_phid);
             } else {
               $committer_name = $this->getMetadataValue('committerName');
             }
             if ($this->getHandleIfExists($author_phid)) {
               $author_name = $this->renderHandleLink($author_phid);
             } else {
               $author_name = $this->getMetadataValue('authorName');
             }
 
             if ($committer_name && ($committer_name != $author_name)) {
               return pht(
                 'Closed by commit %s (authored by %s, committed by %s).',
                 $commit_name,
                 $author_name,
                 $committer_name);
             } else {
               return pht(
                 'Closed by commit %s (authored by %s).',
                 $commit_name,
                 $author_name);
             }
             break;
           default:
             return DifferentialAction::getBasicStoryText($new, $author_handle);
         }
         break;
       case self::TYPE_STATUS:
         switch ($this->getNewValue()) {
           case ArcanistDifferentialRevisionStatus::ACCEPTED:
             return pht('This revision is now accepted and ready to land.');
           case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
             return pht('This revision now requires changes to proceed.');
           case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
             return pht('This revision now requires review to proceed.');
         }
      }
 
     return parent::getTitle();
   }
 
   public function renderExtraInformationLink() {
     if ($this->getMetadataValue('revisionMatchData')) {
       $details_href =
         '/differential/revision/closedetails/'.$this->getPHID().'/';
       $details_link = javelin_tag(
         'a',
         array(
           'href' => $details_href,
           'sigil' => 'workflow',
         ),
         pht('Explain Why'));
       return $details_link;
     }
     return parent::renderExtraInformationLink();
   }
 
   public function getTitleForFeed() {
     $author_phid = $this->getAuthorPHID();
     $object_phid = $this->getObjectPHID();
 
     $old = $this->getOldValue();
     $new = $this->getNewValue();
 
     $author_link = $this->renderHandleLink($author_phid);
     $object_link = $this->renderHandleLink($object_phid);
 
     switch ($this->getTransactionType()) {
       case self::TYPE_INLINE:
         return pht(
           '%s added inline comments to %s.',
           $author_link,
           $object_link);
       case self::TYPE_UPDATE:
         return pht(
           '%s updated the diff for %s.',
           $author_link,
           $object_link);
       case self::TYPE_ACTION:
         switch ($new) {
           case DifferentialAction::ACTION_ACCEPT:
             return pht(
               '%s accepted %s.',
               $author_link,
               $object_link);
           case DifferentialAction::ACTION_REJECT:
             return pht(
               '%s requested changes to %s.',
               $author_link,
               $object_link);
           case DifferentialAction::ACTION_RETHINK:
             return pht(
               '%s planned changes to %s.',
               $author_link,
               $object_link);
           case DifferentialAction::ACTION_ABANDON:
             return pht(
               '%s abandoned %s.',
               $author_link,
               $object_link);
           case DifferentialAction::ACTION_CLOSE:
             if (!$this->getMetadataValue('isCommitClose')) {
               return pht(
                 '%s closed %s.',
                 $author_link,
                 $object_link);
             } else {
               $commit_name = $this->renderHandleLink(
                 $this->getMetadataValue('commitPHID'));
               $committer_phid = $this->getMetadataValue('committerPHID');
               $author_phid = $this->getMetadataValue('authorPHID');
 
               if ($this->getHandleIfExists($committer_phid)) {
                 $committer_name = $this->renderHandleLink($committer_phid);
               } else {
                 $committer_name = $this->getMetadataValue('committerName');
               }
 
               if ($this->getHandleIfExists($author_phid)) {
                 $author_name = $this->renderHandleLink($author_phid);
               } else {
                 $author_name = $this->getMetadataValue('authorName');
               }
 
               // Check if the committer and author are the same. They're the
               // same if both resolved and are the same user, or if neither
               // resolved and the text is identical.
               if ($committer_phid && $author_phid) {
                 $same_author = ($committer_phid == $author_phid);
               } else if (!$committer_phid && !$author_phid) {
                 $same_author = ($committer_name == $author_name);
               } else {
                 $same_author = false;
               }
 
               if ($committer_name && !$same_author) {
                 return pht(
                   '%s closed %s by committing %s (authored by %s).',
                   $author_link,
                   $object_link,
                   $commit_name,
                   $author_name);
               } else {
                 return pht(
                   '%s closed %s by committing %s.',
                   $author_link,
                   $object_link,
                   $commit_name);
               }
             }
             break;
 
           case DifferentialAction::ACTION_REQUEST:
             return pht(
               '%s requested review of %s.',
               $author_link,
               $object_link);
           case DifferentialAction::ACTION_RECLAIM:
             return pht(
               '%s reclaimed %s.',
               $author_link,
               $object_link);
           case DifferentialAction::ACTION_RESIGN:
             return pht(
               '%s resigned from %s.',
               $author_link,
               $object_link);
           case DifferentialAction::ACTION_CLAIM:
             return pht(
               '%s commandeered %s.',
               $author_link,
               $object_link);
           case DifferentialAction::ACTION_REOPEN:
             return pht(
               '%s reopened %s.',
               $author_link,
               $object_link);
         }
         break;
       case self::TYPE_STATUS:
         switch ($this->getNewValue()) {
           case ArcanistDifferentialRevisionStatus::ACCEPTED:
             return pht(
               '%s is now accepted and ready to land.',
               $object_link);
           case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
             return pht(
               '%s now requires changes to proceed.',
               $object_link);
           case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
             return pht(
               '%s now requires review to proceed.',
               $object_link);
         }
      }
 
     return parent::getTitleForFeed();
   }
 
   public function getIcon() {
     switch ($this->getTransactionType()) {
       case self::TYPE_INLINE:
         return 'fa-comment';
       case self::TYPE_UPDATE:
         return 'fa-refresh';
       case self::TYPE_STATUS:
         switch ($this->getNewValue()) {
           case ArcanistDifferentialRevisionStatus::ACCEPTED:
             return 'fa-check';
           case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
             return 'fa-times';
           case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
             return 'fa-undo';
         }
         break;
       case self::TYPE_ACTION:
         switch ($this->getNewValue()) {
           case DifferentialAction::ACTION_CLOSE:
             return 'fa-check';
           case DifferentialAction::ACTION_ACCEPT:
             return 'fa-check-circle-o';
           case DifferentialAction::ACTION_REJECT:
             return 'fa-times-circle-o';
           case DifferentialAction::ACTION_ABANDON:
             return 'fa-plane';
           case DifferentialAction::ACTION_RETHINK:
             return 'fa-headphones';
           case DifferentialAction::ACTION_REQUEST:
             return 'fa-refresh';
           case DifferentialAction::ACTION_RECLAIM:
           case DifferentialAction::ACTION_REOPEN:
             return 'fa-bullhorn';
           case DifferentialAction::ACTION_RESIGN:
             return 'fa-flag';
           case DifferentialAction::ACTION_CLAIM:
             return 'fa-flag';
         }
       case PhabricatorTransactions::TYPE_EDGE:
         switch ($this->getMetadataValue('edge:type')) {
           case DifferentialRevisionHasReviewerEdgeType::EDGECONST:
             return 'fa-user';
         }
     }
 
     return parent::getIcon();
   }
 
   public function shouldDisplayGroupWith(array $group) {
 
     // Never group status changes with other types of actions, they're indirect
     // and don't make sense when combined with direct actions.
 
     $type_status = self::TYPE_STATUS;
 
     if ($this->getTransactionType() == $type_status) {
       return false;
     }
 
     foreach ($group as $xaction) {
       if ($xaction->getTransactionType() == $type_status) {
         return false;
       }
     }
 
     return parent::shouldDisplayGroupWith($group);
   }
 
 
   public function getColor() {
     switch ($this->getTransactionType()) {
       case self::TYPE_UPDATE:
         return PhabricatorTransactions::COLOR_SKY;
       case self::TYPE_STATUS:
         switch ($this->getNewValue()) {
           case ArcanistDifferentialRevisionStatus::ACCEPTED:
             return PhabricatorTransactions::COLOR_GREEN;
           case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
             return PhabricatorTransactions::COLOR_RED;
           case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
             return PhabricatorTransactions::COLOR_ORANGE;
         }
         break;
       case self::TYPE_ACTION:
         switch ($this->getNewValue()) {
           case DifferentialAction::ACTION_CLOSE:
             return PhabricatorTransactions::COLOR_INDIGO;
           case DifferentialAction::ACTION_ACCEPT:
             return PhabricatorTransactions::COLOR_GREEN;
           case DifferentialAction::ACTION_REJECT:
             return PhabricatorTransactions::COLOR_RED;
           case DifferentialAction::ACTION_ABANDON:
             return PhabricatorTransactions::COLOR_INDIGO;
           case DifferentialAction::ACTION_RETHINK:
             return PhabricatorTransactions::COLOR_RED;
           case DifferentialAction::ACTION_REQUEST:
             return PhabricatorTransactions::COLOR_SKY;
           case DifferentialAction::ACTION_RECLAIM:
             return PhabricatorTransactions::COLOR_SKY;
           case DifferentialAction::ACTION_REOPEN:
             return PhabricatorTransactions::COLOR_SKY;
           case DifferentialAction::ACTION_RESIGN:
             return PhabricatorTransactions::COLOR_ORANGE;
           case DifferentialAction::ACTION_CLAIM:
             return PhabricatorTransactions::COLOR_YELLOW;
         }
     }
 
 
     return parent::getColor();
   }
 
   public function getNoEffectDescription() {
     switch ($this->getTransactionType()) {
       case PhabricatorTransactions::TYPE_EDGE:
         switch ($this->getMetadataValue('edge:type')) {
           case DifferentialRevisionHasReviewerEdgeType::EDGECONST:
             return pht(
               'The reviewers you are trying to add are already reviewing '.
               'this revision.');
         }
         break;
       case self::TYPE_ACTION:
         switch ($this->getNewValue()) {
           case DifferentialAction::ACTION_CLOSE:
             return pht('This revision is already closed.');
           case DifferentialAction::ACTION_ABANDON:
             return pht('This revision has already been abandoned.');
           case DifferentialAction::ACTION_RECLAIM:
             return pht(
               'You can not reclaim this revision because his revision is '.
               'not abandoned.');
           case DifferentialAction::ACTION_REOPEN:
             return pht(
               'You can not reopen this revision because this revision is '.
               'not closed.');
           case DifferentialAction::ACTION_RETHINK:
             return pht('This revision already requires changes.');
           case DifferentialAction::ACTION_REQUEST:
             return pht('Review is already requested for this revision.');
           case DifferentialAction::ACTION_RESIGN:
             return pht(
               'You can not resign from this revision because you are not '.
               'a reviewer.');
           case DifferentialAction::ACTION_CLAIM:
             return pht(
               'You can not commandeer this revision because you already own '.
               'it.');
           case DifferentialAction::ACTION_ACCEPT:
             return pht('You have already accepted this revision.');
           case DifferentialAction::ACTION_REJECT:
             return pht('You have already requested changes to this revision.');
         }
         break;
     }
 
     return parent::getNoEffectDescription();
   }
 
   public function renderAsTextForDoorkeeper(
     DoorkeeperFeedStoryPublisher $publisher,
     PhabricatorFeedStory $story,
     array $xactions) {
 
     $body = parent::renderAsTextForDoorkeeper($publisher, $story, $xactions);
 
     $inlines = array();
     foreach ($xactions as $xaction) {
       if ($xaction->getTransactionType() == self::TYPE_INLINE) {
         $inlines[] = $xaction;
       }
     }
 
     // TODO: This is a bit gross, but far less bad than it used to be. It
     // could be further cleaned up at some point.
 
     if ($inlines) {
       $engine = PhabricatorMarkupEngine::newMarkupEngine(array())
         ->setConfig('viewer', new PhabricatorUser())
         ->setMode(PhutilRemarkupEngine::MODE_TEXT);
 
       $body .= "\n\n";
       $body .= pht('Inline Comments');
       $body .= "\n";
 
       $changeset_ids = array();
       foreach ($inlines as $inline) {
         $changeset_ids[] = $inline->getComment()->getChangesetID();
       }
 
       $changesets = id(new DifferentialChangeset())->loadAllWhere(
         'id IN (%Ld)',
         $changeset_ids);
 
       foreach ($inlines as $inline) {
         $comment = $inline->getComment();
         $changeset = idx($changesets, $comment->getChangesetID());
         if (!$changeset) {
           continue;
         }
 
         $filename = $changeset->getDisplayFilename();
         $linenumber = $comment->getLineNumber();
         $inline_text = $engine->markupText($comment->getContent());
         $inline_text = rtrim($inline_text);
 
         $body .= "{$filename}:{$linenumber} {$inline_text}\n";
       }
     }
 
     return $body;
   }
 
 
 }
diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php
index 0dd40f39c3..dac2c118dc 100644
--- a/src/applications/differential/view/DifferentialChangesetListView.php
+++ b/src/applications/differential/view/DifferentialChangesetListView.php
@@ -1,340 +1,340 @@
 <?php
 
 final class DifferentialChangesetListView extends AphrontView {
 
   private $changesets = array();
   private $visibleChangesets = array();
   private $references = array();
   private $inlineURI;
   private $renderURI = '/differential/changeset/';
   private $whitespace;
 
   private $standaloneURI;
   private $leftRawFileURI;
   private $rightRawFileURI;
 
   private $symbolIndexes = array();
   private $repository;
   private $branch;
   private $diff;
   private $vsMap = array();
 
   private $title;
   private $parser;
 
   public function setParser(DifferentialChangesetParser $parser) {
     $this->parser = $parser;
     return $this;
   }
 
   public function getParser() {
     return $this->parser;
   }
 
   public function setTitle($title) {
     $this->title = $title;
     return $this;
   }
   private function getTitle() {
     return $this->title;
   }
 
   public function setBranch($branch) {
     $this->branch = $branch;
     return $this;
   }
   private function getBranch() {
     return $this->branch;
   }
 
   public function setChangesets($changesets) {
     $this->changesets = $changesets;
     return $this;
   }
 
   public function setVisibleChangesets($visible_changesets) {
     $this->visibleChangesets = $visible_changesets;
     return $this;
   }
 
   public function setInlineCommentControllerURI($uri) {
     $this->inlineURI = $uri;
     return $this;
   }
 
   public function setRepository(PhabricatorRepository $repository) {
     $this->repository = $repository;
     return $this;
   }
 
   public function setDiff(DifferentialDiff $diff) {
     $this->diff = $diff;
     return $this;
   }
 
   public function setRenderingReferences(array $references) {
     $this->references = $references;
     return $this;
   }
 
   public function setSymbolIndexes(array $indexes) {
     $this->symbolIndexes = $indexes;
     return $this;
   }
 
   public function setRenderURI($render_uri) {
     $this->renderURI = $render_uri;
     return $this;
   }
 
   public function setWhitespace($whitespace) {
     $this->whitespace = $whitespace;
     return $this;
   }
 
   public function setVsMap(array $vs_map) {
     $this->vsMap = $vs_map;
     return $this;
   }
 
   public function getVsMap() {
     return $this->vsMap;
   }
 
   public function setStandaloneURI($uri) {
     $this->standaloneURI = $uri;
     return $this;
   }
 
   public function setRawFileURIs($l, $r) {
     $this->leftRawFileURI = $l;
     $this->rightRawFileURI = $r;
     return $this;
   }
 
   public function render() {
     $this->requireResource('differential-changeset-view-css');
 
     $changesets = $this->changesets;
 
     Javelin::initBehavior('differential-toggle-files', array(
       'pht' => array(
         'undo' => pht('Undo'),
         'collapsed' => pht('This file content has been collapsed.'),
       ),
     ));
 
     Javelin::initBehavior(
       'differential-dropdown-menus',
       array(
         'pht' => array(
           'Open in Editor' => pht('Open in Editor'),
           'Show Entire File' => pht('Show Entire File'),
           'Entire File Shown' => pht('Entire File Shown'),
           "Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"),
           'Expand File' => pht('Expand File'),
           'Collapse File' => pht('Collapse File'),
           'Browse in Diffusion' => pht('Browse in Diffusion'),
           'View Standalone' => pht('View Standalone'),
           'Show Raw File (Left)' => pht('Show Raw File (Left)'),
           'Show Raw File (Right)' => pht('Show Raw File (Right)'),
           'Configure Editor' => pht('Configure Editor'),
           'Load Changes' => pht('Load Changes'),
           'View Side-by-Side' => pht('View Side-by-Side'),
           'View Unified' => pht('View Unified'),
           'Change Text Encoding...' => pht('Change Text Encoding...'),
           'Highlight As...' => pht('Highlight As...'),
         ),
       ));
 
     $renderer = DifferentialChangesetParser::getDefaultRendererForViewer(
       $this->getUser());
 
     $output = array();
     $ids = array();
     foreach ($changesets as $key => $changeset) {
 
       $file = $changeset->getFilename();
       $class = 'differential-changeset';
       if (!$this->inlineURI) {
         $class .= ' differential-changeset-noneditable';
       }
 
       $ref = $this->references[$key];
 
       $detail = id(new DifferentialChangesetDetailView())
         ->setUser($this->getUser());
 
       $uniq_id = 'diff-'.$changeset->getAnchorName();
       $detail->setID($uniq_id);
 
       $view_options = $this->renderViewOptionsDropdown(
         $detail,
         $ref,
         $changeset);
 
       $detail->setChangeset($changeset);
       $detail->addButton($view_options);
       $detail->setSymbolIndex(idx($this->symbolIndexes, $key));
       $detail->setVsChangesetID(idx($this->vsMap, $changeset->getID()));
       $detail->setEditable(true);
       $detail->setRenderingRef($ref);
 
       $detail->setRenderURI($this->renderURI);
       $detail->setWhitespace($this->whitespace);
       $detail->setRenderer($renderer);
 
       if ($this->getParser()) {
         $detail->appendChild($this->getParser()->renderChangeset());
         $detail->setLoaded(true);
       } else {
         $detail->setAutoload(isset($this->visibleChangesets[$key]));
         if (isset($this->visibleChangesets[$key])) {
-          $load = 'Loading...';
+          $load = pht('Loading...');
         } else {
           $load = javelin_tag(
             'a',
             array(
               'class' => 'button grey',
               'href' => '#'.$uniq_id,
               'sigil' => 'differential-load',
               'meta' => array(
                 'id' => $detail->getID(),
                 'kill' => true,
               ),
               'mustcapture' => true,
             ),
             pht('Load File'));
         }
         $detail->appendChild(
           phutil_tag(
             'div',
             array(
               'id' => $uniq_id,
             ),
             phutil_tag(
               'div',
               array('class' => 'differential-loading'),
               $load)));
       }
 
       $output[] = $detail->render();
       $ids[] = $detail->getID();
     }
 
     $this->requireResource('aphront-tooltip-css');
 
     $this->initBehavior('differential-populate', array(
       'changesetViewIDs' => $ids,
     ));
 
     $this->initBehavior('differential-comment-jump', array());
 
     if ($this->inlineURI) {
       Javelin::initBehavior('differential-edit-inline-comments', array(
         'uri' => $this->inlineURI,
         'stage' => 'differential-review-stage',
         'revealIcon' => hsprintf('%s', new PHUIDiffRevealIconView()),
       ));
     }
 
     $header = id(new PHUIHeaderView())
       ->setHeader($this->getTitle());
 
     $content = phutil_tag(
       'div',
       array(
         'class' => 'differential-review-stage',
         'id'    => 'differential-review-stage',
       ),
       $output);
 
     $object_box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->appendChild($content);
 
     return $object_box;
   }
 
   private function renderViewOptionsDropdown(
     DifferentialChangesetDetailView $detail,
     $ref,
     DifferentialChangeset $changeset) {
 
     $meta = array();
 
     $qparams = array(
       'ref'         => $ref,
       'whitespace'  => $this->whitespace,
     );
 
     if ($this->standaloneURI) {
       $uri = new PhutilURI($this->standaloneURI);
       $uri->setQueryParams($uri->getQueryParams() + $qparams);
       $meta['standaloneURI'] = (string)$uri;
     }
 
     $repository = $this->repository;
     if ($repository) {
       try {
         $meta['diffusionURI'] =
           (string)$repository->getDiffusionBrowseURIForPath(
             $this->user,
             $changeset->getAbsoluteRepositoryPath($repository, $this->diff),
             idx($changeset->getMetadata(), 'line:first'),
             $this->getBranch());
       } catch (DiffusionSetupException $e) {
         // Ignore
       }
     }
 
     $change = $changeset->getChangeType();
 
     if ($this->leftRawFileURI) {
       if ($change != DifferentialChangeType::TYPE_ADD) {
         $uri = new PhutilURI($this->leftRawFileURI);
         $uri->setQueryParams($uri->getQueryParams() + $qparams);
         $meta['leftURI'] = (string)$uri;
       }
     }
 
     if ($this->rightRawFileURI) {
       if ($change != DifferentialChangeType::TYPE_DELETE &&
           $change != DifferentialChangeType::TYPE_MULTICOPY) {
         $uri = new PhutilURI($this->rightRawFileURI);
         $uri->setQueryParams($uri->getQueryParams() + $qparams);
         $meta['rightURI'] = (string)$uri;
       }
     }
 
     $user = $this->user;
     if ($user && $repository) {
       $path = ltrim(
         $changeset->getAbsoluteRepositoryPath($repository, $this->diff),
         '/');
       $line = idx($changeset->getMetadata(), 'line:first', 1);
       $callsign = $repository->getCallsign();
       $editor_link = $user->loadEditorLink($path, $line, $callsign);
       if ($editor_link) {
         $meta['editor'] = $editor_link;
       } else {
         $meta['editorConfigure'] = '/settings/panel/display/';
       }
     }
 
     $meta['containerID'] = $detail->getID();
     $caret = phutil_tag('span', array('class' => 'caret'), '');
 
     return javelin_tag(
       'a',
       array(
         'class'   => 'button grey small dropdown',
         'meta'    => $meta,
         'href'    => idx($meta, 'detailURI', '#'),
         'target'  => '_blank',
         'sigil'   => 'differential-view-options',
       ),
       array(pht('View Options'), $caret));
   }
 
 }
diff --git a/src/applications/differential/view/DifferentialResultsTableView.php b/src/applications/differential/view/DifferentialResultsTableView.php
index 226c68484c..f05b24216b 100644
--- a/src/applications/differential/view/DifferentialResultsTableView.php
+++ b/src/applications/differential/view/DifferentialResultsTableView.php
@@ -1,115 +1,115 @@
 <?php
 
 final class DifferentialResultsTableView extends AphrontView {
 
   private $rows;
   private $showMoreString;
 
   public function setRows(array $rows) {
     $this->rows = $rows;
     return $this;
   }
 
   public function setShowMoreString($show_more_string) {
     $this->showMoreString = $show_more_string;
     return $this;
   }
 
   public function render() {
 
     $rows = array();
 
     $any_hidden = false;
     foreach ($this->rows as $row) {
 
       $style = idx($row, 'style');
       switch ($style) {
         case 'section':
           $cells = phutil_tag(
             'th',
             array(
               'colspan' => 2,
             ),
             idx($row, 'name'));
           break;
         default:
           $name = phutil_tag(
             'th',
             array(
             ),
             idx($row, 'name'));
           $value = phutil_tag(
             'td',
             array(
             ),
             idx($row, 'value'));
           $cells = array($name, $value);
           break;
       }
 
       $show = idx($row, 'show');
 
       $rows[] = javelin_tag(
         'tr',
         array(
           'style' => $show ? null : 'display: none',
           'sigil' => $show ? null : 'differential-results-row-toggle',
           'class' => 'differential-results-row-'.$style,
         ),
         $cells);
 
       if (!$show) {
         $any_hidden = true;
       }
     }
 
     if ($any_hidden) {
       $show_more = javelin_tag(
         'a',
         array(
           'href'        => '#',
           'mustcapture' => true,
         ),
         $this->showMoreString);
 
       $hide_more = javelin_tag(
         'a',
         array(
           'href'        => '#',
           'mustcapture' => true,
         ),
-        'Hide');
+        pht('Hide'));
 
       $rows[] = javelin_tag(
         'tr',
         array(
           'class' => 'differential-results-row-show',
           'sigil' => 'differential-results-row-show',
         ),
         phutil_tag('th', array('colspan' => 2), $show_more));
 
       $rows[] = javelin_tag(
         'tr',
         array(
           'class' => 'differential-results-row-show',
           'sigil' => 'differential-results-row-hide',
           'style' => 'display: none',
         ),
         phutil_tag('th', array('colspan' => 2), $hide_more));
 
       $this->initBehavior('differential-show-field-details');
     }
 
     $this->requireResource('differential-results-table-css');
 
     return javelin_tag(
       'table',
       array(
         'class' => 'differential-results-table',
         'sigil' => 'differential-results-table',
       ),
       $rows);
   }
 
 
 }
diff --git a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
index 6f8ee6db93..301774dd18 100644
--- a/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
+++ b/src/applications/differential/view/DifferentialRevisionUpdateHistoryView.php
@@ -1,439 +1,438 @@
 <?php
 
 final class DifferentialRevisionUpdateHistoryView extends AphrontView {
 
   private $diffs = array();
   private $selectedVersusDiffID;
   private $selectedDiffID;
   private $selectedWhitespace;
   private $commitsForLinks = array();
 
   public function setDiffs(array $diffs) {
     assert_instances_of($diffs, 'DifferentialDiff');
     $this->diffs = $diffs;
     return $this;
   }
 
   public function setSelectedVersusDiffID($id) {
     $this->selectedVersusDiffID = $id;
     return $this;
   }
 
   public function setSelectedDiffID($id) {
     $this->selectedDiffID = $id;
     return $this;
   }
 
   public function setSelectedWhitespace($whitespace) {
     $this->selectedWhitespace = $whitespace;
     return $this;
   }
 
   public function setCommitsForLinks(array $commits) {
     assert_instances_of($commits, 'PhabricatorRepositoryCommit');
     $this->commitsForLinks = $commits;
     return $this;
   }
 
   public function render() {
-
     $this->requireResource('differential-core-view-css');
     $this->requireResource('differential-revision-history-css');
 
     $data = array(
       array(
-        'name' => 'Base',
+        'name' => pht('Base'),
         'id'   => null,
-        'desc' => 'Base',
+        'desc' => pht('Base'),
         'age'  => null,
         'obj'  => null,
       ),
     );
 
     $seq = 0;
     foreach ($this->diffs as $diff) {
       $data[] = array(
-        'name' => 'Diff '.(++$seq),
+        'name' => pht('Diff %d', ++$seq),
         'id'   => $diff->getID(),
         'desc' => $diff->getDescription(),
         'age'  => $diff->getDateCreated(),
         'obj'  => $diff,
       );
     }
 
     $max_id = $diff->getID();
     $revision_id = $diff->getRevisionID();
 
     $idx = 0;
     $rows = array();
     $disable = false;
     $radios = array();
     $last_base = null;
     $rowc = array();
     foreach ($data as $row) {
 
       $diff = $row['obj'];
       $name = $row['name'];
       $id   = $row['id'];
 
       $old_class = false;
       $new_class = false;
 
       if ($id) {
         $new_checked = ($this->selectedDiffID == $id);
         $new = javelin_tag(
           'input',
           array(
             'type' => 'radio',
             'name' => 'id',
             'value' => $id,
             'checked' => $new_checked ? 'checked' : null,
             'sigil' => 'differential-new-radio',
           ));
         if ($new_checked) {
           $new_class = true;
           $disable = true;
         }
         $new = phutil_tag(
           'div',
           array(
             'class' => 'differential-update-history-radio',
           ),
           $new);
       } else {
         $new = null;
       }
 
       if ($max_id != $id) {
         $uniq = celerity_generate_unique_node_id();
         $old_checked = ($this->selectedVersusDiffID == $id);
         $old = phutil_tag(
           'input',
           array(
             'type' => 'radio',
             'name' => 'vs',
             'value' => $id,
             'id' => $uniq,
             'checked' => $old_checked ? 'checked' : null,
             'disabled' => $disable ? 'disabled' : null,
           ));
         $radios[] = $uniq;
         if ($old_checked) {
           $old_class = true;
         }
         $old = phutil_tag(
           'div',
           array(
             'class' => 'differential-update-history-radio',
           ),
           $old);
       } else {
         $old = null;
       }
 
       $desc = $row['desc'];
 
       if ($row['age']) {
         $age = phabricator_datetime($row['age'], $this->getUser());
       } else {
         $age = null;
       }
 
       if ($diff) {
         $lint = self::renderDiffLintStar($row['obj']);
         $lint = phutil_tag(
           'div',
           array(
             'class' => 'lintunit-star',
             'title' => self::getDiffLintMessage($diff),
           ),
           $lint);
 
         $unit = self::renderDiffUnitStar($row['obj']);
         $unit = phutil_tag(
           'div',
           array(
             'class' => 'lintunit-star',
             'title' => self::getDiffUnitMessage($diff),
           ),
           $unit);
 
         $base = $this->renderBaseRevision($diff);
       } else {
         $lint = null;
         $unit = null;
         $base = null;
       }
 
       if ($last_base !== null && $base !== $last_base) {
         // TODO: Render some kind of notice about rebases.
       }
       $last_base = $base;
 
       if ($revision_id) {
         $id_link = phutil_tag(
           'a',
           array(
             'href' => '/D'.$revision_id.'?id='.$id,
           ),
           $id);
       } else {
         $id_link = phutil_tag(
           'a',
           array(
             'href' => '/differential/diff/'.$id.'/',
           ),
           $id);
       }
 
       $rows[] = array(
         $name,
         $id_link,
         $base,
         $desc,
         $age,
         $lint,
         $unit,
         $old,
         $new,
       );
 
       $classes = array();
       if ($old_class) {
         $classes[] = 'differential-update-history-old-now';
       }
       if ($new_class) {
         $classes[] = 'differential-update-history-new-now';
       }
       $rowc[] = nonempty(implode(' ', $classes), null);
     }
 
     Javelin::initBehavior(
       'differential-diff-radios',
       array(
         'radios' => $radios,
       ));
 
     $options = array(
       DifferentialChangesetParser::WHITESPACE_IGNORE_ALL => pht('Ignore All'),
       DifferentialChangesetParser::WHITESPACE_IGNORE_MOST => pht('Ignore Most'),
       DifferentialChangesetParser::WHITESPACE_IGNORE_TRAILING =>
         pht('Ignore Trailing'),
       DifferentialChangesetParser::WHITESPACE_SHOW_ALL => pht('Show All'),
     );
 
     foreach ($options as $value => $label) {
       $options[$value] = phutil_tag(
         'option',
         array(
           'value' => $value,
           'selected' => ($value == $this->selectedWhitespace)
           ? 'selected'
           : null,
         ),
         $label);
     }
     $select = phutil_tag('select', array('name' => 'whitespace'), $options);
 
 
     $table = id(new AphrontTableView($rows));
     $table->setHeaders(
       array(
         pht('Diff'),
         pht('ID'),
         pht('Base'),
         pht('Description'),
         pht('Created'),
         pht('Lint'),
         pht('Unit'),
         '',
         '',
       ));
     $table->setColumnClasses(
       array(
         'pri',
         '',
         '',
         'wide',
         'date',
         'center',
         'center',
         'center differential-update-history-old',
         'center differential-update-history-new',
       ));
     $table->setRowClasses($rowc);
     $table->setDeviceVisibility(
       array(
         true,
         true,
         false,
         true,
         false,
         false,
         false,
         true,
         true,
       ));
 
     $show_diff = phutil_tag(
       'div',
       array(
         'class' => 'differential-update-history-footer',
       ),
       array(
         phutil_tag(
           'label',
           array(),
           array(
             pht('Whitespace Changes:'),
             $select,
           )),
         phutil_tag(
           'button',
           array(),
           pht('Show Diff')),
       ));
 
     $content = phabricator_form(
       $this->getUser(),
       array(
         'action' => '#toc',
       ),
       array(
         $table,
         $show_diff,
       ));
 
     return id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Revision Update History'))
       ->setFlush(true)
       ->appendChild($content);
   }
 
   const STAR_NONE = 'none';
   const STAR_OKAY = 'okay';
   const STAR_WARN = 'warn';
   const STAR_FAIL = 'fail';
   const STAR_SKIP = 'skip';
 
   public static function renderDiffLintStar(DifferentialDiff $diff) {
     static $map = array(
       DifferentialLintStatus::LINT_NONE => self::STAR_NONE,
       DifferentialLintStatus::LINT_OKAY => self::STAR_OKAY,
       DifferentialLintStatus::LINT_WARN => self::STAR_WARN,
       DifferentialLintStatus::LINT_FAIL => self::STAR_FAIL,
       DifferentialLintStatus::LINT_SKIP => self::STAR_SKIP,
       DifferentialLintStatus::LINT_AUTO_SKIP => self::STAR_SKIP,
       DifferentialLintStatus::LINT_POSTPONED => self::STAR_SKIP,
     );
 
     $star = idx($map, $diff->getLintStatus(), self::STAR_FAIL);
 
     return self::renderDiffStar($star);
   }
 
   public static function renderDiffUnitStar(DifferentialDiff $diff) {
     static $map = array(
       DifferentialUnitStatus::UNIT_NONE => self::STAR_NONE,
       DifferentialUnitStatus::UNIT_OKAY => self::STAR_OKAY,
       DifferentialUnitStatus::UNIT_WARN => self::STAR_WARN,
       DifferentialUnitStatus::UNIT_FAIL => self::STAR_FAIL,
       DifferentialUnitStatus::UNIT_SKIP => self::STAR_SKIP,
       DifferentialUnitStatus::UNIT_AUTO_SKIP => self::STAR_SKIP,
       DifferentialUnitStatus::UNIT_POSTPONED => self::STAR_SKIP,
     );
 
     $star = idx($map, $diff->getUnitStatus(), self::STAR_FAIL);
 
     return self::renderDiffStar($star);
   }
 
   public static function getDiffLintMessage(DifferentialDiff $diff) {
     switch ($diff->getLintStatus()) {
       case DifferentialLintStatus::LINT_NONE:
         return pht('No Linters Available');
       case DifferentialLintStatus::LINT_OKAY:
         return pht('Lint OK');
       case DifferentialLintStatus::LINT_WARN:
         return pht('Lint Warnings');
       case DifferentialLintStatus::LINT_FAIL:
         return pht('Lint Errors');
       case DifferentialLintStatus::LINT_SKIP:
         return pht('Lint Skipped');
       case DifferentialLintStatus::LINT_AUTO_SKIP:
         return pht('Automatic diff as part of commit; lint not applicable.');
       case DifferentialLintStatus::LINT_POSTPONED:
         return pht('Lint Postponed');
     }
     return pht('Unknown');
   }
 
   public static function getDiffUnitMessage(DifferentialDiff $diff) {
     switch ($diff->getUnitStatus()) {
       case DifferentialUnitStatus::UNIT_NONE:
         return pht('No Unit Test Coverage');
       case DifferentialUnitStatus::UNIT_OKAY:
         return pht('Unit Tests OK');
       case DifferentialUnitStatus::UNIT_WARN:
         return pht('Unit Test Warnings');
       case DifferentialUnitStatus::UNIT_FAIL:
         return pht('Unit Test Errors');
       case DifferentialUnitStatus::UNIT_SKIP:
         return pht('Unit Tests Skipped');
       case DifferentialUnitStatus::UNIT_AUTO_SKIP:
         return pht(
           'Automatic diff as part of commit; unit tests not applicable.');
       case DifferentialUnitStatus::UNIT_POSTPONED:
         return pht('Unit Tests Postponed');
     }
     return pht('Unknown');
   }
 
   private static function renderDiffStar($star) {
     $class = 'diff-star-'.$star;
     return phutil_tag(
       'span',
       array('class' => $class),
       "\xE2\x98\x85");
   }
 
   private function renderBaseRevision(DifferentialDiff $diff) {
     switch ($diff->getSourceControlSystem()) {
       case 'git':
         $base = $diff->getSourceControlBaseRevision();
         if (strpos($base, '@') === false) {
           $label = substr($base, 0, 7);
         } else {
           // The diff is from git-svn
           $base = explode('@', $base);
           $base = last($base);
           $label = $base;
         }
         break;
       case 'svn':
         $base = $diff->getSourceControlBaseRevision();
         $base = explode('@', $base);
         $base = last($base);
         $label = $base;
         break;
       default:
         $label = null;
         break;
     }
     $link = null;
     if ($label) {
       $commit_for_link = idx(
         $this->commitsForLinks,
         $diff->getSourceControlBaseRevision());
       if ($commit_for_link) {
         $link = phutil_tag(
           'a',
           array('href' => $commit_for_link->getURI()),
           $label);
       } else {
         $link = $label;
       }
     }
     return $link;
   }
 }
diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php
index 43dfdedaee..4939ac63fc 100644
--- a/src/applications/diffusion/controller/DiffusionLintController.php
+++ b/src/applications/diffusion/controller/DiffusionLintController.php
@@ -1,341 +1,341 @@
 <?php
 
 final class DiffusionLintController extends DiffusionController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   protected function processDiffusionRequest(AphrontRequest $request) {
     $user = $request->getUser();
     $drequest = $this->diffusionRequest;
 
     if ($request->getStr('lint') !== null) {
       $controller = new DiffusionLintDetailsController();
       $controller->setDiffusionRequest($drequest);
       $controller->setCurrentApplication($this->getCurrentApplication());
       return $this->delegateToController($controller);
     }
 
     $owners = array();
     if (!$drequest) {
       if (!$request->getArr('owner')) {
         $owners = array($user->getPHID());
       } else {
         $owners = array(head($request->getArr('owner')));
       }
     }
 
     $codes = $this->loadLintCodes($owners);
 
     if ($codes && !$drequest) {
       // TODO: Build some real Query classes for this stuff.
 
       $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
         'id IN (%Ld)',
         array_unique(ipull($codes, 'branchID')));
 
       $repositories = id(new PhabricatorRepositoryQuery())
         ->setViewer($user)
         ->withIDs(mpull($branches, 'getRepositoryID'))
         ->execute();
 
       $drequests = array();
       foreach ($branches as $id => $branch) {
         if (empty($repositories[$branch->getRepositoryID()])) {
           continue;
         }
 
         $drequests[$id] = DiffusionRequest::newFromDictionary(array(
           'user' => $user,
           'repository' => $repositories[$branch->getRepositoryID()],
           'branch' => $branch->getName(),
         ));
       }
     }
 
     $rows = array();
     $total = 0;
     foreach ($codes as $code) {
       if (!$this->diffusionRequest) {
         $drequest = idx($drequests, $code['branchID']);
       }
 
       if (!$drequest) {
         continue;
       }
 
       $total += $code['n'];
 
       $href_lint = $drequest->generateURI(array(
         'action' => 'lint',
         'lint' => $code['code'],
       ));
       $href_browse = $drequest->generateURI(array(
         'action' => 'browse',
         'lint' => $code['code'],
       ));
       $href_repo = $drequest->generateURI(array('action' => 'lint'));
 
       $rows[] = array(
         phutil_tag('a', array('href' => $href_lint), $code['n']),
         phutil_tag('a', array('href' => $href_browse), $code['files']),
         phutil_tag('a', array('href' => $href_repo), $drequest->getCallsign()),
         ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']),
         $code['code'],
         $code['maxName'],
         $code['maxDescription'],
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(array(
         pht('Problems'),
         pht('Files'),
         pht('Repository'),
         pht('Severity'),
         pht('Code'),
         pht('Name'),
         pht('Example'),
       ))
       ->setColumnVisibility(array(true, true, !$this->diffusionRequest))
       ->setColumnClasses(array('n', 'n', '', '', 'pri', '', ''));
 
     $content = array();
 
     if (!$this->diffusionRequest) {
       $form = id(new AphrontFormView())
         ->setUser($user)
         ->setMethod('GET')
         ->appendControl(
           id(new AphrontFormTokenizerControl())
             ->setDatasource(new PhabricatorPeopleDatasource())
             ->setLimit(1)
             ->setName('owner')
             ->setLabel(pht('Owner'))
             ->setValue($owners))
         ->appendChild(
           id(new AphrontFormSubmitControl())
-            ->setValue('Filter'));
+            ->setValue(pht('Filter')));
       $content[] = id(new AphrontListFilterView())->appendChild($form);
     }
 
     $content[] = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Lint'))
       ->appendChild($table);
 
     $title = array('Lint');
     $crumbs = $this->buildCrumbs(
       array(
         'branch' => true,
         'path'   => true,
         'view'   => 'lint',
       ));
 
     if ($this->diffusionRequest) {
       $title[] = $drequest->getCallsign();
     } else {
       $crumbs->addTextCrumb(pht('All Lint'));
     }
 
     if ($this->diffusionRequest) {
       $branch = $drequest->loadBranch();
 
       $header = id(new PHUIHeaderView())
         ->setHeader($this->renderPathLinks($drequest, 'lint'))
         ->setUser($user)
         ->setPolicyObject($drequest->getRepository());
       $actions = $this->buildActionView($drequest);
       $properties = $this->buildPropertyView(
         $drequest,
         $branch,
         $total,
         $actions);
 
       $object_box = id(new PHUIObjectBoxView())
         ->setHeader($header)
         ->addPropertyList($properties);
     } else {
       $object_box = null;
     }
 
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $object_box,
         $content,
       ),
       array(
         'title' => $title,
       ));
   }
 
   private function loadLintCodes(array $owner_phids) {
     $drequest = $this->diffusionRequest;
     $conn = id(new PhabricatorRepository())->establishConnection('r');
     $where = array('1 = 1');
 
     if ($drequest) {
       $branch = $drequest->loadBranch();
       if (!$branch) {
         return array();
       }
 
       $where[] = qsprintf($conn, 'branchID = %d', $branch->getID());
 
       if ($drequest->getPath() != '') {
         $path = '/'.$drequest->getPath();
         $is_dir = (substr($path, -1) == '/');
         $where[] = ($is_dir
           ? qsprintf($conn, 'path LIKE %>', $path)
           : qsprintf($conn, 'path = %s', $path));
       }
     }
 
     if ($owner_phids) {
       $or = array();
       $or[] = qsprintf($conn, 'authorPHID IN (%Ls)', $owner_phids);
 
       $paths = array();
       $packages = id(new PhabricatorOwnersOwner())
         ->loadAllWhere('userPHID IN (%Ls)', $owner_phids);
       if ($packages) {
         $paths = id(new PhabricatorOwnersPath())->loadAllWhere(
           'packageID IN (%Ld)',
           mpull($packages, 'getPackageID'));
       }
 
       if ($paths) {
         $repositories = id(new PhabricatorRepositoryQuery())
           ->setViewer($this->getRequest()->getUser())
           ->withPHIDs(mpull($paths, 'getRepositoryPHID'))
           ->execute();
         $repositories = mpull($repositories, 'getID', 'getPHID');
 
         $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
           'repositoryID IN (%Ld)',
           $repositories);
         $branches = mgroup($branches, 'getRepositoryID');
       }
 
       foreach ($paths as $path) {
         $branch = idx(
           $branches,
           idx(
             $repositories,
             $path->getRepositoryPHID()));
         if ($branch) {
           $condition = qsprintf(
             $conn,
             '(branchID IN (%Ld) AND path LIKE %>)',
             array_keys($branch),
             $path->getPath());
           if ($path->getExcluded()) {
             $where[] = 'NOT '.$condition;
           } else {
             $or[] = $condition;
           }
         }
       }
       $where[] = '('.implode(' OR ', $or).')';
     }
 
     return queryfx_all(
       $conn,
       'SELECT
           branchID,
           code,
           MAX(severity) AS maxSeverity,
           MAX(name) AS maxName,
           MAX(description) AS maxDescription,
           COUNT(DISTINCT path) AS files,
           COUNT(*) AS n
         FROM %T
         WHERE %Q
         GROUP BY branchID, code
         ORDER BY n DESC',
       PhabricatorRepository::TABLE_LINTMESSAGE,
       implode(' AND ', $where));
   }
 
   protected function buildActionView(DiffusionRequest $drequest) {
     $viewer = $this->getRequest()->getUser();
 
     $view = id(new PhabricatorActionListView())
       ->setUser($viewer);
 
     $list_uri = $drequest->generateURI(
       array(
         'action' => 'lint',
         'lint' => '',
       ));
 
     $view->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('View As List'))
         ->setHref($list_uri)
         ->setIcon('fa-list'));
 
     $history_uri = $drequest->generateURI(
       array(
         'action' => 'history',
       ));
 
     $view->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('View History'))
         ->setHref($history_uri)
         ->setIcon('fa-clock-o'));
 
     $browse_uri = $drequest->generateURI(
       array(
         'action' => 'browse',
       ));
 
     $view->addAction(
       id(new PhabricatorActionView())
         ->setName(pht('Browse Content'))
         ->setHref($browse_uri)
         ->setIcon('fa-files-o'));
 
     return $view;
   }
 
   protected function buildPropertyView(
     DiffusionRequest $drequest,
     PhabricatorRepositoryBranch $branch,
     $total,
     PhabricatorActionListView $actions) {
 
     $viewer = $this->getRequest()->getUser();
 
     $view = id(new PHUIPropertyListView())
       ->setUser($viewer)
       ->setActionList($actions);
 
     $callsign = $drequest->getRepository()->getCallsign();
     $lint_commit = $branch->getLintCommit();
 
     $view->addProperty(
       pht('Lint Commit'),
       phutil_tag(
         'a',
         array(
           'href' => $drequest->generateURI(
             array(
               'action' => 'commit',
               'commit' => $lint_commit,
             )),
         ),
         $drequest->getRepository()->formatCommitName($lint_commit)));
 
     $view->addProperty(
       pht('Total Messages'),
       pht('%s', new PhutilNumber($total)));
 
     return $view;
   }
 
 
 }
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php
index 2aece855e9..1fcf366967 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php
@@ -1,285 +1,285 @@
 <?php
 
 final class DiffusionRepositoryEditHostingController
   extends DiffusionRepositoryEditController {
 
   private $serve;
 
   protected function processDiffusionRequest(AphrontRequest $request) {
     $user = $request->getUser();
     $drequest = $this->diffusionRequest;
     $repository = $drequest->getRepository();
     $this->serve = $request->getURIData('serve');
 
     $repository = id(new PhabricatorRepositoryQuery())
       ->setViewer($user)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->withIDs(array($repository->getID()))
       ->executeOne();
     if (!$repository) {
       return new Aphront404Response();
     }
 
     if (!$this->serve) {
       return $this->handleHosting($repository);
     } else {
       return $this->handleProtocols($repository);
     }
   }
 
   public function handleHosting(PhabricatorRepository $repository) {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $v_hosting = $repository->isHosted();
 
     $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
     $next_uri = $this->getRepositoryControllerURI($repository, 'edit/serve/');
 
     if ($request->isFormPost()) {
       $v_hosting = $request->getBool('hosting');
 
       $xactions = array();
       $template = id(new PhabricatorRepositoryTransaction());
 
       $type_hosting = PhabricatorRepositoryTransaction::TYPE_HOSTING;
 
       $xactions[] = id(clone $template)
         ->setTransactionType($type_hosting)
         ->setNewValue($v_hosting);
 
       id(new PhabricatorRepositoryEditor())
         ->setContinueOnNoEffect(true)
         ->setContentSourceFromRequest($request)
         ->setActor($user)
         ->applyTransactions($repository, $xactions);
 
       return id(new AphrontRedirectResponse())->setURI($next_uri);
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Edit Hosting'));
 
     $title = pht('Edit Hosting (%s)', $repository->getName());
 
     $hosted_control = id(new AphrontFormRadioButtonControl())
         ->setName('hosting')
         ->setLabel(pht('Hosting'))
         ->addButton(
           true,
           pht('Host Repository on Phabricator'),
           pht(
             'Phabricator will host this repository. Users will be able to '.
             'push commits to Phabricator. Phabricator will not pull '.
             'changes from elsewhere.'))
         ->addButton(
           false,
           pht('Host Repository Elsewhere'),
           pht(
             'Phabricator will pull updates to this repository from a master '.
             'repository elsewhere (for example, on GitHub or Bitbucket). '.
             'Users will not be able to push commits to this repository.'))
         ->setValue($v_hosting);
 
     $doc_href = PhabricatorEnv::getDoclink(
       'Diffusion User Guide: Repository Hosting');
 
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->appendRemarkupInstructions(
         pht(
           'Phabricator can host repositories, or it can track repositories '.
           'hosted elsewhere (like on GitHub or Bitbucket). For information '.
           'on configuring hosting, see [[ %s | Diffusion User Guide: '.
           'Repository Hosting]]',
           $doc_href))
       ->appendChild($hosted_control)
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Save and Continue'))
           ->addCancelButton($edit_uri));
 
     $object_box = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setForm($form);
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $object_box,
       ),
       array(
         'title' => $title,
       ));
   }
 
   public function handleProtocols(PhabricatorRepository $repository) {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $type = $repository->getVersionControlSystem();
     $is_svn = ($type == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN);
 
     $v_http_mode = $repository->getDetail(
       'serve-over-http',
       PhabricatorRepository::SERVE_OFF);
     $v_ssh_mode = $repository->getDetail(
       'serve-over-ssh',
       PhabricatorRepository::SERVE_OFF);
 
     $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
     $prev_uri = $this->getRepositoryControllerURI($repository, 'edit/hosting/');
 
     if ($request->isFormPost()) {
       $v_http_mode = $request->getStr('http');
       $v_ssh_mode = $request->getStr('ssh');
 
       $xactions = array();
       $template = id(new PhabricatorRepositoryTransaction());
 
       $type_http = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP;
       $type_ssh = PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH;
 
       if (!$is_svn) {
         $xactions[] = id(clone $template)
           ->setTransactionType($type_http)
           ->setNewValue($v_http_mode);
       }
 
       $xactions[] = id(clone $template)
         ->setTransactionType($type_ssh)
         ->setNewValue($v_ssh_mode);
 
       id(new PhabricatorRepositoryEditor())
         ->setContinueOnNoEffect(true)
         ->setContentSourceFromRequest($request)
         ->setActor($user)
         ->applyTransactions($repository, $xactions);
 
       return id(new AphrontRedirectResponse())->setURI($edit_uri);
     }
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Edit Protocols'));
 
     $title = pht('Edit Protocols (%s)', $repository->getName());
 
 
     $rw_message = pht(
       'Phabricator will serve a read-write copy of this repository.');
 
     if (!$repository->isHosted()) {
       $rw_message = array(
         $rw_message,
         phutil_tag('br'),
         phutil_tag('br'),
         pht(
           '%s: This repository is hosted elsewhere, so Phabricator can not '.
           'perform writes. This mode will act like "Read Only" for '.
           'repositories hosted elsewhere.',
-          phutil_tag('strong', array(), 'WARNING')),
+          phutil_tag('strong', array(), pht('WARNING'))),
       );
     }
 
     $ssh_control =
       id(new AphrontFormRadioButtonControl())
         ->setName('ssh')
         ->setLabel(pht('SSH'))
         ->setValue($v_ssh_mode)
         ->addButton(
           PhabricatorRepository::SERVE_OFF,
           PhabricatorRepository::getProtocolAvailabilityName(
             PhabricatorRepository::SERVE_OFF),
           pht('Phabricator will not serve this repository over SSH.'))
         ->addButton(
           PhabricatorRepository::SERVE_READONLY,
           PhabricatorRepository::getProtocolAvailabilityName(
             PhabricatorRepository::SERVE_READONLY),
           pht(
             'Phabricator will serve a read-only copy of this repository '.
             'over SSH.'))
         ->addButton(
           PhabricatorRepository::SERVE_READWRITE,
           PhabricatorRepository::getProtocolAvailabilityName(
             PhabricatorRepository::SERVE_READWRITE),
           $rw_message);
 
     $http_control =
       id(new AphrontFormRadioButtonControl())
         ->setName('http')
         ->setLabel(pht('HTTP'))
         ->setValue($v_http_mode)
         ->addButton(
           PhabricatorRepository::SERVE_OFF,
           PhabricatorRepository::getProtocolAvailabilityName(
             PhabricatorRepository::SERVE_OFF),
           pht('Phabricator will not serve this repository over HTTP.'))
         ->addButton(
           PhabricatorRepository::SERVE_READONLY,
           PhabricatorRepository::getProtocolAvailabilityName(
             PhabricatorRepository::SERVE_READONLY),
           pht(
             'Phabricator will serve a read-only copy of this repository '.
             'over HTTP.'))
         ->addButton(
           PhabricatorRepository::SERVE_READWRITE,
           PhabricatorRepository::getProtocolAvailabilityName(
             PhabricatorRepository::SERVE_READWRITE),
           $rw_message);
 
     if ($is_svn) {
       $http_control = id(new AphrontFormMarkupControl())
         ->setLabel(pht('HTTP'))
         ->setValue(
           phutil_tag(
             'em',
             array(),
             pht(
               'Phabricator does not currently support HTTP access to '.
               'Subversion repositories.')));
     }
 
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->appendRemarkupInstructions(
         pht(
           'Phabricator can serve repositories over various protocols. You can '.
           'configure server protocols here.'))
       ->appendChild($ssh_control);
 
     if (!PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth')) {
       $form->appendRemarkupInstructions(
         pht(
           'NOTE: The configuration setting [[ %s | %s ]] is currently '.
           'disabled. You must enable it to activate authenticated access '.
           'to repositories over HTTP.',
           '/config/edit/diffusion.allow-http-auth/',
           'diffusion.allow-http-auth'));
     }
 
     $form
       ->appendChild($http_control)
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Save Changes'))
           ->addCancelButton($prev_uri, pht('Back')));
 
     $object_box = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setForm($form);
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $object_box,
       ),
       array(
         'title' => $title,
       ));
   }
 
 }
diff --git a/src/applications/diffusion/view/DiffusionHistoryTableView.php b/src/applications/diffusion/view/DiffusionHistoryTableView.php
index 80220f7c21..ce1a693696 100644
--- a/src/applications/diffusion/view/DiffusionHistoryTableView.php
+++ b/src/applications/diffusion/view/DiffusionHistoryTableView.php
@@ -1,400 +1,400 @@
 <?php
 
 final class DiffusionHistoryTableView extends DiffusionView {
 
   private $history;
   private $revisions = array();
   private $handles = array();
   private $isHead;
   private $parents;
   private $buildCache;
 
   public function setHistory(array $history) {
     assert_instances_of($history, 'DiffusionPathChange');
     $this->history = $history;
     $this->buildCache = null;
     return $this;
   }
 
   public function loadRevisions() {
     $commit_phids = array();
     foreach ($this->history as $item) {
       if ($item->getCommit()) {
         $commit_phids[] = $item->getCommit()->getPHID();
       }
     }
 
     // TODO: Get rid of this.
     $this->revisions = id(new DifferentialRevision())
       ->loadIDsByCommitPHIDs($commit_phids);
     return $this;
   }
 
   public function setHandles(array $handles) {
     assert_instances_of($handles, 'PhabricatorObjectHandle');
     $this->handles = $handles;
     return $this;
   }
 
   public function getRequiredHandlePHIDs() {
     $phids = array();
     foreach ($this->history as $item) {
       $data = $item->getCommitData();
       if ($data) {
         if ($data->getCommitDetail('authorPHID')) {
           $phids[$data->getCommitDetail('authorPHID')] = true;
         }
         if ($data->getCommitDetail('committerPHID')) {
           $phids[$data->getCommitDetail('committerPHID')] = true;
         }
       }
     }
     return array_keys($phids);
   }
 
   public function setParents(array $parents) {
     $this->parents = $parents;
     return $this;
   }
 
   public function setIsHead($is_head) {
     $this->isHead = $is_head;
     return $this;
   }
 
   public function loadBuildablesOnDemand() {
     if ($this->buildCache !== null) {
       return $this->buildCache;
     }
 
     $commits_to_builds = array();
 
     $commits = mpull($this->history, 'getCommit');
 
     $commit_phids = mpull($commits, 'getPHID');
 
     $buildables = id(new HarbormasterBuildableQuery())
       ->setViewer($this->getUser())
       ->withBuildablePHIDs($commit_phids)
       ->withManualBuildables(false)
       ->execute();
 
     $this->buildCache = mpull($buildables, null, 'getBuildablePHID');
 
     return $this->buildCache;
   }
 
   public function render() {
     $drequest = $this->getDiffusionRequest();
 
     $handles = $this->handles;
 
     $graph = null;
     if ($this->parents) {
       $graph = $this->renderGraph();
     }
 
     $show_builds = PhabricatorApplication::isClassInstalledForViewer(
       'PhabricatorHarbormasterApplication',
       $this->getUser());
 
     $rows = array();
     $ii = 0;
     foreach ($this->history as $history) {
       $epoch = $history->getEpoch();
 
       if ($epoch) {
         $date = phabricator_date($epoch, $this->user);
         $time = phabricator_time($epoch, $this->user);
       } else {
         $date = null;
         $time = null;
       }
 
       $data = $history->getCommitData();
       $author_phid = $committer = $committer_phid = null;
       if ($data) {
         $author_phid = $data->getCommitDetail('authorPHID');
         $committer_phid = $data->getCommitDetail('committerPHID');
         $committer = $data->getCommitDetail('committer');
       }
 
       if ($author_phid && isset($handles[$author_phid])) {
         $author = $handles[$author_phid]->renderLink();
       } else {
         $author = self::renderName($history->getAuthorName());
       }
 
       $different_committer = false;
       if ($committer_phid) {
         $different_committer = ($committer_phid != $author_phid);
       } else if ($committer != '') {
         $different_committer = ($committer != $history->getAuthorName());
       }
       if ($different_committer) {
         if ($committer_phid && isset($handles[$committer_phid])) {
           $committer = $handles[$committer_phid]->renderLink();
         } else {
           $committer = self::renderName($committer);
         }
         $author = hsprintf('%s/%s', $author, $committer);
       }
 
       // We can show details once the message and change have been imported.
       $partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
                         PhabricatorRepositoryCommit::IMPORTED_CHANGE;
 
       $commit = $history->getCommit();
       if ($commit && $commit->isPartiallyImported($partial_import) && $data) {
         $summary = AphrontTableView::renderSingleDisplayLine(
           $history->getSummary());
       } else {
-        $summary = phutil_tag('em', array(), "Importing\xE2\x80\xA6");
+        $summary = phutil_tag('em', array(), pht("Importing\xE2\x80\xA6"));
       }
 
       $build = null;
       if ($show_builds) {
         $buildable_lookup = $this->loadBuildablesOnDemand();
         $buildable = idx($buildable_lookup, $commit->getPHID());
         if ($buildable !== null) {
           $icon = HarbormasterBuildable::getBuildableStatusIcon(
             $buildable->getBuildableStatus());
           $color = HarbormasterBuildable::getBuildableStatusColor(
             $buildable->getBuildableStatus());
           $name = HarbormasterBuildable::getBuildableStatusName(
             $buildable->getBuildableStatus());
 
           $icon_view = id(new PHUIIconView())
             ->setIconFont($icon.' '.$color);
 
           $tooltip_view = javelin_tag(
             'span',
             array(
               'sigil' => 'has-tooltip',
               'meta' => array('tip' => $name),
             ),
             $icon_view);
 
           Javelin::initBehavior('phabricator-tooltips');
 
           $href_view = phutil_tag(
             'a',
             array('href' => '/'.$buildable->getMonogram()),
             $tooltip_view);
 
           $build = $href_view;
 
           $has_any_build = true;
         }
       }
 
       $rows[] = array(
         $graph ? $graph[$ii++] : null,
         self::linkCommit(
           $drequest->getRepository(),
           $history->getCommitIdentifier()),
         $build,
         ($commit ?
           self::linkRevision(idx($this->revisions, $commit->getPHID())) :
           null),
         $author,
         $summary,
         $date,
         $time,
       );
     }
 
     $view = new AphrontTableView($rows);
     $view->setHeaders(
       array(
         '',
         pht('Commit'),
         '',
         pht('Revision'),
         pht('Author/Committer'),
         pht('Details'),
         pht('Date'),
         pht('Time'),
       ));
     $view->setColumnClasses(
       array(
         'threads',
         'n',
         'icon',
         'n',
         '',
         'wide',
         '',
         'right',
       ));
     $view->setColumnVisibility(
       array(
         $graph ? true : false,
       ));
     $view->setDeviceVisibility(
       array(
         $graph ? true : false,
         true,
         true,
         true,
         false,
         true,
         false,
         false,
       ));
     return $view->render();
   }
 
   /**
    * Draw a merge/branch graph from the parent revision data. We're basically
    * building up a bunch of strings like this:
    *
    *  ^
    *  |^
    *  o|
    *  |o
    *  o
    *
    * ...which form an ASCII representation of the graph we eventually want to
    * draw.
    *
    * NOTE: The actual implementation is black magic.
    */
   private function renderGraph() {
     // This keeps our accumulated information about each line of the
     // merge/branch graph.
     $graph = array();
 
     // This holds the next commit we're looking for in each column of the
     // graph.
     $threads = array();
 
     // This is the largest number of columns any row has, i.e. the width of
     // the graph.
     $count = 0;
 
     foreach ($this->history as $key => $history) {
       $joins = array();
       $splits = array();
 
       $parent_list = $this->parents[$history->getCommitIdentifier()];
 
       // Look for some thread which has this commit as the next commit. If
       // we find one, this commit goes on that thread. Otherwise, this commit
       // goes on a new thread.
 
       $line = '';
       $found = false;
       $pos = count($threads);
       for ($n = 0; $n < $count; $n++) {
         if (empty($threads[$n])) {
           $line .= ' ';
           continue;
         }
 
         if ($threads[$n] == $history->getCommitIdentifier()) {
           if ($found) {
             $line .= ' ';
             $joins[] = $n;
             unset($threads[$n]);
           } else {
             $line .= 'o';
             $found = true;
             $pos = $n;
           }
         } else {
 
           // We render a "|" for any threads which have a commit that we haven't
           // seen yet, this is later drawn as a vertical line.
           $line .= '|';
         }
       }
 
       // If we didn't find the thread this commit goes on, start a new thread.
       // We use "o" to mark the commit for the rendering engine, or "^" to
       // indicate that there's nothing after it so the line from the commit
       // upward should not be drawn.
 
       if (!$found) {
         if ($this->isHead) {
           $line .= '^';
         } else {
           $line .= 'o';
           foreach ($graph as $k => $meta) {
             // Go back across all the lines we've already drawn and add a
             // "|" to the end, since this is connected to some future commit
             // we don't know about.
             for ($jj = strlen($meta['line']); $jj <= $count; $jj++) {
               $graph[$k]['line'] .= '|';
             }
           }
         }
       }
 
       // Update the next commit on this thread to the commit's first parent.
       // This might have the effect of making a new thread.
       $threads[$pos] = head($parent_list);
 
       // If we made a new thread, increase the thread count.
       $count = max($pos + 1, $count);
 
       // Now, deal with splits (merges). I picked this terms opposite to the
       // underlying repository term to confuse you.
       foreach (array_slice($parent_list, 1) as $parent) {
         $found = false;
 
         // Try to find the other parent(s) in our existing threads. If we find
         // them, split to that thread.
 
         foreach ($threads as $idx => $thread_commit) {
           if ($thread_commit == $parent) {
             $found = true;
             $splits[] = $idx;
           }
         }
 
         // If we didn't find the parent, we don't know about it yet. Find the
         // first free thread and add it as the "next" commit in that thread.
         // This might create a new thread.
 
         if (!$found) {
           for ($n = 0; $n < $count; $n++) {
             if (empty($threads[$n])) {
               break;
             }
           }
           $threads[$n] = $parent;
           $splits[] = $n;
           $count = max($n + 1, $count);
         }
       }
 
       $graph[] = array(
         'line' => $line,
         'split' => $splits,
         'join' => $joins,
       );
     }
 
     // Render into tags for the behavior.
 
     foreach ($graph as $k => $meta) {
       $graph[$k] = javelin_tag(
         'div',
         array(
           'sigil' => 'commit-graph',
           'meta' => $meta,
         ),
         '');
     }
 
     Javelin::initBehavior(
       'diffusion-commit-graph',
       array(
         'count' => $count,
       ));
 
     return $graph;
   }
 
 }
diff --git a/src/applications/doorkeeper/engine/DoorkeeperObjectRef.php b/src/applications/doorkeeper/engine/DoorkeeperObjectRef.php
index 0244532bcc..3cf2a8dbe2 100644
--- a/src/applications/doorkeeper/engine/DoorkeeperObjectRef.php
+++ b/src/applications/doorkeeper/engine/DoorkeeperObjectRef.php
@@ -1,126 +1,125 @@
 <?php
 
 final class DoorkeeperObjectRef extends Phobject {
 
   private $objectKey;
   private $applicationType;
   private $applicationDomain;
   private $objectType;
   private $objectID;
   private $attributes = array();
   private $isVisible;
   private $syncFailed;
   private $externalObject;
 
   public function newExternalObject() {
     return id(new DoorkeeperExternalObject())
       ->setApplicationType($this->getApplicationType())
       ->setApplicationDomain($this->getApplicationDomain())
       ->setObjectType($this->getObjectType())
       ->setObjectID($this->getObjectID())
       ->setViewPolicy(PhabricatorPolicies::POLICY_USER);
   }
 
   public function attachExternalObject(
     DoorkeeperExternalObject $external_object) {
     $this->externalObject = $external_object;
     return $this;
   }
 
   public function getExternalObject() {
     if (!$this->externalObject) {
-      throw new Exception(
-        'Call attachExternalObject() before getExternalObject()!');
+      throw new PhutilInvalidStateException('attachExternalObject');
     }
     return $this->externalObject;
   }
 
   public function setIsVisible($is_visible) {
     $this->isVisible = $is_visible;
     return $this;
   }
 
   public function getIsVisible() {
     return $this->isVisible;
   }
 
   public function setSyncFailed($sync_failed) {
     $this->syncFailed = $sync_failed;
     return $this;
   }
 
   public function getSyncFailed() {
     return $this->syncFailed;
   }
 
   public function getAttribute($key, $default = null) {
     return idx($this->attributes, $key, $default);
   }
 
   public function setAttribute($key, $value) {
     $this->attributes[$key] = $value;
     return $this;
   }
 
   public function setObjectID($object_id) {
     $this->objectID = $object_id;
     return $this;
   }
 
   public function getObjectID() {
     return $this->objectID;
   }
 
 
   public function setObjectType($object_type) {
     $this->objectType = $object_type;
     return $this;
   }
 
   public function getObjectType() {
     return $this->objectType;
   }
 
 
   public function setApplicationDomain($application_domain) {
     $this->applicationDomain = $application_domain;
     return $this;
   }
 
   public function getApplicationDomain() {
     return $this->applicationDomain;
   }
 
 
   public function setApplicationType($application_type) {
     $this->applicationType = $application_type;
     return $this;
   }
 
   public function getApplicationType() {
     return $this->applicationType;
   }
 
   public function getFullName() {
     return coalesce(
       $this->getAttribute('fullname'),
       $this->getAttribute('name'),
       pht('External Object'));
   }
 
   public function getObjectKey() {
     if (!$this->objectKey) {
       $this->objectKey = PhabricatorHash::digestForIndex(
         implode(
           ':',
           array(
             $this->getApplicationType(),
             $this->getApplicationDomain(),
             $this->getObjectType(),
             $this->getObjectID(),
           )));
     }
     return $this->objectKey;
   }
 
 }
diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
index 226815a642..d98d33a5e3 100644
--- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
@@ -1,476 +1,476 @@
 <?php
 
 /**
  * @task lease      Lease Acquisition
  * @task resource   Resource Allocation
  * @task log        Logging
  */
 abstract class DrydockBlueprintImplementation {
 
   private $activeResource;
   private $activeLease;
   private $instance;
 
   abstract public function getType();
   abstract public function getInterface(
     DrydockResource $resource,
     DrydockLease $lease,
     $type);
 
   abstract public function isEnabled();
 
   abstract public function getBlueprintName();
   abstract public function getDescription();
 
   public function getBlueprintClass() {
     return get_class($this);
   }
 
   protected function loadLease($lease_id) {
     // TODO: Get rid of this?
     $query = id(new DrydockLeaseQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withIDs(array($lease_id))
       ->execute();
 
     $lease = idx($query, $lease_id);
 
     if (!$lease) {
       throw new Exception(pht("No such lease '%d'!", $lease_id));
     }
 
     return $lease;
   }
 
   protected function getInstance() {
     if (!$this->instance) {
       throw new Exception(
         pht('Attach the blueprint instance to the implementation.'));
     }
 
     return $this->instance;
   }
 
   public function attachInstance(DrydockBlueprint $instance) {
     $this->instance = $instance;
     return $this;
   }
 
   public function getFieldSpecifications() {
     return array();
   }
 
   public function getDetail($key, $default = null) {
     return $this->getInstance()->getDetail($key, $default);
   }
 
 
 /* -(  Lease Acquisition  )-------------------------------------------------- */
 
 
   /**
    * @task lease
    */
   final public function filterResource(
     DrydockResource $resource,
     DrydockLease $lease) {
 
     $scope = $this->pushActiveScope($resource, $lease);
 
     return $this->canAllocateLease($resource, $lease);
   }
 
 
   /**
    * Enforce basic checks on lease/resource compatibility. Allows resources to
    * reject leases if they are incompatible, even if the resource types match.
    *
    * For example, if a resource represents a 32-bit host, this method might
    * reject leases that need a 64-bit host. If a resource represents a working
    * copy of repository "X", this method might reject leases which need a
    * working copy of repository "Y". Generally, although the main types of
    * a lease and resource may match (e.g., both "host"), it may not actually be
    * possible to satisfy the lease with a specific resource.
    *
    * This method generally should not enforce limits or perform capacity
    * checks. Perform those in @{method:shouldAllocateLease} instead. It also
    * should not perform actual acquisition of the lease; perform that in
    * @{method:executeAcquireLease} instead.
    *
    * @param   DrydockResource   Candidiate resource to allocate the lease on.
    * @param   DrydockLease      Pending lease that wants to allocate here.
    * @return  bool              True if the resource and lease are compatible.
    * @task lease
    */
   abstract protected function canAllocateLease(
     DrydockResource $resource,
     DrydockLease $lease);
 
 
   /**
    * @task lease
    */
   final public function allocateLease(
     DrydockResource $resource,
     DrydockLease $lease) {
 
     $scope = $this->pushActiveScope($resource, $lease);
 
     $this->log(pht('Trying to Allocate Lease'));
 
     $lease->setStatus(DrydockLeaseStatus::STATUS_ACQUIRING);
     $lease->setResourceID($resource->getID());
     $lease->attachResource($resource);
 
     $ephemeral_lease = id(clone $lease)->makeEphemeral();
 
     $allocated = false;
     $allocation_exception = null;
 
     $resource->openTransaction();
       $resource->beginReadLocking();
         $resource->reload();
 
         // TODO: Policy stuff.
         $other_leases = id(new DrydockLease())->loadAllWhere(
           'status IN (%Ld) AND resourceID = %d',
           array(
             DrydockLeaseStatus::STATUS_ACQUIRING,
             DrydockLeaseStatus::STATUS_ACTIVE,
           ),
           $resource->getID());
 
         try {
           $allocated = $this->shouldAllocateLease(
             $resource,
             $ephemeral_lease,
             $other_leases);
         } catch (Exception $ex) {
           $allocation_exception = $ex;
         }
 
         if ($allocated) {
           $lease->save();
         }
       $resource->endReadLocking();
     if ($allocated) {
       $resource->saveTransaction();
-      $this->log('Allocated Lease');
+      $this->log(pht('Allocated Lease'));
     } else {
       $resource->killTransaction();
       $this->log(pht('Failed to Allocate Lease'));
     }
 
     if ($allocation_exception) {
       $this->logException($allocation_exception);
     }
 
     return $allocated;
   }
 
 
   /**
    * Enforce lease limits on resources. Allows resources to reject leases if
    * they would become over-allocated by accepting them.
    *
    * For example, if a resource represents disk space, this method might check
    * how much space the lease is asking for (say, 200MB) and how much space is
    * left unallocated on the resource. It could grant the lease (return true)
    * if it has enough remaining space (more than 200MB), and reject the lease
    * (return false) if it does not (less than 200MB).
    *
    * A resource might also allow only exclusive leases. In this case it could
    * accept a new lease (return true) if there are no active leases, or reject
    * the new lease (return false) if there any other leases.
    *
    * A lock is held on the resource while this method executes to prevent
    * multiple processes from allocating leases on the resource simultaneously.
    * However, this means you should implement the method as cheaply as possible.
    * In particular, do not perform any actual acquisition or setup in this
    * method.
    *
    * If allocation is permitted, the lease will be moved to `ACQUIRING` status
    * and @{method:executeAcquireLease} will be called to actually perform
    * acquisition.
    *
    * General compatibility checks unrelated to resource limits and capacity are
    * better implemented in @{method:canAllocateLease}, which serves as a
    * cheap filter before lock acquisition.
    *
    * @param   DrydockResource     Candidate resource to allocate the lease on.
    * @param   DrydockLease        Pending lease that wants to allocate here.
    * @param   list<DrydockLease>  Other allocated and acquired leases on the
    *                              resource. The implementation can inspect them
    *                              to verify it can safely add the new lease.
    * @return  bool                True to allocate the lease on the resource;
    *                              false to reject it.
    * @task lease
    */
   abstract protected function shouldAllocateLease(
     DrydockResource $resource,
     DrydockLease $lease,
     array $other_leases);
 
 
   /**
    * @task lease
    */
   final public function acquireLease(
     DrydockResource $resource,
     DrydockLease $lease) {
 
     $scope = $this->pushActiveScope($resource, $lease);
 
     $this->log(pht('Acquiring Lease'));
     $lease->setStatus(DrydockLeaseStatus::STATUS_ACTIVE);
     $lease->setResourceID($resource->getID());
     $lease->attachResource($resource);
 
     $ephemeral_lease = id(clone $lease)->makeEphemeral();
 
     try {
       $this->executeAcquireLease($resource, $ephemeral_lease);
     } catch (Exception $ex) {
       $this->logException($ex);
       throw $ex;
     }
 
     $lease->setAttributes($ephemeral_lease->getAttributes());
     $lease->save();
     $this->log(pht('Acquired Lease'));
   }
 
 
   /**
    * Acquire and activate an allocated lease. Allows resources to peform setup
    * as leases are brought online.
    *
    * Following a successful call to @{method:canAllocateLease}, a lease is moved
    * to `ACQUIRING` status and this method is called after resource locks are
    * released. Nothing is locked while this method executes; the implementation
    * is free to perform expensive operations like writing files and directories,
    * executing commands, etc.
    *
    * After this method executes, the lease status is moved to `ACTIVE` and the
    * original leasee may access it.
    *
    * If acquisition fails, throw an exception.
    *
    * @param   DrydockResource   Resource to acquire a lease on.
    * @param   DrydockLease      Lease to acquire.
    * @return  void
    */
   abstract protected function executeAcquireLease(
     DrydockResource $resource,
     DrydockLease $lease);
 
 
 
   final public function releaseLease(
     DrydockResource $resource,
     DrydockLease $lease) {
     $scope = $this->pushActiveScope(null, $lease);
 
     $released = false;
 
     $lease->openTransaction();
       $lease->beginReadLocking();
         $lease->reload();
 
         if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) {
           $lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
           $lease->save();
           $released = true;
         }
 
       $lease->endReadLocking();
     $lease->saveTransaction();
 
     if (!$released) {
       throw new Exception(pht('Unable to release lease: lease not active!'));
     }
 
   }
 
 
 
 /* -(  Resource Allocation  )------------------------------------------------ */
 
 
   public function canAllocateMoreResources(array $pool) {
     return true;
   }
 
   abstract protected function executeAllocateResource(DrydockLease $lease);
 
 
   final public function allocateResource(DrydockLease $lease) {
     $scope = $this->pushActiveScope(null, $lease);
 
     $this->log(
       pht(
         "Blueprint '%s': Allocating Resource for '%s'",
         $this->getBlueprintClass(),
         $lease->getLeaseName()));
 
     try {
       $resource = $this->executeAllocateResource($lease);
       $this->validateAllocatedResource($resource);
     } catch (Exception $ex) {
       $this->logException($ex);
       throw $ex;
     }
 
     return $resource;
   }
 
 
 /* -(  Logging  )------------------------------------------------------------ */
 
 
   /**
    * @task log
    */
   protected function logException(Exception $ex) {
     $this->log($ex->getMessage());
   }
 
 
   /**
    * @task log
    */
   protected function log($message) {
     self::writeLog(
       $this->activeResource,
       $this->activeLease,
       $message);
   }
 
 
   /**
    * @task log
    */
   public static function writeLog(
     DrydockResource $resource = null,
     DrydockLease $lease = null,
     $message = null) {
 
     $log = id(new DrydockLog())
       ->setEpoch(time())
       ->setMessage($message);
 
     if ($resource) {
       $log->setResourceID($resource->getID());
     }
 
     if ($lease) {
       $log->setLeaseID($lease->getID());
     }
 
     $log->save();
   }
 
 
   public static function getAllBlueprintImplementations() {
     static $list = null;
 
     if ($list === null) {
       $blueprints = id(new PhutilSymbolLoader())
         ->setType('class')
         ->setAncestorClass(__CLASS__)
         ->setConcreteOnly(true)
         ->selectAndLoadSymbols();
       $list = ipull($blueprints, 'name', 'name');
       foreach ($list as $class_name => $ignored) {
         $list[$class_name] = newv($class_name, array());
       }
     }
 
     return $list;
   }
 
   public static function getAllBlueprintImplementationsForResource($type) {
     static $groups = null;
     if ($groups === null) {
       $groups = mgroup(self::getAllBlueprintImplementations(), 'getType');
     }
     return idx($groups, $type, array());
   }
 
   public static function getNamedImplementation($class) {
     return idx(self::getAllBlueprintImplementations(), $class);
   }
 
   protected function newResourceTemplate($name) {
     $resource = id(new DrydockResource())
       ->setBlueprintPHID($this->getInstance()->getPHID())
       ->setBlueprintClass($this->getBlueprintClass())
       ->setType($this->getType())
       ->setStatus(DrydockResourceStatus::STATUS_PENDING)
       ->setName($name)
       ->save();
 
     $this->activeResource = $resource;
 
     $this->log(
       pht(
         "Blueprint '%s': Created New Template",
         $this->getBlueprintClass()));
 
     return $resource;
   }
 
   /**
    * Sanity checks that the blueprint is implemented properly.
    */
   private function validateAllocatedResource($resource) {
     $blueprint = $this->getBlueprintClass();
 
     if (!($resource instanceof DrydockResource)) {
       throw new Exception(
         pht(
           "Blueprint '%s' is not properly implemented: %s must return an ".
           "object of type %s or throw, but returned something else.",
           $blueprint,
           'executeAllocateResource()',
           'DrydockResource'));
     }
 
     $current_status = $resource->getStatus();
     $req_status = DrydockResourceStatus::STATUS_OPEN;
     if ($current_status != $req_status) {
       $current_name = DrydockResourceStatus::getNameForStatus($current_status);
       $req_name = DrydockResourceStatus::getNameForStatus($req_status);
       throw new Exception(
         pht(
           "Blueprint '%s' is not properly implemented: %s must return a %s ".
           "with status '%s', but returned one with status '%s'.",
           $blueprint,
           'executeAllocateResource()',
           'DrydockResource',
           $req_name,
           $current_name));
     }
   }
 
   private function pushActiveScope(
     DrydockResource $resource = null,
     DrydockLease $lease = null) {
 
     if (($this->activeResource !== null) ||
         ($this->activeLease !== null)) {
       throw new Exception(pht('There is already an active resource or lease!'));
     }
 
     $this->activeResource = $resource;
     $this->activeLease = $lease;
 
     return new DrydockBlueprintScopeGuard($this);
   }
 
   public function popActiveScope() {
     $this->activeResource = null;
     $this->activeLease = null;
   }
 
 }
diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
index 54d2b3b0f0..264394f8ac 100644
--- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
@@ -1,118 +1,120 @@
 <?php
 
 final class DrydockWorkingCopyBlueprintImplementation
   extends DrydockBlueprintImplementation {
 
   public function isEnabled() {
     return true;
   }
 
   public function getBlueprintName() {
     return pht('Working Copy');
   }
 
   public function getDescription() {
     return pht('Allows Drydock to check out working copies of repositories.');
   }
 
   protected function canAllocateLease(
     DrydockResource $resource,
     DrydockLease $lease) {
 
     $resource_repo = $resource->getAttribute('repositoryID');
     $lease_repo = $lease->getAttribute('repositoryID');
 
     return ($resource_repo && $lease_repo && ($resource_repo == $lease_repo));
   }
 
   protected function shouldAllocateLease(
     DrydockResource $resource,
     DrydockLease $lease,
     array $other_leases) {
 
     return !$other_leases;
   }
 
   protected function executeAllocateResource(DrydockLease $lease) {
     $repository_id = $lease->getAttribute('repositoryID');
     if (!$repository_id) {
       throw new Exception(
         pht(
           "Lease is missing required '%s' attribute.",
           'repositoryID'));
     }
 
     $repository = id(new PhabricatorRepositoryQuery())
       ->setViewer(PhabricatorUser::getOmnipotentUser())
       ->withIDs(array($repository_id))
       ->executeOne();
 
     if (!$repository) {
       throw new Exception(
         pht(
           "Repository '%s' does not exist!",
           $repository_id));
     }
 
     switch ($repository->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         break;
       default:
         throw new Exception(pht('Unsupported VCS!'));
     }
 
     // TODO: Policy stuff here too.
     $host_lease = id(new DrydockLease())
       ->setResourceType('host')
       ->waitUntilActive();
 
     $path = $host_lease->getAttribute('path').$repository->getCallsign();
 
     $this->log(
       pht('Cloning %s into %s....', $repository->getCallsign(), $path));
 
     $cmd = $host_lease->getInterface('command');
     $cmd->execx(
       'git clone --origin origin %P %s',
       $repository->getRemoteURIEnvelope(),
       $path);
 
     $this->log(pht('Complete.'));
 
     $resource = $this->newResourceTemplate(
-      'Working Copy ('.$repository->getCallsign().')');
+      pht(
+        'Working Copy (%s)',
+        $repository->getCallsign()));
     $resource->setStatus(DrydockResourceStatus::STATUS_OPEN);
     $resource->setAttribute('lease.host', $host_lease->getID());
     $resource->setAttribute('path', $path);
     $resource->setAttribute('repositoryID', $repository->getID());
     $resource->save();
 
     return $resource;
   }
 
   protected function executeAcquireLease(
     DrydockResource $resource,
     DrydockLease $lease) {
     return;
   }
 
   public function getType() {
     return 'working-copy';
   }
 
   public function getInterface(
     DrydockResource $resource,
     DrydockLease $lease,
     $type) {
 
     switch ($type) {
       case 'command':
         return $this
           ->loadLease($resource->getAttribute('lease.host'))
           ->getInterface($type);
     }
 
     throw new Exception(pht("No interface of type '%s'.", $type));
   }
 
 }
diff --git a/src/applications/drydock/view/DrydockLogListView.php b/src/applications/drydock/view/DrydockLogListView.php
index aba883ed65..69c5c5bc3e 100644
--- a/src/applications/drydock/view/DrydockLogListView.php
+++ b/src/applications/drydock/view/DrydockLogListView.php
@@ -1,69 +1,69 @@
 <?php
 
 final class DrydockLogListView extends AphrontView {
 
   private $logs;
 
   public function setLogs(array $logs) {
     assert_instances_of($logs, 'DrydockLog');
     $this->logs = $logs;
     return $this;
   }
 
   public function render() {
     $logs = $this->logs;
     $viewer = $this->getUser();
 
     $view = new PHUIObjectItemListView();
 
     $rows = array();
     foreach ($logs as $log) {
       $resource_uri = '/drydock/resource/'.$log->getResourceID().'/';
       $lease_uri = '/drydock/lease/'.$log->getLeaseID().'/';
 
       $rows[] = array(
         phutil_tag(
           'a',
           array(
             'href' => $resource_uri,
           ),
           $log->getResourceID()),
         phutil_tag(
           'a',
           array(
             'href' => $lease_uri,
           ),
           $log->getLeaseID()),
         $log->getMessage(),
         phabricator_date($log->getEpoch(), $viewer),
       );
     }
 
     $table = new AphrontTableView($rows);
     $table->setDeviceReadyTable(true);
     $table->setHeaders(
       array(
-        'Resource',
-        'Lease',
-        'Message',
-        'Date',
+        pht('Resource'),
+        pht('Lease'),
+        pht('Message'),
+        pht('Date'),
       ));
     $table->setShortHeaders(
       array(
         'R',
         'L',
-        'Message',
+        pht('Message'),
         '',
       ));
     $table->setColumnClasses(
       array(
         '',
         '',
         'wide',
         '',
       ));
 
     return $table;
   }
 
 }
diff --git a/src/applications/fact/controller/PhabricatorFactHomeController.php b/src/applications/fact/controller/PhabricatorFactHomeController.php
index 85f8a084df..ededabc044 100644
--- a/src/applications/fact/controller/PhabricatorFactHomeController.php
+++ b/src/applications/fact/controller/PhabricatorFactHomeController.php
@@ -1,126 +1,126 @@
 <?php
 
 final class PhabricatorFactHomeController extends PhabricatorFactController {
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     if ($request->isFormPost()) {
       $uri = new PhutilURI('/fact/chart/');
       $uri->setQueryParam('y1', $request->getStr('y1'));
       return id(new AphrontRedirectResponse())->setURI($uri);
     }
 
     $types = array(
       '+N:*',
       '+N:DREV',
       'updated',
     );
 
     $engines = PhabricatorFactEngine::loadAllEngines();
     $specs = PhabricatorFactSpec::newSpecsForFactTypes($engines, $types);
 
     $facts = id(new PhabricatorFactAggregate())->loadAllWhere(
       'factType IN (%Ls)',
       $types);
 
     $rows = array();
     foreach ($facts as $fact) {
       $spec = $specs[$fact->getFactType()];
 
       $name = $spec->getName();
       $value = $spec->formatValueForDisplay($user, $fact->getValueX());
 
       $rows[] = array($name, $value);
     }
 
     $table = new AphrontTableView($rows);
     $table->setHeaders(
       array(
         pht('Fact'),
         pht('Value'),
       ));
     $table->setColumnClasses(
       array(
         'wide',
         'n',
       ));
 
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText(pht('Facts'));
     $panel->appendChild($table);
 
     $chart_form = $this->buildChartForm();
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Home'));
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $chart_form,
         $panel,
       ),
       array(
         'title' => pht('Facts'),
       ));
   }
 
   private function buildChartForm() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $table = new PhabricatorFactRaw();
     $conn_r = $table->establishConnection('r');
     $table_name = $table->getTableName();
 
     $facts = queryfx_all(
       $conn_r,
       'SELECT DISTINCT factType from %T',
       $table_name);
 
     $specs = PhabricatorFactSpec::newSpecsForFactTypes(
       PhabricatorFactEngine::loadAllEngines(),
       ipull($facts, 'factType'));
 
     $options = array();
     foreach ($specs as $spec) {
       if ($spec->getUnit() == PhabricatorFactSpec::UNIT_COUNT) {
         $options[$spec->getType()] = $spec->getName();
       }
     }
 
     if (!$options) {
       return id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_NODATA)
         ->setTitle(pht('No Chartable Facts'))
         ->appendChild(phutil_tag(
           'p',
           array(),
           pht('There are no facts that can be plotted yet.')));
     }
 
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->appendChild(
         id(new AphrontFormSelectControl())
-          ->setLabel('Y-Axis')
+          ->setLabel(pht('Y-Axis'))
           ->setName('y1')
           ->setOptions($options))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Plot Chart')));
 
     $panel = new PHUIObjectBoxView();
     $panel->setForm($form);
     $panel->setHeaderText(pht('Plot Chart'));
 
     return $panel;
   }
 
 }
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
index cb6a90d626..e4f94e05d1 100644
--- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
+++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php
@@ -1,101 +1,101 @@
 <?php
 
 final class HarbormasterPlanRunController extends HarbormasterController {
 
   private $id;
 
   public function willProcessRequest(array $data) {
     $this->id = $data['id'];
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $this->requireApplicationCapability(
       HarbormasterManagePlansCapability::CAPABILITY);
 
     $plan_id = $this->id;
     $plan = id(new HarbormasterBuildPlanQuery())
       ->setViewer($viewer)
       ->withIDs(array($plan_id))
       ->executeOne();
     if (!$plan) {
       return new Aphront404Response();
     }
 
     $e_name = true;
     $v_name = null;
 
     $errors = array();
     if ($request->isFormPost()) {
       $buildable = HarbormasterBuildable::initializeNewBuildable($viewer)
         ->setIsManualBuildable(true);
 
       $v_name = $request->getStr('buildablePHID');
 
       if ($v_name) {
         $object = id(new PhabricatorObjectQuery())
           ->setViewer($viewer)
           ->withNames(array($v_name))
           ->executeOne();
 
         if ($object instanceof HarbormasterBuildableInterface) {
           $buildable
             ->setBuildablePHID($object->getHarbormasterBuildablePHID())
             ->setContainerPHID($object->getHarbormasterContainerPHID());
         } else {
           $e_name = pht('Invalid');
           $errors[] = pht('Enter the name of a revision or commit.');
         }
       } else {
         $e_name = pht('Required');
         $errors[] = pht('You must choose a revision or commit to build.');
       }
 
       if (!$errors) {
         $buildable->save();
         $buildable->applyPlan($plan);
 
         $buildable_uri = '/B'.$buildable->getID();
         return id(new AphrontRedirectResponse())->setURI($buildable_uri);
       }
     }
 
     if ($errors) {
       $errors = id(new PHUIInfoView())->setErrors($errors);
     }
 
     $title = pht('Run Build Plan Manually');
     $cancel_uri = $this->getApplicationURI("plan/{$plan_id}/");
     $save_button = pht('Run Plan Manually');
 
     $form = id(new PHUIFormLayoutView())
       ->setUser($viewer)
       ->appendRemarkupInstructions(
         pht(
           "Enter the name of a commit or revision to run this plan on (for ".
           "example, `rX123456` or `D123`).\n\n".
           "For more detailed output, you can also run manual builds from ".
           "the command line:\n\n".
           "  phabricator/ $ ./bin/harbormaster build <object> --plan %s",
           $plan->getID()))
       ->appendChild(
         id(new AphrontFormTextControl())
-          ->setLabel('Buildable Name')
+          ->setLabel(pht('Buildable Name'))
           ->setName('buildablePHID')
           ->setError($e_name)
           ->setValue($v_name));
 
     $dialog = id(new AphrontDialogView())
       ->setWidth(AphrontDialogView::WIDTH_FULL)
       ->setUser($viewer)
       ->setTitle($title)
       ->appendChild($form)
       ->addCancelButton($cancel_uri)
       ->addSubmitButton($save_button);
 
     return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
 }
diff --git a/src/applications/herald/storage/transcript/HeraldTranscript.php b/src/applications/herald/storage/transcript/HeraldTranscript.php
index 749bbf633a..8b458c93ff 100644
--- a/src/applications/herald/storage/transcript/HeraldTranscript.php
+++ b/src/applications/herald/storage/transcript/HeraldTranscript.php
@@ -1,234 +1,234 @@
 <?php
 
 final class HeraldTranscript extends HeraldDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorDestructibleInterface {
 
   protected $objectTranscript;
   protected $ruleTranscripts = array();
   protected $conditionTranscripts = array();
   protected $applyTranscripts = array();
 
   protected $time;
   protected $host;
   protected $duration;
 
   protected $objectPHID;
   protected $dryRun;
   protected $garbageCollected = 0;
 
   const TABLE_SAVED_HEADER = 'herald_savedheader';
 
   public function getXHeraldRulesHeader() {
     $ids = array();
     foreach ($this->applyTranscripts as $xscript) {
       if ($xscript->getApplied()) {
         if ($xscript->getRuleID()) {
           $ids[] = $xscript->getRuleID();
         }
       }
     }
     if (!$ids) {
       return 'none';
     }
 
     // A rule may have multiple effects, which will cause it to be listed
     // multiple times.
     $ids = array_unique($ids);
 
     foreach ($ids as $k => $id) {
       $ids[$k] = '<'.$id.'>';
     }
 
     return implode(', ', $ids);
   }
 
   public static function saveXHeraldRulesHeader($phid, $header) {
 
     // Combine any existing header with the new header, listing all rules
     // which have ever triggered for this object.
     $header = self::combineXHeraldRulesHeaders(
       self::loadXHeraldRulesHeader($phid),
       $header);
 
     queryfx(
       id(new HeraldTranscript())->establishConnection('w'),
       'INSERT INTO %T (phid, header) VALUES (%s, %s)
         ON DUPLICATE KEY UPDATE header = VALUES(header)',
       self::TABLE_SAVED_HEADER,
       $phid,
       $header);
 
     return $header;
   }
 
   private static function combineXHeraldRulesHeaders($u, $v) {
     $u = preg_split('/[, ]+/', $u);
     $v = preg_split('/[, ]+/', $v);
 
     $combined = array_unique(array_filter(array_merge($u, $v)));
     return implode(', ', $combined);
   }
 
   public static function loadXHeraldRulesHeader($phid) {
     $header = queryfx_one(
       id(new HeraldTranscript())->establishConnection('r'),
       'SELECT * FROM %T WHERE phid = %s',
       self::TABLE_SAVED_HEADER,
       $phid);
     if ($header) {
       return idx($header, 'header');
     }
     return null;
   }
 
 
   protected function getConfiguration() {
     // Ugh. Too much of a mess to deal with.
     return array(
       self::CONFIG_AUX_PHID     => true,
       self::CONFIG_TIMESTAMPS   => false,
       self::CONFIG_SERIALIZATION => array(
         'objectTranscript'      => self::SERIALIZATION_PHP,
         'ruleTranscripts'       => self::SERIALIZATION_PHP,
         'conditionTranscripts'  => self::SERIALIZATION_PHP,
         'applyTranscripts'      => self::SERIALIZATION_PHP,
       ),
       self::CONFIG_BINARY => array(
         'objectTranscript'      => true,
         'ruleTranscripts'       => true,
         'conditionTranscripts'  => true,
         'applyTranscripts'      => true,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'time' => 'epoch',
         'host' => 'text255',
         'duration' => 'double',
         'dryRun' => 'bool',
         'garbageCollected' => 'bool',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_phid' => null,
         'phid' => array(
           'columns' => array('phid'),
           'unique' => true,
         ),
         'objectPHID' => array(
           'columns' => array('objectPHID'),
         ),
         'garbageCollected' => array(
           'columns' => array('garbageCollected', 'time'),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function __construct() {
     $this->time = time();
     $this->host = php_uname('n');
   }
 
   public function addApplyTranscript(HeraldApplyTranscript $transcript) {
     $this->applyTranscripts[] = $transcript;
     return $this;
   }
 
   public function getApplyTranscripts() {
     return nonempty($this->applyTranscripts, array());
   }
 
   public function setDuration($duration) {
     $this->duration = $duration;
     return $this;
   }
 
   public function setObjectTranscript(HeraldObjectTranscript $transcript) {
     $this->objectTranscript = $transcript;
     return $this;
   }
 
   public function getObjectTranscript() {
     return $this->objectTranscript;
   }
 
   public function addRuleTranscript(HeraldRuleTranscript $transcript) {
     $this->ruleTranscripts[$transcript->getRuleID()] = $transcript;
     return $this;
   }
 
   public function discardDetails() {
     $this->applyTranscripts = null;
     $this->ruleTranscripts = null;
     $this->objectTranscript = null;
     $this->conditionTranscripts = null;
   }
 
   public function getRuleTranscripts() {
     return nonempty($this->ruleTranscripts, array());
   }
 
   public function addConditionTranscript(
     HeraldConditionTranscript $transcript) {
     $rule_id = $transcript->getRuleID();
     $cond_id = $transcript->getConditionID();
 
     $this->conditionTranscripts[$rule_id][$cond_id] = $transcript;
     return $this;
   }
 
   public function getConditionTranscriptsForRule($rule_id) {
     return idx($this->conditionTranscripts, $rule_id, array());
   }
 
   public function getMetadataMap() {
     return array(
-      'Run At Epoch' => date('F jS, g:i:s A', $this->time),
-      'Run On Host'  => $this->host,
-      'Run Duration' => (int)(1000 * $this->duration).' ms',
+      pht('Run At Epoch') => date('F jS, g:i:s A', $this->time),
+      pht('Run On Host')  => $this->host,
+      pht('Run Duration') => (int)(1000 * $this->duration).' ms',
     );
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID('HLXS');
   }
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return PhabricatorPolicies::POLICY_USER;
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return pht(
       'To view a transcript, you must be able to view the object the '.
       'transcript is about.');
   }
 
 
 /* -(  PhabricatorDestructibleInterface  )----------------------------------- */
 
 
   public function destroyObjectPermanently(
     PhabricatorDestructionEngine $engine) {
 
     $this->openTransaction();
       $this->delete();
     $this->saveTransaction();
   }
 
 
 }
diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
index a52b84dcbb..c669e2f858 100644
--- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
+++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
@@ -1,327 +1,327 @@
 <?php
 
 final class PhabricatorManiphestConfigOptions
   extends PhabricatorApplicationConfigOptions {
 
   public function getName() {
     return pht('Maniphest');
   }
 
   public function getDescription() {
     return pht('Configure Maniphest.');
   }
 
   public function getFontIcon() {
     return 'fa-anchor';
   }
 
   public function getGroup() {
     return 'apps';
   }
 
   public function getOptions() {
 
     $priority_defaults = array(
       100 => array(
         'name'  => pht('Unbreak Now!'),
         'short' => pht('Unbreak!'),
         'color' => 'indigo',
         'keywords' => array('unbreak'),
       ),
       90 => array(
         'name' => pht('Needs Triage'),
         'short' => pht('Triage'),
         'color' => 'violet',
         'keywords' => array('triage'),
       ),
       80 => array(
         'name' => pht('High'),
         'short' => pht('High'),
         'color' => 'red',
         'keywords' => array('high'),
       ),
       50 => array(
         'name' => pht('Normal'),
         'short' => pht('Normal'),
         'color' => 'orange',
         'keywords' => array('normal'),
       ),
       25 => array(
         'name' => pht('Low'),
         'short' => pht('Low'),
         'color' => 'yellow',
         'keywords' => array('low'),
       ),
       0 => array(
         'name' => pht('Wishlist'),
         'short' => pht('Wish'),
         'color' => 'sky',
         'keywords' => array('wish', 'wishlist'),
       ),
     );
 
     $status_type = 'custom:ManiphestStatusConfigOptionType';
     $status_defaults = array(
       'open' => array(
         'name' => pht('Open'),
         'special' => ManiphestTaskStatus::SPECIAL_DEFAULT,
         'prefixes' => array(
           'open',
           'opens',
           'reopen',
           'reopens',
         ),
       ),
       'resolved' => array(
         'name' => pht('Resolved'),
         'name.full' => pht('Closed, Resolved'),
         'closed' => true,
         'special' => ManiphestTaskStatus::SPECIAL_CLOSED,
         'prefixes' => array(
           'closed',
           'closes',
           'close',
           'fix',
           'fixes',
           'fixed',
           'resolve',
           'resolves',
           'resolved',
         ),
         'suffixes' => array(
           'as resolved',
           'as fixed',
         ),
         'keywords' => array('closed', 'fixed', 'resolved'),
       ),
       'wontfix' => array(
         'name' => pht('Wontfix'),
         'name.full' => pht('Closed, Wontfix'),
         'closed' => true,
         'prefixes' => array(
           'wontfix',
           'wontfixes',
           'wontfixed',
         ),
         'suffixes' => array(
           'as wontfix',
         ),
       ),
       'invalid' => array(
         'name' => pht('Invalid'),
         'name.full' => pht('Closed, Invalid'),
         'closed' => true,
         'prefixes' => array(
           'invalidate',
           'invalidates',
           'invalidated',
         ),
         'suffixes' => array(
           'as invalid',
         ),
       ),
       'duplicate' => array(
         'name' => pht('Duplicate'),
         'name.full' => pht('Closed, Duplicate'),
         'transaction.icon' => 'fa-files-o',
         'special' => ManiphestTaskStatus::SPECIAL_DUPLICATE,
         'closed' => true,
       ),
       'spite' => array(
         'name' => pht('Spite'),
         'name.full' => pht('Closed, Spite'),
         'name.action' => pht('Spited'),
         'transaction.icon' => 'fa-thumbs-o-down',
         'silly' => true,
         'closed' => true,
         'prefixes' => array(
           'spite',
           'spites',
           'spited',
         ),
         'suffixes' => array(
           'out of spite',
           'as spite',
         ),
       ),
     );
 
     $status_description = $this->deformat(pht(<<<EOTEXT
 Allows you to edit, add, or remove the task statuses available in Maniphest,
 like "Open", "Resolved" and "Invalid". The configuration should contain a map
 of status constants to status specifications (see defaults below for examples).
 
 The constant for each status should be 1-12 characters long and  contain only
 lowercase letters and digits. Valid examples are "open", "closed", and
 "invalid". Users will not normally see these values.
 
 The keys you can provide in a specification are:
 
   - `name` //Required string.// Name of the status, like "Invalid".
   - `name.full` //Optional string.// Longer name, like "Closed, Invalid". This
     appears on the task detail view in the header.
   - `name.action` //Optional string.// Action name for email subjects, like
     "Marked Invalid".
   - `closed` //Optional bool.// Statuses are either "open" or "closed".
     Specifying `true` here will mark the status as closed (like "Resolved" or
     "Invalid"). By default, statuses are open.
   - `special` //Optional string.// Mark this status as special. The special
     statuses are:
     - `default` This is the default status for newly created tasks. You must
       designate one status as default, and it must be an open status.
     - `closed` This is the default status for closed tasks (for example, tasks
       closed via the "!close" action in email or via the quick close button in
       Maniphest). You must designate one status as the default closed status,
       and it must be a closed status.
     - `duplicate` This is the status used when tasks are merged into one
       another as duplicates. You must designate one status for duplicates,
       and it must be a closed status.
   - `transaction.icon` //Optional string.// Allows you to choose a different
     icon to use for this status when showing status changes in the transaction
     log. Please see UIExamples, Icons and Images for a list.
   - `transaction.color` //Optional string.// Allows you to choose a different
     color to use for this status when showing status changes in the transaction
     log.
   - `silly` //Optional bool.// Marks this status as silly, and thus wholly
     inappropriate for use by serious businesses.
   - `prefixes` //Optional list<string>.// Allows you to specify a list of
     text prefixes which will trigger a task transition into this status
     when mentioned in a commit message. For example, providing "closes" here
     will allow users to move tasks to this status by writing `Closes T123` in
     commit messages.
   - `suffixes` //Optional list<string>.// Allows you to specify a list of
     text suffixes which will trigger a task transition into this status
     when mentioned in a commit message, after a valid prefix. For example,
     providing "as invalid" here will allow users to move tasks
     to this status by writing `Closes T123 as invalid`, even if another status
     is selected by the "Closes" prefix.
   - `keywords` //Optional list<string>.// Allows you to specify a list
     of keywords which can be used with `!status` commands in email to select
     this status.
 
 Statuses will appear in the UI in the order specified. Note the status marked
 `special` as `duplicate` is not settable directly and will not appear in UI
 elements, and that any status marked `silly` does not appear if Phabricator
 is configured with `phabricator.serious-business` set to true.
 
 Examining the default configuration and examples below will probably be helpful
 in understanding these options.
 
 EOTEXT
 ));
 
     $status_example = array(
       'open' => array(
-        'name' => 'Open',
+        'name' => pht('Open'),
         'special' => 'default',
       ),
       'closed' => array(
-        'name' => 'Closed',
+        'name' => pht('Closed'),
         'special' => 'closed',
         'closed' => true,
       ),
       'duplicate' => array(
-        'name' => 'Duplicate',
+        'name' => pht('Duplicate'),
         'special' => 'duplicate',
         'closed' => true,
       ),
     );
 
     $json = new PhutilJSON();
     $status_example = $json->encodeFormatted($status_example);
 
     // This is intentionally blank for now, until we can move more Maniphest
     // logic to custom fields.
     $default_fields = array();
 
     foreach ($default_fields as $key => $enabled) {
       $default_fields[$key] = array(
         'disabled' => !$enabled,
       );
     }
 
     $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType';
 
     return array(
       $this->newOption('maniphest.custom-field-definitions', 'wild', array())
         ->setSummary(pht('Custom Maniphest fields.'))
         ->setDescription(
           pht(
             'Array of custom fields for Maniphest tasks. For details on '.
             'adding custom fields to Maniphest, see "Configuring Custom '.
             'Fields" in the documentation.'))
         ->addExample(
           '{"mycompany:estimated-hours": {"name": "Estimated Hours", '.
           '"type": "int", "caption": "Estimated number of hours this will '.
           'take."}}',
           pht('Valid Setting')),
       $this->newOption('maniphest.fields', $custom_field_type, $default_fields)
         ->setCustomData(id(new ManiphestTask())->getCustomFieldBaseClass())
         ->setDescription(pht('Select and reorder task fields.')),
       $this->newOption('maniphest.priorities', 'wild', $priority_defaults)
         ->setSummary(pht('Configure Maniphest priority names.'))
         ->setDescription(
           pht(
             'Allows you to edit or override the default priorities available '.
             'in Maniphest, like "High", "Normal" and "Low". The configuration '.
             'should contain a map of priority constants to priority '.
             'specifications (see defaults below for examples).'.
             "\n\n".
             'The keys you can define for a priority are:'.
             "\n\n".
             '  - `name` Name of the priority.'."\n".
             '  - `short` Alternate shorter name, used in UIs where there is '.
             '    not much space available.'."\n".
             '  - `color` A color for this priority, like "red" or "blue".'.
             '  - `keywords` An optional list of keywords which can '.
             '     be used to select this priority when using `!priority` '.
             '     commands in email.'.
             "\n\n".
             'You can choose which priority is the default for newly created '.
             'tasks with `%s`.',
             'maniphest.default-priority')),
       $this->newOption('maniphest.statuses', $status_type, $status_defaults)
         ->setSummary(pht('Configure Maniphest task statuses.'))
         ->setDescription($status_description)
         ->addExample($status_example, pht('Minimal Valid Config')),
       $this->newOption('maniphest.default-priority', 'int', 90)
         ->setSummary(pht('Default task priority for create flows.'))
         ->setDescription(
           pht(
             'Choose a default priority for newly created tasks. You can '.
             'review and adjust available priorities by using the '.
             '%s configuration option. The default value (`90`) '.
             'corresponds to the default "Needs Triage" priority.',
             'maniphest.priorities')),
       $this->newOption(
         'metamta.maniphest.subject-prefix',
         'string',
         '[Maniphest]')
         ->setDescription(pht('Subject prefix for Maniphest mail.')),
       $this->newOption(
         'maniphest.priorities.unbreak-now',
         'int',
         100)
         ->setSummary(pht('Priority used to populate "Unbreak Now" on home.'))
         ->setDescription(
           pht(
             'Temporary setting. If set, this priority is used to populate the '.
             '"Unbreak Now" panel on the home page. You should adjust this if '.
             'you adjust priorities using `%s`.',
             'maniphest.priorities')),
       $this->newOption(
         'maniphest.priorities.needs-triage',
         'int',
         90)
         ->setSummary(pht('Priority used to populate "Needs Triage" on home.'))
         ->setDescription(
           pht(
             'Temporary setting. If set, this priority is used to populate the '.
             '"Needs Triage" panel on the home page. You should adjust this if '.
             'you adjust priorities using `%s`.',
             'maniphest.priorities')),
 
     );
   }
 
 }
diff --git a/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php b/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php
index 6548e5700f..20f4c438a7 100644
--- a/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php
+++ b/src/applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php
@@ -1,114 +1,114 @@
 <?php
 
 final class ManiphestTaskStatusTestCase extends PhabricatorTestCase {
 
   public function testManiphestStatusConstants() {
     $map = array(
       'y' => true,
       'closed' => true,
       'longlonglong' => true,
       'duplicate2' => true,
 
       '' => false,
       'longlonglonglong' => false,
       '.' => false,
       'ABCD' => false,
       'a b c ' => false,
     );
 
     foreach ($map as $input => $expect) {
       $this->assertEqual(
         $expect,
         ManiphestTaskStatus::isValidStatusConstant($input),
         pht('Validate "%s"', $input));
     }
   }
 
   public function testManiphestStatusConfigValidation() {
     $this->assertConfigValid(
       false,
       pht('Empty'),
       array());
 
     // This is a minimal, valid configuration.
 
     $valid = array(
       'open' => array(
-        'name' => 'Open',
+        'name' => pht('Open'),
         'special' => 'default',
       ),
       'closed' => array(
-        'name' => 'Closed',
+        'name' => pht('Closed'),
         'special' => 'closed',
         'closed' => true,
       ),
       'duplicate' => array(
-        'name' => 'Duplicate',
+        'name' => pht('Duplicate'),
         'special' => 'duplicate',
         'closed' => true,
       ),
     );
     $this->assertConfigValid(true, pht('Minimal Valid Config'), $valid);
 
     // We should raise on a bad key.
     $bad_key = $valid;
-    $bad_key['!'] = array('name' => 'Exclaim');
+    $bad_key['!'] = array('name' => pht('Exclaim'));
     $this->assertConfigValid(false, pht('Bad Key'), $bad_key);
 
     // We should raise on a value type.
     $bad_type = $valid;
     $bad_type['other'] = 'other';
     $this->assertConfigValid(false, pht('Bad Value Type'), $bad_type);
 
     // We should raise on an unknown configuration key.
     $invalid_key = $valid;
     $invalid_key['open']['imaginary'] = 'unicorn';
     $this->assertConfigValid(false, pht('Invalid Key'), $invalid_key);
 
     // We should raise on two statuses with the same special.
     $double_close = $valid;
     $double_close['finished'] = array(
-      'name' => 'Finished',
+      'name' => pht('Finished'),
       'special' => 'closed',
       'closed' => true,
     );
     $this->assertConfigValid(false, pht('Duplicate Special'), $double_close);
 
     // We should raise if any of the special statuses are missing.
     foreach ($valid as $key => $config) {
       $missing = $valid;
       unset($missing[$key]);
       $this->assertConfigValid(false, pht('Missing Special'), $missing);
     }
 
     // The "default" special should be an open status.
     $closed_default = $valid;
     $closed_default['open']['closed'] = true;
     $this->assertConfigValid(false, pht('Closed Default'), $closed_default);
 
     // The "closed" special should be a closed status.
     $open_closed = $valid;
     $open_closed['closed']['closed'] = false;
     $this->assertConfigValid(false, pht('Open Closed'), $open_closed);
 
     // The "duplicate" special should be a closed status.
     $open_duplicate = $valid;
     $open_duplicate['duplicate']['closed'] = false;
     $this->assertConfigValid(false, pht('Open Duplicate'), $open_duplicate);
   }
 
   private function assertConfigValid($expect, $name, array $config) {
     $caught = null;
     try {
       ManiphestTaskStatus::validateConfiguration($config);
     } catch (Exception $ex) {
       $caught = $ex;
     }
 
     $this->assertEqual(
       $expect,
       !($caught instanceof Exception),
       pht('Validation of "%s"', $name));
   }
 
 }
diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php
index b03caed542..aa9c435691 100644
--- a/src/applications/maniphest/controller/ManiphestReportController.php
+++ b/src/applications/maniphest/controller/ManiphestReportController.php
@@ -1,795 +1,795 @@
 <?php
 
 final class ManiphestReportController extends ManiphestController {
 
   private $view;
 
   public function willProcessRequest(array $data) {
     $this->view = idx($data, 'view');
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     if ($request->isFormPost()) {
       $uri = $request->getRequestURI();
 
       $project = head($request->getArr('set_project'));
       $project = nonempty($project, null);
       $uri = $uri->alter('project', $project);
 
       $window = $request->getStr('set_window');
       $uri = $uri->alter('window', $window);
 
       return id(new AphrontRedirectResponse())->setURI($uri);
     }
 
     $nav = new AphrontSideNavFilterView();
     $nav->setBaseURI(new PhutilURI('/maniphest/report/'));
     $nav->addLabel(pht('Open Tasks'));
     $nav->addFilter('user', pht('By User'));
     $nav->addFilter('project', pht('By Project'));
     $nav->addLabel(pht('Burnup'));
     $nav->addFilter('burn', pht('Burnup Rate'));
 
     $this->view = $nav->selectFilter($this->view, 'user');
 
     require_celerity_resource('maniphest-report-css');
 
     switch ($this->view) {
       case 'burn':
         $core = $this->renderBurn();
         break;
       case 'user':
       case 'project':
         $core = $this->renderOpenTasks();
         break;
       default:
         return new Aphront404Response();
     }
 
     $nav->appendChild($core);
     $nav->setCrumbs(
       $this->buildApplicationCrumbs()
         ->setBorder(true)
         ->addTextCrumb(pht('Reports')));
 
     return $this->buildApplicationPage(
       $nav,
       array(
         'title' => pht('Maniphest Reports'),
         'device' => false,
       ));
   }
 
   public function renderBurn() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $handle = null;
 
     $project_phid = $request->getStr('project');
     if ($project_phid) {
       $phids = array($project_phid);
       $handles = $this->loadViewerHandles($phids);
       $handle = $handles[$project_phid];
     }
 
     $table = new ManiphestTransaction();
     $conn = $table->establishConnection('r');
 
     $joins = '';
     if ($project_phid) {
       $joins = qsprintf(
         $conn,
         'JOIN %T t ON x.objectPHID = t.phid
           JOIN %T p ON p.src = t.phid AND p.type = %d AND p.dst = %s',
         id(new ManiphestTask())->getTableName(),
         PhabricatorEdgeConfig::TABLE_NAME_EDGE,
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
         $project_phid);
     }
 
     $data = queryfx_all(
       $conn,
       'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q
         WHERE transactionType = %s
         ORDER BY x.dateCreated ASC',
       $table->getTableName(),
       $joins,
       ManiphestTransaction::TYPE_STATUS);
 
     $stats = array();
     $day_buckets = array();
 
     $open_tasks = array();
 
     foreach ($data as $key => $row) {
 
       // NOTE: Hack to avoid json_decode().
       $oldv = trim($row['oldValue'], '"');
       $newv = trim($row['newValue'], '"');
 
       if ($oldv == 'null') {
         $old_is_open = false;
       } else {
         $old_is_open = ManiphestTaskStatus::isOpenStatus($oldv);
       }
 
       $new_is_open = ManiphestTaskStatus::isOpenStatus($newv);
 
       $is_open  = ($new_is_open && !$old_is_open);
       $is_close = ($old_is_open && !$new_is_open);
 
       $data[$key]['_is_open'] = $is_open;
       $data[$key]['_is_close'] = $is_close;
 
       if (!$is_open && !$is_close) {
         // This is either some kind of bogus event, or a resolution change
         // (e.g., resolved -> invalid). Just skip it.
         continue;
       }
 
       $day_bucket = phabricator_format_local_time(
         $row['dateCreated'],
         $user,
         'Yz');
       $day_buckets[$day_bucket] = $row['dateCreated'];
       if (empty($stats[$day_bucket])) {
         $stats[$day_bucket] = array(
           'open'  => 0,
           'close' => 0,
         );
       }
       $stats[$day_bucket][$is_close ? 'close' : 'open']++;
     }
 
     $template = array(
       'open'  => 0,
       'close' => 0,
     );
 
     $rows = array();
     $rowc = array();
     $last_month = null;
     $last_month_epoch = null;
     $last_week = null;
     $last_week_epoch = null;
     $week = null;
     $month = null;
 
     $last = last_key($stats) - 1;
     $period = $template;
 
     foreach ($stats as $bucket => $info) {
       $epoch = $day_buckets[$bucket];
 
       $week_bucket = phabricator_format_local_time(
         $epoch,
         $user,
         'YW');
       if ($week_bucket != $last_week) {
         if ($week) {
           $rows[] = $this->formatBurnRow(
             pht('Week of %s', phabricator_date($last_week_epoch, $user)),
             $week);
           $rowc[] = 'week';
         }
         $week = $template;
         $last_week = $week_bucket;
         $last_week_epoch = $epoch;
       }
 
       $month_bucket = phabricator_format_local_time(
         $epoch,
         $user,
         'Ym');
       if ($month_bucket != $last_month) {
         if ($month) {
           $rows[] = $this->formatBurnRow(
             phabricator_format_local_time($last_month_epoch, $user, 'F, Y'),
             $month);
           $rowc[] = 'month';
         }
         $month = $template;
         $last_month = $month_bucket;
         $last_month_epoch = $epoch;
       }
 
       $rows[] = $this->formatBurnRow(phabricator_date($epoch, $user), $info);
       $rowc[] = null;
       $week['open'] += $info['open'];
       $week['close'] += $info['close'];
       $month['open'] += $info['open'];
       $month['close'] += $info['close'];
       $period['open'] += $info['open'];
       $period['close'] += $info['close'];
     }
 
     if ($week) {
       $rows[] = $this->formatBurnRow(
         pht('Week To Date'),
         $week);
       $rowc[] = 'week';
     }
 
     if ($month) {
       $rows[] = $this->formatBurnRow(
         pht('Month To Date'),
         $month);
       $rowc[] = 'month';
     }
 
     $rows[] = $this->formatBurnRow(
       pht('All Time'),
       $period);
     $rowc[] = 'aggregate';
 
     $rows = array_reverse($rows);
     $rowc = array_reverse($rowc);
 
     $table = new AphrontTableView($rows);
     $table->setRowClasses($rowc);
     $table->setHeaders(
       array(
         pht('Period'),
         pht('Opened'),
         pht('Closed'),
         pht('Change'),
       ));
     $table->setColumnClasses(
       array(
         'right wide',
         'n',
         'n',
         'n',
       ));
 
     if ($handle) {
       $inst = pht(
         'NOTE: This table reflects tasks currently in '.
         'the project. If a task was opened in the past but added to '.
         'the project recently, it is counted on the day it was '.
         'opened, not the day it was categorized. If a task was part '.
         'of this project in the past but no longer is, it is not '.
         'counted at all.');
       $header = pht('Task Burn Rate for Project %s', $handle->renderLink());
       $caption = phutil_tag('p', array(), $inst);
     } else {
       $header = pht('Task Burn Rate for All Tasks');
       $caption = null;
     }
 
     if ($caption) {
       $caption = id(new PHUIInfoView())
         ->appendChild($caption)
         ->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
     }
 
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText($header);
     if ($caption) {
       $panel->setInfoView($caption);
     }
     $panel->appendChild($table);
 
     $tokens = array();
     if ($handle) {
       $tokens = array($handle);
     }
 
     $filter = $this->renderReportFilters($tokens, $has_window = false);
 
     $id = celerity_generate_unique_node_id();
     $chart = phutil_tag(
       'div',
       array(
         'id' => $id,
         'style' => 'border: 1px solid #BFCFDA; '.
                    'background-color: #fff; '.
                    'margin: 8px 16px; '.
                    'height: 400px; ',
       ),
       '');
 
     list($burn_x, $burn_y) = $this->buildSeries($data);
 
     require_celerity_resource('raphael-core');
     require_celerity_resource('raphael-g');
     require_celerity_resource('raphael-g-line');
 
     Javelin::initBehavior('line-chart', array(
       'hardpoint' => $id,
       'x' => array(
         $burn_x,
       ),
       'y' => array(
         $burn_y,
       ),
       'xformat' => 'epoch',
       'yformat' => 'int',
     ));
 
     return array($filter, $chart, $panel);
   }
 
   private function renderReportFilters(array $tokens, $has_window) {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setDatasource(new PhabricatorProjectDatasource())
           ->setLabel(pht('Project'))
           ->setLimit(1)
           ->setName('set_project')
           // TODO: This is silly, but this is Maniphest reports.
           ->setValue(mpull($tokens, 'getPHID')));
 
     if ($has_window) {
       list($window_str, $ignored, $window_error) = $this->getWindow();
       $form
         ->appendChild(
           id(new AphrontFormTextControl())
             ->setLabel(pht('Recently Means'))
             ->setName('set_window')
             ->setCaption(
               pht('Configure the cutoff for the "Recently Closed" column.'))
             ->setValue($window_str)
             ->setError($window_error));
     }
 
     $form
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Filter By Project')));
 
     $filter = new AphrontListFilterView();
     $filter->appendChild($form);
 
     return $filter;
   }
 
   private function buildSeries(array $data) {
     $out = array();
 
     $counter = 0;
     foreach ($data as $row) {
       $t = (int)$row['dateCreated'];
       if ($row['_is_close']) {
         --$counter;
         $out[$t] = $counter;
       } else if ($row['_is_open']) {
         ++$counter;
         $out[$t] = $counter;
       }
     }
 
     return array(array_keys($out), array_values($out));
   }
 
   private function formatBurnRow($label, $info) {
     $delta = $info['open'] - $info['close'];
     $fmt = number_format($delta);
     if ($delta > 0) {
       $fmt = '+'.$fmt;
       $fmt = phutil_tag('span', array('class' => 'red'), $fmt);
     } else {
       $fmt = phutil_tag('span', array('class' => 'green'), $fmt);
     }
 
     return array(
       $label,
       number_format($info['open']),
       number_format($info['close']),
       $fmt,
     );
   }
 
   public function renderOpenTasks() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
 
     $query = id(new ManiphestTaskQuery())
       ->setViewer($user)
       ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants());
 
     switch ($this->view) {
       case 'project':
         $query->needProjectPHIDs(true);
         break;
     }
 
     $project_phid = $request->getStr('project');
     $project_handle = null;
     if ($project_phid) {
       $phids = array($project_phid);
       $handles = $this->loadViewerHandles($phids);
       $project_handle = $handles[$project_phid];
 
       $query->withEdgeLogicPHIDs(
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
         PhabricatorQueryConstraint::OPERATOR_OR,
         $phids);
     }
 
     $tasks = $query->execute();
 
     $recently_closed = $this->loadRecentlyClosedTasks();
 
     $date = phabricator_date(time(), $user);
 
     switch ($this->view) {
       case 'user':
         $result = mgroup($tasks, 'getOwnerPHID');
         $leftover = idx($result, '', array());
         unset($result['']);
 
         $result_closed = mgroup($recently_closed, 'getOwnerPHID');
         $leftover_closed = idx($result_closed, '', array());
         unset($result_closed['']);
 
         $base_link = '/maniphest/?assigned=';
         $leftover_name = phutil_tag('em', array(), pht('(Up For Grabs)'));
         $col_header = pht('User');
         $header = pht('Open Tasks by User and Priority (%s)', $date);
         break;
       case 'project':
         $result = array();
         $leftover = array();
         foreach ($tasks as $task) {
           $phids = $task->getProjectPHIDs();
           if ($phids) {
             foreach ($phids as $project_phid) {
               $result[$project_phid][] = $task;
             }
           } else {
             $leftover[] = $task;
           }
         }
 
         $result_closed = array();
         $leftover_closed = array();
         foreach ($recently_closed as $task) {
           $phids = $task->getProjectPHIDs();
           if ($phids) {
             foreach ($phids as $project_phid) {
               $result_closed[$project_phid][] = $task;
             }
           } else {
             $leftover_closed[] = $task;
           }
         }
 
         $base_link = '/maniphest/?projects=';
         $leftover_name = phutil_tag('em', array(), pht('(No Project)'));
         $col_header = pht('Project');
         $header = pht('Open Tasks by Project and Priority (%s)', $date);
         break;
     }
 
     $phids = array_keys($result);
     $handles = $this->loadViewerHandles($phids);
     $handles = msort($handles, 'getName');
 
     $order = $request->getStr('order', 'name');
     list($order, $reverse) = AphrontTableView::parseSort($order);
 
     require_celerity_resource('aphront-tooltip-css');
     Javelin::initBehavior('phabricator-tooltips', array());
 
     $rows = array();
     $pri_total = array();
     foreach (array_merge($handles, array(null)) as $handle) {
       if ($handle) {
         if (($project_handle) &&
             ($project_handle->getPHID() == $handle->getPHID())) {
           // If filtering by, e.g., "bugs", don't show a "bugs" group.
           continue;
         }
 
         $tasks = idx($result, $handle->getPHID(), array());
         $name = phutil_tag(
           'a',
           array(
             'href' => $base_link.$handle->getPHID(),
           ),
           $handle->getName());
         $closed = idx($result_closed, $handle->getPHID(), array());
       } else {
         $tasks = $leftover;
         $name  = $leftover_name;
         $closed = $leftover_closed;
       }
 
       $taskv = $tasks;
       $tasks = mgroup($tasks, 'getPriority');
 
       $row = array();
       $row[] = $name;
       $total = 0;
       foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $label) {
         $n = count(idx($tasks, $pri, array()));
         if ($n == 0) {
           $row[] = '-';
         } else {
           $row[] = number_format($n);
         }
         $total += $n;
       }
       $row[] = number_format($total);
 
       list($link, $oldest_all) = $this->renderOldest($taskv);
       $row[] = $link;
 
       $normal_or_better = array();
       foreach ($taskv as $id => $task) {
         // TODO: This is sort of a hard-code for the default "normal" status.
         // When reports are more powerful, this should be made more general.
         if ($task->getPriority() < 50) {
           continue;
         }
         $normal_or_better[$id] = $task;
       }
 
       list($link, $oldest_pri) = $this->renderOldest($normal_or_better);
       $row[] = $link;
 
       if ($closed) {
         $task_ids = implode(',', mpull($closed, 'getID'));
         $row[] = phutil_tag(
           'a',
           array(
             'href' => '/maniphest/?ids='.$task_ids,
             'target' => '_blank',
           ),
           number_format(count($closed)));
       } else {
         $row[] = '-';
       }
 
       switch ($order) {
         case 'total':
           $row['sort'] = $total;
           break;
         case 'oldest-all':
           $row['sort'] = $oldest_all;
           break;
         case 'oldest-pri':
           $row['sort'] = $oldest_pri;
           break;
         case 'closed':
           $row['sort'] = count($closed);
           break;
         case 'name':
         default:
           $row['sort'] = $handle ? $handle->getName() : '~';
           break;
       }
 
       $rows[] = $row;
     }
 
     $rows = isort($rows, 'sort');
     foreach ($rows as $k => $row) {
       unset($rows[$k]['sort']);
     }
     if ($reverse) {
       $rows = array_reverse($rows);
     }
 
     $cname = array($col_header);
     $cclass = array('pri right wide');
     $pri_map = ManiphestTaskPriority::getShortNameMap();
     foreach ($pri_map as $pri => $label) {
       $cname[] = $label;
       $cclass[] = 'n';
     }
-    $cname[] = 'Total';
+    $cname[] = pht('Total');
     $cclass[] = 'n';
     $cname[] = javelin_tag(
       'span',
       array(
         'sigil' => 'has-tooltip',
         'meta'  => array(
           'tip' => pht('Oldest open task.'),
           'size' => 200,
         ),
       ),
       pht('Oldest (All)'));
     $cclass[] = 'n';
     $cname[] = javelin_tag(
       'span',
       array(
         'sigil' => 'has-tooltip',
         'meta'  => array(
           'tip' => pht(
             'Oldest open task, excluding those with Low or Wishlist priority.'),
           'size' => 200,
         ),
       ),
       pht('Oldest (Pri)'));
     $cclass[] = 'n';
 
     list($ignored, $window_epoch) = $this->getWindow();
     $edate = phabricator_datetime($window_epoch, $user);
     $cname[] = javelin_tag(
       'span',
       array(
         'sigil' => 'has-tooltip',
         'meta'  => array(
           'tip'  => pht('Closed after %s', $edate),
           'size' => 260,
         ),
       ),
       pht('Recently Closed'));
     $cclass[] = 'n';
 
     $table = new AphrontTableView($rows);
     $table->setHeaders($cname);
     $table->setColumnClasses($cclass);
     $table->makeSortable(
       $request->getRequestURI(),
       'order',
       $order,
       $reverse,
       array(
         'name',
         null,
         null,
         null,
         null,
         null,
         null,
         'total',
         'oldest-all',
         'oldest-pri',
         'closed',
       ));
 
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText($header);
     $panel->appendChild($table);
 
     $tokens = array();
     if ($project_handle) {
       $tokens = array($project_handle);
     }
     $filter = $this->renderReportFilters($tokens, $has_window = true);
 
     return array($filter, $panel);
   }
 
 
   /**
    * Load all the tasks that have been recently closed.
    */
   private function loadRecentlyClosedTasks() {
     list($ignored, $window_epoch) = $this->getWindow();
 
     $table = new ManiphestTask();
     $xtable = new ManiphestTransaction();
     $conn_r = $table->establishConnection('r');
 
     // TODO: Gross. This table is not meant to be queried like this. Build
     // real stats tables.
 
     $open_status_list = array();
     foreach (ManiphestTaskStatus::getOpenStatusConstants() as $constant) {
       $open_status_list[] = json_encode((string)$constant);
     }
 
     $rows = queryfx_all(
       $conn_r,
       'SELECT t.id FROM %T t JOIN %T x ON x.objectPHID = t.phid
         WHERE t.status NOT IN (%Ls)
         AND x.oldValue IN (null, %Ls)
         AND x.newValue NOT IN (%Ls)
         AND t.dateModified >= %d
         AND x.dateCreated >= %d',
       $table->getTableName(),
       $xtable->getTableName(),
       ManiphestTaskStatus::getOpenStatusConstants(),
       $open_status_list,
       $open_status_list,
       $window_epoch,
       $window_epoch);
 
     if (!$rows) {
       return array();
     }
 
     $ids = ipull($rows, 'id');
 
     $query = id(new ManiphestTaskQuery())
       ->setViewer($this->getRequest()->getUser())
       ->withIDs($ids);
 
     switch ($this->view) {
       case 'project':
         $query->needProjectPHIDs(true);
         break;
     }
 
     return $query->execute();
   }
 
   /**
    * Parse the "Recently Means" filter into:
    *
    *    - A string representation, like "12 AM 7 days ago" (default);
    *    - a locale-aware epoch representation; and
    *    - a possible error.
    */
   private function getWindow() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $window_str = $this->getRequest()->getStr('window', '12 AM 7 days ago');
 
     $error = null;
     $window_epoch = null;
 
     // Do locale-aware parsing so that the user's timezone is assumed for
     // time windows like "3 PM", rather than assuming the server timezone.
 
     $window_epoch = PhabricatorTime::parseLocalTime($window_str, $user);
     if (!$window_epoch) {
       $error = 'Invalid';
       $window_epoch = time() - (60 * 60 * 24 * 7);
     }
 
     // If the time ends up in the future, convert it to the corresponding time
     // and equal distance in the past. This is so users can type "6 days" (which
     // means "6 days from now") and get the behavior of "6 days ago", rather
     // than no results (because the window epoch is in the future). This might
     // be a little confusing because it casues "tomorrow" to mean "yesterday"
     // and "2022" (or whatever) to mean "ten years ago", but these inputs are
     // nonsense anyway.
 
     if ($window_epoch > time()) {
       $window_epoch = time() - ($window_epoch - time());
     }
 
     return array($window_str, $window_epoch, $error);
   }
 
   private function renderOldest(array $tasks) {
     assert_instances_of($tasks, 'ManiphestTask');
     $oldest = null;
     foreach ($tasks as $id => $task) {
       if (($oldest === null) ||
           ($task->getDateCreated() < $tasks[$oldest]->getDateCreated())) {
         $oldest = $id;
       }
     }
 
     if ($oldest === null) {
       return array('-', 0);
     }
 
     $oldest = $tasks[$oldest];
 
     $raw_age = (time() - $oldest->getDateCreated());
     $age = number_format($raw_age / (24 * 60 * 60)).' d';
 
     $link = javelin_tag(
       'a',
       array(
         'href'  => '/T'.$oldest->getID(),
         'sigil' => 'has-tooltip',
         'meta'  => array(
           'tip' => 'T'.$oldest->getID().': '.$oldest->getTitle(),
         ),
         'target' => '_blank',
       ),
       $age);
 
     return array($link, $raw_age);
   }
 
 }
diff --git a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
index eaedb3a069..b43c728257 100644
--- a/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationUninstallController.php
@@ -1,123 +1,123 @@
 <?php
 
 final class PhabricatorApplicationUninstallController
   extends PhabricatorApplicationsController {
 
   private $application;
   private $action;
 
   public function shouldRequireAdmin() {
     return true;
   }
 
   public function willProcessRequest(array $data) {
     $this->application = $data['application'];
     $this->action = $data['action'];
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $selected = PhabricatorApplication::getByClass($this->application);
 
     if (!$selected) {
       return new Aphront404Response();
     }
 
     $view_uri = $this->getApplicationURI('view/'.$this->application);
 
     $prototypes_enabled = PhabricatorEnv::getEnvConfig(
       'phabricator.show-prototypes');
 
     $dialog = id(new AphrontDialogView())
       ->setUser($user)
       ->addCancelButton($view_uri);
 
     if ($selected->isPrototype() && !$prototypes_enabled) {
       $dialog
         ->setTitle(pht('Prototypes Not Enabled'))
         ->appendChild(
           pht(
             'To manage prototypes, enable them by setting %s in your '.
             'Phabricator configuration.',
             phutil_tag('tt', array(), 'phabricator.show-prototypes')));
       return id(new AphrontDialogResponse())->setDialog($dialog);
     }
 
     if ($request->isDialogFormPost()) {
       $this->manageApplication();
       return id(new AphrontRedirectResponse())->setURI($view_uri);
     }
 
     if ($this->action == 'install') {
       if ($selected->canUninstall()) {
         $dialog
-          ->setTitle('Confirmation')
+          ->setTitle(pht('Confirmation'))
           ->appendChild(
             pht(
               'Install %s application?',
               $selected->getName()))
-          ->addSubmitButton('Install');
+          ->addSubmitButton(pht('Install'));
 
       } else {
         $dialog
-          ->setTitle('Information')
+          ->setTitle(pht('Information'))
           ->appendChild(pht('You cannot install an installed application.'));
       }
     } else {
       if ($selected->canUninstall()) {
         $dialog->setTitle(pht('Really Uninstall Application?'));
 
         if ($selected instanceof PhabricatorHomeApplication) {
           $dialog
             ->appendParagraph(
               pht(
                 'Are you absolutely certain you want to uninstall the Home '.
                 'application?'))
             ->appendParagraph(
               pht(
                 'This is very unusual and will leave you without any '.
                 'content on the Phabricator home page. You should only '.
                 'do this if you are certain you know what you are doing.'))
             ->addSubmitButton(pht('Completely Break Phabricator'));
         } else {
           $dialog
             ->appendParagraph(
               pht(
                 'Really uninstall the %s application?',
                 $selected->getName()))
             ->addSubmitButton(pht('Uninstall'));
         }
       } else {
         $dialog
           ->setTitle(pht('Information'))
           ->appendChild(
             pht(
               'This application cannot be uninstalled, '.
               'because it is required for Phabricator to work.'));
       }
     }
     return id(new AphrontDialogResponse())->setDialog($dialog);
   }
 
   public function manageApplication() {
     $key = 'phabricator.uninstalled-applications';
     $config_entry = PhabricatorConfigEntry::loadConfigEntry($key);
     $list = $config_entry->getValue();
     $uninstalled = PhabricatorEnv::getEnvConfig($key);
 
     if (isset($uninstalled[$this->application])) {
       unset($list[$this->application]);
     } else {
       $list[$this->application] = true;
     }
 
     PhabricatorConfigEditor::storeNewValue(
       $this->getRequest()->getUser(),
       $config_entry,
       $list,
       PhabricatorContentSource::newFromRequest($this->getRequest()));
   }
 
 }
diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
index ce27366da3..104fb128a8 100644
--- a/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
+++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
@@ -1,76 +1,76 @@
 <?php
 
 final class PhabricatorMetaMTAMailgunReceiveController
   extends PhabricatorMetaMTAController {
 
   public function shouldRequireLogin() {
     return false;
   }
 
   private function verifyMessage() {
     $api_key = PhabricatorEnv::getEnvConfig('mailgun.api-key');
     $request = $this->getRequest();
     $timestamp = $request->getStr('timestamp');
     $token = $request->getStr('token');
     $sig = $request->getStr('signature');
     return hash_hmac('sha256', $timestamp.$token, $api_key) == $sig;
 
   }
   public function processRequest() {
 
     // No CSRF for Mailgun.
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 
     if (!$this->verifyMessage()) {
       throw new Exception(
-        'Mail signature is not valid. Check your Mailgun API key.');
+        pht('Mail signature is not valid. Check your Mailgun API key.'));
     }
 
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $raw_headers = $request->getStr('headers');
     $raw_headers = explode("\n", rtrim($raw_headers));
     $raw_dict = array();
     foreach (array_filter($raw_headers) as $header) {
       list($name, $value) = explode(':', $header, 2);
       $raw_dict[$name] = ltrim($value);
     }
 
     $headers = array(
       'to'      => $request->getStr('recipient'),
       'from'    => $request->getStr('from'),
       'subject' => $request->getStr('subject'),
     ) + $raw_dict;
 
     $received = new PhabricatorMetaMTAReceivedMail();
     $received->setHeaders($headers);
     $received->setBodies(array(
       'text' => $request->getStr('stripped-text'),
       'html' => $request->getStr('stripped-html'),
     ));
 
     $file_phids = array();
     foreach ($_FILES as $file_raw) {
       try {
         $file = PhabricatorFile::newFromPHPUpload(
           $file_raw,
           array(
             'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
           ));
         $file_phids[] = $file->getPHID();
       } catch (Exception $ex) {
         phlog($ex);
       }
     }
     $received->setAttachments($file_phids);
     $received->save();
 
     $received->processReceivedMail();
 
     $response = new AphrontWebpageResponse();
     $response->setContent(pht("Got it! Thanks, Mailgun!\n"));
     return $response;
   }
 
 }
diff --git a/src/applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php b/src/applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php
index f5b6ab578a..37eb757616 100644
--- a/src/applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php
+++ b/src/applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php
@@ -1,40 +1,40 @@
 <?php
 
 final class PhabricatorMailReceiverTestCase extends PhabricatorTestCase {
 
   public function testAddressSimilarity() {
     $env = PhabricatorEnv::beginScopedEnv();
     $env->overrideEnvConfig('metamta.single-reply-handler-prefix', 'prefix');
 
     $base = 'alincoln@example.com';
 
     $same = array(
       'alincoln@example.com',
       '"Abrahamn Lincoln" <alincoln@example.com>',
       'ALincoln@example.com',
       'prefix+alincoln@example.com',
     );
 
     foreach ($same as $address) {
       $this->assertTrue(
         PhabricatorMailReceiver::matchAddresses($base, $address),
-        "Address {$address}");
+        pht('Address %s', $address));
     }
 
     $diff = array(
       'a.lincoln@example.com',
       'aluncoln@example.com',
       'prefixalincoln@example.com',
       'badprefix+alincoln@example.com',
       'bad+prefix+alincoln@example.com',
       'prefix+alincoln+sufffix@example.com',
     );
 
     foreach ($diff as $address) {
       $this->assertFalse(
         PhabricatorMailReceiver::matchAddresses($base, $address),
         pht('Address: %s', $address));
     }
   }
 
 }
diff --git a/src/applications/people/typeahead/PhabricatorPeopleDatasource.php b/src/applications/people/typeahead/PhabricatorPeopleDatasource.php
index f359197500..cdd68988a9 100644
--- a/src/applications/people/typeahead/PhabricatorPeopleDatasource.php
+++ b/src/applications/people/typeahead/PhabricatorPeopleDatasource.php
@@ -1,89 +1,89 @@
 <?php
 
 final class PhabricatorPeopleDatasource
   extends PhabricatorTypeaheadDatasource {
 
   private $enrichResults;
 
   /**
    * Controls enriched rendering, for global search. This is a bit hacky and
    * should probably be handled in a more general way, but is fairly reasonable
    * for now.
    */
   public function setEnrichResults($enrich) {
     $this->enrichResults = $enrich;
     return $this;
   }
 
   public function getBrowseTitle() {
     return pht('Browse Users');
   }
 
   public function getPlaceholderText() {
     return pht('Type a username...');
   }
 
   public function getDatasourceApplicationClass() {
     return 'PhabricatorPeopleApplication';
   }
 
   public function loadResults() {
     $viewer = $this->getViewer();
     $tokens = $this->getTokens();
 
     $query = id(new PhabricatorPeopleQuery())
       ->setOrderVector(array('username'));
 
     if ($tokens) {
       $query->withNameTokens($tokens);
     }
 
     $users = $this->executeQuery($query);
 
     if ($this->enrichResults && $users) {
       $phids = mpull($users, 'getPHID');
       $handles = id(new PhabricatorHandleQuery())
         ->setViewer($viewer)
         ->withPHIDs($phids)
         ->execute();
     }
 
     $results = array();
     foreach ($users as $user) {
       $closed = null;
       if ($user->getIsDisabled()) {
         $closed = pht('Disabled');
       } else if ($user->getIsSystemAgent()) {
         $closed = pht('Bot');
       } else if ($user->getIsMailingList()) {
         $closed = pht('Mailing List');
       }
 
       $result = id(new PhabricatorTypeaheadResult())
         ->setName($user->getFullName())
         ->setURI('/p/'.$user->getUsername())
         ->setPHID($user->getPHID())
         ->setPriorityString($user->getUsername())
         ->setPriorityType('user')
         ->setClosed($closed);
 
       if ($user->getIsMailingList()) {
         $result->setIcon('fa-envelope-o');
       }
 
       if ($this->enrichResults) {
-        $display_type = 'User';
+        $display_type = pht('User');
         if ($user->getIsAdmin()) {
-          $display_type = 'Administrator';
+          $display_type = pht('Administrator');
         }
         $result->setDisplayType($display_type);
         $result->setImageURI($handles[$user->getPHID()]->getImageURI());
       }
 
       $results[] = $result;
     }
 
     return $results;
   }
 
 }
diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php
index 4d8cebffa0..75c68852d0 100644
--- a/src/applications/phame/application/PhabricatorPhameApplication.php
+++ b/src/applications/phame/application/PhabricatorPhameApplication.php
@@ -1,78 +1,78 @@
 <?php
 
 final class PhabricatorPhameApplication extends PhabricatorApplication {
 
   public function getName() {
     return pht('Phame');
   }
 
   public function getBaseURI() {
     return '/phame/';
   }
 
   public function getFontIcon() {
     return 'fa-star';
   }
 
   public function getShortDescription() {
-    return 'Blog';
+    return pht('Blog');
   }
 
   public function getTitleGlyph() {
     return "\xe2\x9c\xa9";
   }
 
   public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
     return array(
       array(
         'name' => pht('Phame User Guide'),
         'href' => PhabricatorEnv::getDoclink('Phame User Guide'),
       ),
     );
   }
 
   public function isPrototype() {
     return true;
   }
 
   public function getRoutes() {
     return array(
      '/phame/' => array(
         '' => 'PhamePostListController',
         'r/(?P<id>\d+)/(?P<hash>[^/]+)/(?P<name>.*)'
           => 'PhameResourceController',
 
         'live/(?P<id>[^/]+)/(?P<more>.*)' => 'PhameBlogLiveController',
         'post/' => array(
           '(?:(?P<filter>draft|all)/)?' => 'PhamePostListController',
           'blogger/(?P<bloggername>[\w\.-_]+)/' => 'PhamePostListController',
           'delete/(?P<id>[^/]+)/' => 'PhamePostDeleteController',
           'edit/(?:(?P<id>[^/]+)/)?' => 'PhamePostEditController',
           'view/(?P<id>\d+)/' => 'PhamePostViewController',
           'publish/(?P<id>\d+)/' => 'PhamePostPublishController',
           'unpublish/(?P<id>\d+)/' => 'PhamePostUnpublishController',
           'notlive/(?P<id>\d+)/' => 'PhamePostNotLiveController',
           'preview/' => 'PhamePostPreviewController',
           'framed/(?P<id>\d+)/' => 'PhamePostFramedController',
           'new/' => 'PhamePostNewController',
           'move/(?P<id>\d+)/' => 'PhamePostNewController',
         ),
         'blog/' => array(
           '(?:(?P<filter>user|all)/)?' => 'PhameBlogListController',
           'delete/(?P<id>[^/]+)/' => 'PhameBlogDeleteController',
           'edit/(?P<id>[^/]+)/' => 'PhameBlogEditController',
           'view/(?P<id>[^/]+)/' => 'PhameBlogViewController',
           'feed/(?P<id>[^/]+)/' => 'PhameBlogFeedController',
           'new/' => 'PhameBlogEditController',
         ),
       ),
     );
   }
 
   public function getQuicksandURIPatternBlacklist() {
     return array(
       '/phame/live/.*',
     );
   }
 
 }
diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php
index e44e9615d3..1ea48a618a 100644
--- a/src/applications/phame/storage/PhamePost.php
+++ b/src/applications/phame/storage/PhamePost.php
@@ -1,290 +1,290 @@
 <?php
 
 final class PhamePost extends PhameDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorMarkupInterface,
     PhabricatorApplicationTransactionInterface,
     PhabricatorTokenReceiverInterface {
 
   const MARKUP_FIELD_BODY    = 'markup:body';
   const MARKUP_FIELD_SUMMARY = 'markup:summary';
 
   const VISIBILITY_DRAFT     = 0;
   const VISIBILITY_PUBLISHED = 1;
 
   protected $bloggerPHID;
   protected $title;
   protected $phameTitle;
   protected $body;
   protected $visibility;
   protected $configData;
   protected $datePublished;
   protected $blogPHID;
 
   private $blog;
 
   public static function initializePost(
     PhabricatorUser $blogger,
     PhameBlog $blog) {
 
     $post = id(new PhamePost())
       ->setBloggerPHID($blogger->getPHID())
       ->setBlogPHID($blog->getPHID())
       ->setBlog($blog)
       ->setDatePublished(0)
       ->setVisibility(self::VISIBILITY_DRAFT);
     return $post;
   }
 
   public function setBlog(PhameBlog $blog) {
     $this->blog = $blog;
     return $this;
   }
 
   public function getBlog() {
     return $this->blog;
   }
 
   public function getViewURI() {
     // go for the pretty uri if we can
     $domain = ($this->blog ? $this->blog->getDomain() : '');
     if ($domain) {
       $phame_title = PhabricatorSlug::normalize($this->getPhameTitle());
       return 'http://'.$domain.'/post/'.$phame_title;
     }
     $uri = '/phame/post/view/'.$this->getID().'/';
     return PhabricatorEnv::getProductionURI($uri);
   }
 
   public function getEditURI() {
     return '/phame/post/edit/'.$this->getID().'/';
   }
 
   public function isDraft() {
     return $this->getVisibility() == self::VISIBILITY_DRAFT;
   }
 
   public function getHumanName() {
     if ($this->isDraft()) {
       $name = 'draft';
     } else {
       $name = 'post';
     }
 
     return $name;
   }
 
   public function setCommentsWidget($widget) {
     $config_data = $this->getConfigData();
     $config_data['comments_widget'] = $widget;
     return $this;
   }
 
   public function getCommentsWidget() {
     $config_data = $this->getConfigData();
     if (empty($config_data)) {
       return 'none';
     }
     return idx($config_data, 'comments_widget', 'none');
   }
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID   => true,
       self::CONFIG_SERIALIZATION => array(
         'configData' => self::SERIALIZATION_JSON,
       ),
       self::CONFIG_COLUMN_SCHEMA => array(
         'title' => 'text255',
         'phameTitle' => 'sort64',
         'visibility' => 'uint32',
 
         // T6203/NULLABILITY
         // These seem like they should always be non-null?
         'blogPHID' => 'phid?',
         'body' => 'text?',
         'configData' => 'text?',
 
         // T6203/NULLABILITY
         // This one probably should be nullable?
         'datePublished' => 'epoch',
       ),
       self::CONFIG_KEY_SCHEMA => array(
         'key_phid' => null,
         'phid' => array(
           'columns' => array('phid'),
           'unique' => true,
         ),
         'phameTitle' => array(
           'columns' => array('bloggerPHID', 'phameTitle'),
           'unique' => true,
         ),
         'bloggerPosts' => array(
           'columns' => array(
             'bloggerPHID',
             'visibility',
             'datePublished',
             'id',
           ),
         ),
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorPhamePostPHIDType::TYPECONST);
   }
 
   public function toDictionary() {
     return array(
       'id'            => $this->getID(),
       'phid'          => $this->getPHID(),
       'blogPHID'      => $this->getBlogPHID(),
       'bloggerPHID'   => $this->getBloggerPHID(),
       'viewURI'       => $this->getViewURI(),
       'title'         => $this->getTitle(),
       'phameTitle'    => $this->getPhameTitle(),
       'body'          => $this->getBody(),
       'summary'       => PhabricatorMarkupEngine::summarize($this->getBody()),
       'datePublished' => $this->getDatePublished(),
       'published'     => !$this->isDraft(),
     );
   }
 
   public static function getVisibilityOptionsForSelect() {
     return array(
       self::VISIBILITY_DRAFT     => pht('Draft: visible only to me.'),
       self::VISIBILITY_PUBLISHED => pht(
         'Published: visible to the whole world.'),
     );
   }
 
   public function getCommentsWidgetOptionsForSelect() {
     $current = $this->getCommentsWidget();
     $options = array();
 
     if ($current == 'facebook' ||
         PhabricatorFacebookAuthProvider::getFacebookApplicationID()) {
-      $options['facebook'] = 'Facebook';
+      $options['facebook'] = pht('Facebook');
     }
     if ($current == 'disqus' ||
         PhabricatorEnv::getEnvConfig('disqus.shortname')) {
-      $options['disqus'] = 'Disqus';
+      $options['disqus'] = pht('Disqus');
     }
-    $options['none'] = 'None';
+    $options['none'] = pht('None');
 
     return $options;
   }
 
 
 /* -(  PhabricatorPolicyInterface Implementation  )-------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     // Draft posts are visible only to the author. Published posts are visible
     // to whoever the blog is visible to.
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         if (!$this->isDraft() && $this->getBlog()) {
           return $this->getBlog()->getViewPolicy();
         } else {
           return PhabricatorPolicies::POLICY_NOONE;
         }
         break;
       case PhabricatorPolicyCapability::CAN_EDIT:
         return PhabricatorPolicies::POLICY_NOONE;
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $user) {
     // A blog post's author can always view it, and is the only user allowed
     // to edit it.
 
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
       case PhabricatorPolicyCapability::CAN_EDIT:
         return ($user->getPHID() == $this->getBloggerPHID());
     }
   }
 
   public function describeAutomaticCapability($capability) {
     return pht('The author of a blog post can always view and edit it.');
   }
 
 
 /* -(  PhabricatorMarkupInterface Implementation  )-------------------------- */
 
 
   public function getMarkupFieldKey($field) {
     $hash = PhabricatorHash::digest($this->getMarkupText($field));
     return $this->getPHID().':'.$field.':'.$hash;
   }
 
   public function newMarkupEngine($field) {
     return PhabricatorMarkupEngine::newPhameMarkupEngine();
   }
 
   public function getMarkupText($field) {
     switch ($field) {
       case self::MARKUP_FIELD_BODY:
         return $this->getBody();
       case self::MARKUP_FIELD_SUMMARY:
         return PhabricatorMarkupEngine::summarize($this->getBody());
     }
   }
 
   public function didMarkupText(
     $field,
     $output,
     PhutilMarkupEngine $engine) {
     return $output;
   }
 
   public function shouldUseMarkupCache($field) {
     return (bool)$this->getPHID();
   }
 
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
 
   public function getApplicationTransactionEditor() {
     return new PhamePostEditor();
   }
 
   public function getApplicationTransactionObject() {
     return $this;
   }
 
   public function getApplicationTransactionTemplate() {
     return new PhamePostTransaction();
   }
 
   public function willRenderTimeline(
     PhabricatorApplicationTransactionView $timeline,
     AphrontRequest $request) {
 
     return $timeline;
   }
 
 
 /* -(  PhabricatorTokenReceiverInterface  )---------------------------------- */
 
 
   public function getUsersToNotifyOfTokenGiven() {
     return array(
       $this->getBloggerPHID(),
     );
   }
 
 }
diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php
index 9d18fa32f1..3ed2cc0779 100644
--- a/src/applications/pholio/controller/PholioMockEditController.php
+++ b/src/applications/pholio/controller/PholioMockEditController.php
@@ -1,395 +1,395 @@
 <?php
 
 final class PholioMockEditController extends PholioController {
 
   private $id;
 
   public function willProcessRequest(array $data) {
     $this->id = idx($data, 'id');
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     if ($this->id) {
       $mock = id(new PholioMockQuery())
         ->setViewer($user)
         ->needImages(true)
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->withIDs(array($this->id))
         ->executeOne();
 
       if (!$mock) {
         return new Aphront404Response();
       }
 
       $title = pht('Edit Mock');
 
       $is_new = false;
       $mock_images = $mock->getImages();
       $files = mpull($mock_images, 'getFile');
       $mock_images = mpull($mock_images, null, 'getFilePHID');
     } else {
       $mock = PholioMock::initializeNewMock($user);
 
       $title = pht('Create Mock');
 
       $is_new = true;
       $files = array();
       $mock_images = array();
     }
 
     if ($is_new) {
       $v_projects = array();
     } else {
       $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
         $mock->getPHID(),
         PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
       $v_projects = array_reverse($v_projects);
     }
 
     $e_name = true;
     $e_images = count($mock_images) ? null : true;
     $errors = array();
     $posted_mock_images = array();
 
     $v_name = $mock->getName();
     $v_desc = $mock->getDescription();
     $v_status = $mock->getStatus();
     $v_view = $mock->getViewPolicy();
     $v_edit = $mock->getEditPolicy();
     $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID(
       $mock->getPHID());
 
     if ($request->isFormPost()) {
       $xactions = array();
 
       $type_name = PholioTransaction::TYPE_NAME;
       $type_desc = PholioTransaction::TYPE_DESCRIPTION;
       $type_status = PholioTransaction::TYPE_STATUS;
       $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
       $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
       $type_cc   = PhabricatorTransactions::TYPE_SUBSCRIBERS;
 
       $v_name = $request->getStr('name');
       $v_desc = $request->getStr('description');
       $v_status = $request->getStr('status');
       $v_view = $request->getStr('can_view');
       $v_edit = $request->getStr('can_edit');
       $v_cc   = $request->getArr('cc');
       $v_projects = $request->getArr('projects');
 
       $mock_xactions = array();
       $mock_xactions[$type_name] = $v_name;
       $mock_xactions[$type_desc] = $v_desc;
       $mock_xactions[$type_status] = $v_status;
       $mock_xactions[$type_view] = $v_view;
       $mock_xactions[$type_edit] = $v_edit;
       $mock_xactions[$type_cc]   = array('=' => $v_cc);
 
       if (!strlen($request->getStr('name'))) {
-        $e_name = 'Required';
+        $e_name = pht('Required');
         $errors[] = pht('You must give the mock a name.');
       }
 
       $file_phids = $request->getArr('file_phids');
       if ($file_phids) {
         $files = id(new PhabricatorFileQuery())
           ->setViewer($user)
           ->withPHIDs($file_phids)
           ->execute();
         $files = mpull($files, null, 'getPHID');
         $files = array_select_keys($files, $file_phids);
       } else {
         $files = array();
       }
 
       if (!$files) {
         $e_images = pht('Required');
         $errors[] = pht('You must add at least one image to the mock.');
       } else {
         $mock->setCoverPHID(head($files)->getPHID());
       }
 
       foreach ($mock_xactions as $type => $value) {
         $xactions[$type] = id(new PholioTransaction())
           ->setTransactionType($type)
           ->setNewValue($value);
       }
 
       $order = $request->getStrList('imageOrder');
       $sequence_map = array_flip($order);
       $replaces = $request->getArr('replaces');
       $replaces_map = array_flip($replaces);
 
       /**
        * Foreach file posted, check to see whether we are replacing an image,
        * adding an image, or simply updating image metadata. Create
        * transactions for these cases as appropos.
        */
       foreach ($files as $file_phid => $file) {
         $replaces_image_phid = null;
         if (isset($replaces_map[$file_phid])) {
           $old_file_phid = $replaces_map[$file_phid];
           if ($old_file_phid != $file_phid) {
             $old_image = idx($mock_images, $old_file_phid);
             if ($old_image) {
               $replaces_image_phid = $old_image->getPHID();
             }
           }
         }
 
         $existing_image = idx($mock_images, $file_phid);
 
         $title = (string)$request->getStr('title_'.$file_phid);
         $description = (string)$request->getStr('description_'.$file_phid);
         $sequence = $sequence_map[$file_phid];
 
         if ($replaces_image_phid) {
           $replace_image = id(new PholioImage())
             ->setReplacesImagePHID($replaces_image_phid)
             ->setFilePhid($file_phid)
             ->attachFile($file)
             ->setName(strlen($title) ? $title : $file->getName())
             ->setDescription($description)
             ->setSequence($sequence);
           $xactions[] = id(new PholioTransaction())
             ->setTransactionType(
               PholioTransaction::TYPE_IMAGE_REPLACE)
             ->setNewValue($replace_image);
           $posted_mock_images[] = $replace_image;
         } else if (!$existing_image) { // this is an add
           $add_image = id(new PholioImage())
             ->setFilePhid($file_phid)
             ->attachFile($file)
             ->setName(strlen($title) ? $title : $file->getName())
             ->setDescription($description)
             ->setSequence($sequence);
           $xactions[] = id(new PholioTransaction())
             ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE)
             ->setNewValue(
               array('+' => array($add_image)));
           $posted_mock_images[] = $add_image;
         } else {
           $xactions[] = id(new PholioTransaction())
             ->setTransactionType(PholioTransaction::TYPE_IMAGE_NAME)
             ->setNewValue(
               array($existing_image->getPHID() => $title));
           $xactions[] = id(new PholioTransaction())
             ->setTransactionType(
               PholioTransaction::TYPE_IMAGE_DESCRIPTION)
               ->setNewValue(
                 array($existing_image->getPHID() => $description));
           $xactions[] = id(new PholioTransaction())
             ->setTransactionType(
               PholioTransaction::TYPE_IMAGE_SEQUENCE)
               ->setNewValue(
                 array($existing_image->getPHID() => $sequence));
 
           $posted_mock_images[] = $existing_image;
         }
       }
       foreach ($mock_images as $file_phid => $mock_image) {
         if (!isset($files[$file_phid]) && !isset($replaces[$file_phid])) {
           // this is an outright delete
           $xactions[] = id(new PholioTransaction())
             ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE)
             ->setNewValue(
               array('-' => array($mock_image)));
         }
       }
 
       if (!$errors) {
         $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
         $xactions[] = id(new PholioTransaction())
           ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
           ->setMetadataValue('edge:type', $proj_edge_type)
           ->setNewValue(array('=' => array_fuse($v_projects)));
 
         $mock->openTransaction();
         $editor = id(new PholioMockEditor())
           ->setContentSourceFromRequest($request)
           ->setContinueOnNoEffect(true)
           ->setActor($user);
 
         $xactions = $editor->applyTransactions($mock, $xactions);
 
         $mock->saveTransaction();
 
         return id(new AphrontRedirectResponse())
           ->setURI('/M'.$mock->getID());
       }
     }
 
     if ($this->id) {
       $submit = id(new AphrontFormSubmitControl())
         ->addCancelButton('/M'.$this->id)
         ->setValue(pht('Save'));
     } else {
       $submit = id(new AphrontFormSubmitControl())
         ->addCancelButton($this->getApplicationURI())
         ->setValue(pht('Create'));
     }
 
     $policies = id(new PhabricatorPolicyQuery())
       ->setViewer($user)
       ->setObject($mock)
       ->execute();
 
     // NOTE: Make this show up correctly on the rendered form.
     $mock->setViewPolicy($v_view);
     $mock->setEditPolicy($v_edit);
 
     $image_elements = array();
     if ($posted_mock_images) {
       $display_mock_images = $posted_mock_images;
     } else {
       $display_mock_images = $mock_images;
     }
     foreach ($display_mock_images as $mock_image) {
       $image_elements[] = id(new PholioUploadedImageView())
         ->setUser($user)
         ->setImage($mock_image)
         ->setReplacesPHID($mock_image->getFilePHID());
     }
 
     $list_id = celerity_generate_unique_node_id();
     $drop_id = celerity_generate_unique_node_id();
     $order_id = celerity_generate_unique_node_id();
 
     $list_control = phutil_tag(
       'div',
       array(
         'id' => $list_id,
         'class' => 'pholio-edit-list',
       ),
       $image_elements);
 
     $drop_control = phutil_tag(
       'div',
       array(
         'id' => $drop_id,
         'class' => 'pholio-edit-drop',
       ),
       pht('Drag and drop images here to add them to the mock.'));
 
     $order_control = phutil_tag(
       'input',
       array(
         'type' => 'hidden',
         'name' => 'imageOrder',
         'id' => $order_id,
       ));
 
     Javelin::initBehavior(
       'pholio-mock-edit',
       array(
         'listID' => $list_id,
         'dropID' => $drop_id,
         'orderID' => $order_id,
         'uploadURI' => '/file/dropupload/',
         'renderURI' => $this->getApplicationURI('image/upload/'),
         'pht' => array(
           'uploading' => pht('Uploading Image...'),
           'uploaded' => pht('Upload Complete...'),
           'undo' => pht('Undo'),
           'removed' => pht('This image will be removed from the mock.'),
         ),
       ));
 
     require_celerity_resource('pholio-edit-css');
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->appendChild($order_control)
       ->appendChild(
         id(new AphrontFormTextControl())
         ->setName('name')
         ->setValue($v_name)
         ->setLabel(pht('Name'))
         ->setError($e_name))
       ->appendChild(
         id(new PhabricatorRemarkupControl())
         ->setName('description')
         ->setValue($v_desc)
         ->setLabel(pht('Description'))
         ->setUser($user));
 
     if ($this->id) {
       $form->appendChild(
         id(new AphrontFormSelectControl())
         ->setLabel(pht('Status'))
         ->setName('status')
         ->setValue($mock->getStatus())
         ->setOptions($mock->getStatuses()));
     } else {
       $form->addHiddenInput('status', 'open');
     }
 
     $form
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Projects'))
           ->setName('projects')
           ->setValue($v_projects)
           ->setDatasource(new PhabricatorProjectDatasource()))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setLabel(pht('Subscribers'))
           ->setName('cc')
           ->setValue($v_cc)
           ->setUser($user)
           ->setDatasource(new PhabricatorMetaMTAMailableDatasource()))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($user)
           ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
           ->setPolicyObject($mock)
           ->setPolicies($policies)
           ->setName('can_view'))
       ->appendChild(
         id(new AphrontFormPolicyControl())
           ->setUser($user)
           ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
           ->setPolicyObject($mock)
           ->setPolicies($policies)
           ->setName('can_edit'))
       ->appendChild(
         id(new AphrontFormMarkupControl())
           ->setValue($list_control))
       ->appendChild(
         id(new AphrontFormMarkupControl())
           ->setValue($drop_control)
           ->setError($e_images))
       ->appendChild($submit);
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setFormErrors($errors)
       ->setForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
     if (!$is_new) {
       $crumbs->addTextCrumb($mock->getMonogram(), '/'.$mock->getMonogram());
     }
     $crumbs->addTextCrumb($title);
 
     $content = array(
       $crumbs,
       $form_box,
     );
 
     $this->addExtraQuicksandConfig(
       array('mockEditConfig' => true));
     return $this->buildApplicationPage(
       $content,
       array(
         'title' => $title,
       ));
   }
 
 }
diff --git a/src/applications/phortune/controller/PhortuneProductListController.php b/src/applications/phortune/controller/PhortuneProductListController.php
index 8e219630d4..8a1181b33e 100644
--- a/src/applications/phortune/controller/PhortuneProductListController.php
+++ b/src/applications/phortune/controller/PhortuneProductListController.php
@@ -1,57 +1,59 @@
 <?php
 
 final class PhortuneProductListController extends PhabricatorController {
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $pager = new AphrontCursorPagerView();
     $pager->readFromRequest($request);
 
     $query = id(new PhortuneProductQuery())
       ->setViewer($user);
 
     $products = $query->executeWithCursorPager($pager);
 
     $title = pht('Product List');
 
     $crumbs = $this->buildApplicationCrumbs();
-    $crumbs->addTextCrumb('Products', $this->getApplicationURI('product/'));
+    $crumbs->addTextCrumb(
+      pht('Products'),
+      $this->getApplicationURI('product/'));
     $crumbs->addAction(
       id(new PHUIListItemView())
         ->setName(pht('Create Product'))
         ->setHref($this->getApplicationURI('product/edit/'))
         ->setIcon('fa-plus-square'));
 
     $product_list = id(new PHUIObjectItemListView())
       ->setUser($user)
       ->setNoDataString(pht('No products.'));
 
     foreach ($products as $product) {
       $view_uri = $this->getApplicationURI(
         'product/view/'.$product->getID().'/');
 
       $price = $product->getPriceAsCurrency();
 
       $item = id(new PHUIObjectItemView())
         ->setObjectName($product->getID())
         ->setHeader($product->getProductName())
         ->setHref($view_uri)
         ->addAttribute($price->formatForDisplay());
 
       $product_list->addItem($item);
     }
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $product_list,
         $pager,
       ),
       array(
         'title' => $title,
       ));
   }
 
 }
diff --git a/src/applications/phortune/controller/PhortuneProviderActionController.php b/src/applications/phortune/controller/PhortuneProviderActionController.php
index dcb171911f..b856b67d97 100644
--- a/src/applications/phortune/controller/PhortuneProviderActionController.php
+++ b/src/applications/phortune/controller/PhortuneProviderActionController.php
@@ -1,87 +1,85 @@
 <?php
 
 final class PhortuneProviderActionController
   extends PhortuneController {
 
   private $id;
   private $action;
 
   public function willProcessRequest(array $data) {
     $this->id = $data['id'];
     $this->setAction($data['action']);
   }
 
   public function setAction($action) {
     $this->action = $action;
     return $this;
   }
 
   public function getAction() {
     return $this->action;
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $provider_config = id(new PhortunePaymentProviderConfigQuery())
       ->setViewer($viewer)
       ->withIDs(array($this->id))
       ->executeOne();
     if (!$provider_config) {
       return new Aphront404Response();
     }
 
     $provider = $provider_config->buildProvider();
 
     if (!$provider->canRespondToControllerAction($this->getAction())) {
       return new Aphront404Response();
     }
 
     $response = $provider->processControllerRequest($this, $request);
 
     if ($response instanceof AphrontResponse) {
       return $response;
     }
 
-    $title = 'Phortune';
-
     return $this->buildApplicationPage(
       $response,
       array(
-        'title' => $title,
+        'title' => pht('Phortune'),
       ));
   }
 
 
   public function loadCart($id) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     return id(new PhortuneCartQuery())
       ->setViewer($viewer)
       ->needPurchases(true)
       ->withIDs(array($id))
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->executeOne();
   }
 
   public function loadActiveCharge(PhortuneCart $cart) {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     return id(new PhortuneChargeQuery())
       ->setViewer($viewer)
       ->withCartPHIDs(array($cart->getPHID()))
       ->withStatuses(
         array(
           PhortuneCharge::STATUS_CHARGING,
         ))
       ->executeOne();
   }
 
 }
diff --git a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php
index b235f5b842..f5a79a4b28 100644
--- a/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php
+++ b/src/applications/phpast/controller/PhabricatorXHPASTViewRunController.php
@@ -1,57 +1,57 @@
 <?php
 
 final class PhabricatorXHPASTViewRunController
   extends PhabricatorXHPASTViewController {
 
   public function processRequest() {
 
     $request = $this->getRequest();
     $user = $request->getUser();
 
     if ($request->isFormPost()) {
       $source = $request->getStr('source');
 
       $future = PhutilXHPASTBinary::getParserFuture($source);
       $resolved = $future->resolve();
 
       // This is just to let it throw exceptions if stuff is broken.
       $parse_tree = XHPASTTree::newFromDataAndResolvedExecFuture(
         $source,
         $resolved);
 
       list($err, $stdout, $stderr) = $resolved;
 
       $storage_tree = new PhabricatorXHPASTViewParseTree();
       $storage_tree->setInput($source);
       $storage_tree->setStdout($stdout);
       $storage_tree->setAuthorPHID($user->getPHID());
       $storage_tree->save();
 
       return id(new AphrontRedirectResponse())
         ->setURI('/xhpast/view/'.$storage_tree->getID().'/');
     }
 
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel(pht('Source'))
           ->setName('source')
           ->setValue("<?php\n\n")
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL))
       ->appendChild(
         id(new AphrontFormSubmitControl())
-          ->setValue('Parse'));
+          ->setValue(pht('Parse')));
 
     $form_box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Generate XHP AST'))
       ->setForm($form);
 
     return $this->buildApplicationPage(
       $form_box,
       array(
         'title' => pht('XHPAST View'),
       ));
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentBrowseController.php b/src/applications/phragment/controller/PhragmentBrowseController.php
index d41ff4442a..7b0a16cc52 100644
--- a/src/applications/phragment/controller/PhragmentBrowseController.php
+++ b/src/applications/phragment/controller/PhragmentBrowseController.php
@@ -1,98 +1,98 @@
 <?php
 
 final class PhragmentBrowseController extends PhragmentController {
 
   private $dblob;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function willProcessRequest(array $data) {
     $this->dblob = idx($data, 'dblob', '');
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $parents = $this->loadParentFragments($this->dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $current = nonempty(last($parents), null);
 
     $path = '';
     if ($current !== null) {
       $path = $current->getPath();
     }
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     if ($this->hasApplicationCapability(
       PhragmentCanCreateCapability::CAPABILITY)) {
       $crumbs->addAction(
         id(new PHUIListItemView())
           ->setName(pht('Create Fragment'))
           ->setHref($this->getApplicationURI('/create/'.$path))
           ->setIcon('fa-plus-square'));
     }
 
     $current_box = $this->createCurrentFragmentView($current, false);
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     $fragments = null;
     if ($current === null) {
       // Find all root fragments.
       $fragments = id(new PhragmentFragmentQuery())
         ->setViewer($this->getRequest()->getUser())
         ->needLatestVersion(true)
         ->withDepths(array(1))
         ->execute();
     } else {
       // Find all child fragments.
       $fragments = id(new PhragmentFragmentQuery())
         ->setViewer($this->getRequest()->getUser())
         ->needLatestVersion(true)
         ->withLeadingPath($current->getPath().'/')
         ->withDepths(array($current->getDepth() + 1))
         ->execute();
     }
 
     foreach ($fragments as $fragment) {
       $item = id(new PHUIObjectItemView());
       $item->setHeader($fragment->getName());
       $item->setHref($fragment->getURI());
       if (!$fragment->isDirectory()) {
         $item->addAttribute(pht(
           'Last Updated %s',
           phabricator_datetime(
             $fragment->getLatestVersion()->getDateCreated(),
             $viewer)));
         $item->addAttribute(pht(
           'Latest Version %s',
           $fragment->getLatestVersion()->getSequence()));
         if ($fragment->isDeleted()) {
           $item->setDisabled(true);
           $item->addAttribute(pht('Deleted'));
         }
       } else {
-        $item->addAttribute('Directory');
+        $item->addAttribute(pht('Directory'));
       }
       $list->addItem($item);
     }
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $this->renderConfigurationWarningIfRequired(),
         $current_box,
         $list,
       ),
       array(
         'title' => pht('Browse Fragments'),
       ));
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentHistoryController.php b/src/applications/phragment/controller/PhragmentHistoryController.php
index cb84fd819f..72e0000b80 100644
--- a/src/applications/phragment/controller/PhragmentHistoryController.php
+++ b/src/applications/phragment/controller/PhragmentHistoryController.php
@@ -1,112 +1,112 @@
 <?php
 
 final class PhragmentHistoryController extends PhragmentController {
 
   private $dblob;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function willProcessRequest(array $data) {
     $this->dblob = idx($data, 'dblob', '');
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $parents = $this->loadParentFragments($this->dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $current = idx($parents, count($parents) - 1, null);
 
     $path = $current->getPath();
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     if ($this->hasApplicationCapability(
       PhragmentCanCreateCapability::CAPABILITY)) {
       $crumbs->addAction(
         id(new PHUIListItemView())
           ->setName(pht('Create Fragment'))
           ->setHref($this->getApplicationURI('/create/'.$path))
           ->setIcon('fa-plus-square'));
     }
 
     $current_box = $this->createCurrentFragmentView($current, true);
 
     $versions = id(new PhragmentFragmentVersionQuery())
       ->setViewer($viewer)
       ->withFragmentPHIDs(array($current->getPHID()))
       ->execute();
 
     $list = id(new PHUIObjectItemListView())
       ->setUser($viewer);
 
     $file_phids = mpull($versions, 'getFilePHID');
     $files = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withPHIDs($file_phids)
       ->execute();
     $files = mpull($files, null, 'getPHID');
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $viewer,
       $current,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $first = true;
     foreach ($versions as $version) {
       $item = id(new PHUIObjectItemView());
       $item->setHeader(pht('Version %s', $version->getSequence()));
       $item->setHref($version->getURI());
       $item->addAttribute(phabricator_datetime(
         $version->getDateCreated(),
         $viewer));
 
       if ($version->getFilePHID() === null) {
         $item->setDisabled(true);
-        $item->addAttribute('Deletion');
+        $item->addAttribute(pht('Deletion'));
       }
 
       if (!$first && $can_edit) {
         $item->addAction(id(new PHUIListItemView())
           ->setIcon('fa-refresh')
           ->setRenderNameAsTooltip(true)
           ->setWorkflow(true)
           ->setName(pht('Revert to Here'))
           ->setHref($this->getApplicationURI(
             'revert/'.$version->getID().'/'.$current->getPath())));
       }
 
       $disabled = !isset($files[$version->getFilePHID()]);
       $action = id(new PHUIListItemView())
         ->setIcon('fa-download')
         ->setDisabled($disabled || !$this->isCorrectlyConfigured())
         ->setRenderNameAsTooltip(true)
         ->setName(pht('Download'));
       if (!$disabled && $this->isCorrectlyConfigured()) {
         $action->setHref($files[$version->getFilePHID()]
           ->getDownloadURI($version->getURI()));
       }
       $item->addAction($action);
 
       $list->addItem($item);
 
       $first = false;
     }
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $this->renderConfigurationWarningIfRequired(),
         $current_box,
         $list,
       ),
       array(
         'title' => pht('Fragment History'),
       ));
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
index 2cdea8528a..e135819280 100644
--- a/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
+++ b/src/applications/phragment/controller/PhragmentSnapshotCreateController.php
@@ -1,173 +1,173 @@
 <?php
 
 final class PhragmentSnapshotCreateController extends PhragmentController {
 
   private $dblob;
 
   public function willProcessRequest(array $data) {
     $this->dblob = idx($data, 'dblob', '');
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $parents = $this->loadParentFragments($this->dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $fragment = nonempty(last($parents), null);
     if ($fragment === null) {
       return new Aphront404Response();
     }
 
     PhabricatorPolicyFilter::requireCapability(
       $viewer,
       $fragment,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $children = id(new PhragmentFragmentQuery())
       ->setViewer($viewer)
       ->needLatestVersion(true)
       ->withLeadingPath($fragment->getPath().'/')
       ->execute();
 
     $errors = array();
     if ($request->isFormPost()) {
 
       $v_name = $request->getStr('name');
       if (strlen($v_name) === 0) {
         $errors[] = pht('You must specify a name.');
       }
       if (strpos($v_name, '/') !== false) {
         $errors[] = pht('Snapshot names can not contain "/".');
       }
 
       if (!count($errors)) {
         $snapshot = null;
 
         try {
           // Create the snapshot.
           $snapshot = id(new PhragmentSnapshot())
             ->setPrimaryFragmentPHID($fragment->getPHID())
             ->setName($v_name)
             ->save();
         } catch (AphrontDuplicateKeyQueryException $e) {
           $errors[] = pht('A snapshot with this name already exists.');
         }
 
         if (!count($errors)) {
           // Add the primary fragment.
           id(new PhragmentSnapshotChild())
             ->setSnapshotPHID($snapshot->getPHID())
             ->setFragmentPHID($fragment->getPHID())
             ->setFragmentVersionPHID($fragment->getLatestVersionPHID())
             ->save();
 
           // Add all of the child fragments.
           foreach ($children as $child) {
             id(new PhragmentSnapshotChild())
               ->setSnapshotPHID($snapshot->getPHID())
               ->setFragmentPHID($child->getPHID())
               ->setFragmentVersionPHID($child->getLatestVersionPHID())
               ->save();
           }
 
           return id(new AphrontRedirectResponse())
             ->setURI('/phragment/snapshot/view/'.$snapshot->getID());
         }
       }
     }
 
     $fragment_sequence = '-';
     if ($fragment->getLatestVersion() !== null) {
       $fragment_sequence = $fragment->getLatestVersion()->getSequence();
     }
 
     $rows = array();
     $rows[] = phutil_tag(
       'tr',
       array(),
       array(
-        phutil_tag('th', array(), 'Fragment'),
-        phutil_tag('th', array(), 'Version'),
+        phutil_tag('th', array(), pht('Fragment')),
+        phutil_tag('th', array(), pht('Version')),
       ));
     $rows[] = phutil_tag(
       'tr',
       array(),
       array(
         phutil_tag('td', array(), $fragment->getPath()),
         phutil_tag('td', array(), $fragment_sequence),
       ));
     foreach ($children as $child) {
       $sequence = '-';
       if ($child->getLatestVersion() !== null) {
         $sequence = $child->getLatestVersion()->getSequence();
       }
       $rows[] = phutil_tag(
         'tr',
         array(),
         array(
           phutil_tag('td', array(), $child->getPath()),
           phutil_tag('td', array(), $sequence),
         ));
     }
 
     $table = phutil_tag(
       'table',
       array('class' => 'remarkup-table'),
       $rows);
 
     $container = phutil_tag(
       'div',
       array('class' => 'phabricator-remarkup'),
       array(
         phutil_tag(
           'p',
           array(),
           pht(
             'The snapshot will contain the following fragments at '.
             'the specified versions: ')),
         $table,
       ));
 
     $form = id(new AphrontFormView())
       ->setUser($viewer)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Fragment Path'))
           ->setDisabled(true)
           ->setValue('/'.$fragment->getPath()))
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setLabel(pht('Snapshot Name'))
           ->setName('name'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Create Snapshot'))
           ->addCancelButton(
             $this->getApplicationURI('browse/'.$fragment->getPath())))
       ->appendChild(
         id(new PHUIFormDividerControl()))
       ->appendInstructions($container);
 
     $crumbs = $this->buildApplicationCrumbsWithPath($parents);
     $crumbs->addTextCrumb(pht('Create Snapshot'));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText(pht('Create Snapshot of %s', $fragment->getName()))
       ->setFormErrors($errors)
       ->setForm($form);
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $this->renderConfigurationWarningIfRequired(),
         $box,
       ),
       array(
         'title' => pht('Create Fragment'),
       ));
   }
 
 }
diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php
index 574090eede..0de9f4d344 100644
--- a/src/applications/phragment/controller/PhragmentZIPController.php
+++ b/src/applications/phragment/controller/PhragmentZIPController.php
@@ -1,156 +1,156 @@
 <?php
 
 final class PhragmentZIPController extends PhragmentController {
 
   private $dblob;
   private $snapshot;
 
   private $snapshotCache;
 
   public function shouldAllowPublic() {
     return true;
   }
 
   public function willProcessRequest(array $data) {
     $this->dblob = idx($data, 'dblob', '');
     $this->snapshot = idx($data, 'snapshot', null);
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     $parents = $this->loadParentFragments($this->dblob);
     if ($parents === null) {
       return new Aphront404Response();
     }
     $fragment = idx($parents, count($parents) - 1, null);
 
     if ($this->snapshot !== null) {
       $snapshot = id(new PhragmentSnapshotQuery())
         ->setViewer($viewer)
         ->withPrimaryFragmentPHIDs(array($fragment->getPHID()))
         ->withNames(array($this->snapshot))
         ->executeOne();
       if ($snapshot === null) {
         return new Aphront404Response();
       }
 
       $cache = id(new PhragmentSnapshotChildQuery())
         ->setViewer($viewer)
         ->needFragmentVersions(true)
         ->withSnapshotPHIDs(array($snapshot->getPHID()))
         ->execute();
       $this->snapshotCache = mpull(
         $cache,
         'getFragmentVersion',
         'getFragmentPHID');
     }
 
     $temp = new TempFile();
 
     $zip = null;
     try {
       $zip = new ZipArchive();
     } catch (Exception $e) {
       $dialog = new AphrontDialogView();
       $dialog->setUser($viewer);
 
       $inst = pht(
         'This system does not have the ZIP PHP extension installed. This '.
         'is required to download ZIPs from Phragment.');
 
       $dialog->setTitle(pht('ZIP Extension Not Installed'));
       $dialog->appendParagraph($inst);
 
       $dialog->addCancelButton('/phragment/browse/'.$this->dblob);
       return id(new AphrontDialogResponse())->setDialog($dialog);
     }
 
     if (!$zip->open((string)$temp, ZipArchive::CREATE)) {
-      throw new Exception('Unable to create ZIP archive!');
+      throw new Exception(pht('Unable to create ZIP archive!'));
     }
 
     $mappings = $this->getFragmentMappings($fragment, $fragment->getPath());
 
     $phids = array();
     foreach ($mappings as $path => $file_phid) {
       $phids[] = $file_phid;
     }
 
     $files = id(new PhabricatorFileQuery())
       ->setViewer($viewer)
       ->withPHIDs($phids)
       ->execute();
     $files = mpull($files, null, 'getPHID');
     foreach ($mappings as $path => $file_phid) {
       if (!isset($files[$file_phid])) {
         // The path is most likely pointing to a deleted fragment, which
         // hence no longer has a file associated with it.
         unset($mappings[$path]);
         continue;
       }
       $mappings[$path] = $files[$file_phid];
     }
 
     foreach ($mappings as $path => $file) {
       if ($file !== null) {
         $zip->addFromString($path, $file->loadFileData());
       }
     }
     $zip->close();
 
     $zip_name = $fragment->getName();
     if (substr($zip_name, -4) !== '.zip') {
       $zip_name .= '.zip';
     }
 
     $data = Filesystem::readFile((string)$temp);
     $file = PhabricatorFile::buildFromFileDataOrHash(
       $data,
       array(
         'name' => $zip_name,
         'ttl' => time() + 60 * 60 * 24,
       ));
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
       $file->attachToObject($fragment->getPHID());
     unset($unguarded);
 
     $return = $fragment->getURI();
     if ($request->getExists('return')) {
       $return = $request->getStr('return');
     }
 
     return id(new AphrontRedirectResponse())
       ->setIsExternal(true)
       ->setURI($file->getDownloadURI($return));
   }
 
   /**
    * Returns a list of mappings like array('some/path.txt' => 'file PHID');
    */
   private function getFragmentMappings(PhragmentFragment $current, $base_path) {
     $mappings = $current->getFragmentMappings(
       $this->getRequest()->getUser(),
       $base_path);
 
     $result = array();
     foreach ($mappings as $path => $fragment) {
       $version = $this->getVersion($fragment);
       if ($version !== null) {
         $result[$path] = $version->getFilePHID();
       }
     }
     return $result;
   }
 
   private function getVersion($fragment) {
     if ($this->snapshot === null) {
       return $fragment->getLatestVersion();
     } else {
       return idx($this->snapshotCache, $fragment->getPHID(), null);
     }
   }
 
 }
diff --git a/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php b/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php
index 876ed9d0d7..4d19812fe3 100644
--- a/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php
+++ b/src/applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php
@@ -1,291 +1,291 @@
 <?php
 
 final class PhabricatorProjectEditorTestCase extends PhabricatorTestCase {
 
   protected function getPhabricatorTestCaseConfiguration() {
     return array(
       self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
     );
   }
 
   public function testViewProject() {
     $user = $this->createUser();
     $user->save();
 
     $user2 = $this->createUser();
     $user2->save();
 
     $proj = $this->createProject($user);
 
     $proj = $this->refreshProject($proj, $user, true);
 
     $this->joinProject($proj, $user);
     $proj->setViewPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->save();
 
     $can_view = PhabricatorPolicyCapability::CAN_VIEW;
 
     // When the view policy is set to "users", any user can see the project.
     $this->assertTrue((bool)$this->refreshProject($proj, $user));
     $this->assertTrue((bool)$this->refreshProject($proj, $user2));
 
 
     // When the view policy is set to "no one", members can still see the
     // project.
     $proj->setViewPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $this->assertTrue((bool)$this->refreshProject($proj, $user));
     $this->assertFalse((bool)$this->refreshProject($proj, $user2));
   }
 
   public function testEditProject() {
     $user = $this->createUser();
     $user->save();
 
     $user2 = $this->createUser();
     $user2->save();
 
     $proj = $this->createProject($user);
 
 
     // When edit and view policies are set to "user", anyone can edit.
     $proj->setViewPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->save();
 
     $this->assertTrue($this->attemptProjectEdit($proj, $user));
 
 
     // When edit policy is set to "no one", no one can edit.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $caught = null;
     try {
       $this->attemptProjectEdit($proj, $user);
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue($caught instanceof Exception);
   }
 
   private function attemptProjectEdit(
     PhabricatorProject $proj,
     PhabricatorUser $user,
     $skip_refresh = false) {
 
     $proj = $this->refreshProject($proj, $user, true);
 
     $new_name = $proj->getName().' '.mt_rand();
 
     $xaction = new PhabricatorProjectTransaction();
     $xaction->setTransactionType(PhabricatorProjectTransaction::TYPE_NAME);
     $xaction->setNewValue($new_name);
 
     $editor = new PhabricatorProjectTransactionEditor();
     $editor->setActor($user);
     $editor->setContentSource(PhabricatorContentSource::newConsoleSource());
     $editor->applyTransactions($proj, array($xaction));
 
     return true;
   }
 
   public function testJoinLeaveProject() {
     $user = $this->createUser();
     $user->save();
 
     $proj = $this->createProjectWithNewAuthor();
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue(
       (bool)$proj,
       pht(
         'Assumption that projects are default visible '.
         'to any user when created.'));
 
     $this->assertFalse(
       $proj->isUserMember($user->getPHID()),
       pht('Arbitrary user not member of project.'));
 
     // Join the project.
     $this->joinProject($proj, $user);
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue((bool)$proj);
 
     $this->assertTrue(
       $proj->isUserMember($user->getPHID()),
       pht('Join works.'));
 
 
     // Join the project again.
     $this->joinProject($proj, $user);
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue((bool)$proj);
 
     $this->assertTrue(
       $proj->isUserMember($user->getPHID()),
       pht('Joining an already-joined project is a no-op.'));
 
 
     // Leave the project.
     $this->leaveProject($proj, $user);
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue((bool)$proj);
 
     $this->assertFalse(
       $proj->isUserMember($user->getPHID()),
       pht('Leave works.'));
 
 
     // Leave the project again.
     $this->leaveProject($proj, $user);
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue((bool)$proj);
 
     $this->assertFalse(
       $proj->isUserMember($user->getPHID()),
       pht('Leaving an already-left project is a no-op.'));
 
 
     // If a user can't edit or join a project, joining fails.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $proj = $this->refreshProject($proj, $user, true);
     $caught = null;
     try {
       $this->joinProject($proj, $user);
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue($ex instanceof Exception);
 
 
     // If a user can edit a project, they can join.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->joinProject($proj, $user);
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue(
       $proj->isUserMember($user->getPHID()),
       pht('Join allowed with edit permission.'));
     $this->leaveProject($proj, $user);
 
 
     // If a user can join a project, they can join, even if they can't edit.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->setJoinPolicy(PhabricatorPolicies::POLICY_USER);
     $proj->save();
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->joinProject($proj, $user);
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertTrue(
       $proj->isUserMember($user->getPHID()),
       pht('Join allowed with join permission.'));
 
 
     // A user can leave a project even if they can't edit it or join.
     $proj->setEditPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->setJoinPolicy(PhabricatorPolicies::POLICY_NOONE);
     $proj->save();
 
     $proj = $this->refreshProject($proj, $user, true);
     $this->leaveProject($proj, $user);
     $proj = $this->refreshProject($proj, $user, true);
     $this->assertFalse(
       $proj->isUserMember($user->getPHID()),
       pht('Leave allowed without any permission.'));
   }
 
   private function refreshProject(
     PhabricatorProject $project,
     PhabricatorUser $viewer,
     $need_members = false) {
 
     $results = id(new PhabricatorProjectQuery())
       ->setViewer($viewer)
       ->needMembers($need_members)
       ->withIDs(array($project->getID()))
       ->execute();
 
     if ($results) {
       return head($results);
     } else {
       return null;
     }
   }
 
   private function createProject(PhabricatorUser $user) {
     $project = PhabricatorProject::initializeNewProject($user);
-    $project->setName('Test Project '.mt_rand());
+    $project->setName(pht('Test Project %d', mt_rand()));
     $project->save();
 
     return $project;
   }
 
   private function createProjectWithNewAuthor() {
     $author = $this->createUser();
     $author->save();
 
     $project = $this->createProject($author);
 
     return $project;
   }
 
   private function createUser() {
     $rand = mt_rand();
 
     $user = new PhabricatorUser();
     $user->setUsername('unittestuser'.$rand);
-    $user->setRealName('Unit Test User '.$rand);
+    $user->setRealName(pht('Unit Test User %d', $rand));
 
     return $user;
   }
 
   private function joinProject(
     PhabricatorProject $project,
     PhabricatorUser $user) {
     $this->joinOrLeaveProject($project, $user, '+');
   }
 
   private function leaveProject(
     PhabricatorProject $project,
     PhabricatorUser $user) {
     $this->joinOrLeaveProject($project, $user, '-');
   }
 
   private function joinOrLeaveProject(
     PhabricatorProject $project,
     PhabricatorUser $user,
     $operation) {
 
     $spec = array(
       $operation => array($user->getPHID() => $user->getPHID()),
     );
 
     $xactions = array();
     $xactions[] = id(new PhabricatorProjectTransaction())
       ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
       ->setMetadataValue(
         'edge:type',
         PhabricatorProjectProjectHasMemberEdgeType::EDGECONST)
       ->setNewValue($spec);
 
     $editor = id(new PhabricatorProjectTransactionEditor())
       ->setActor($user)
       ->setContentSource(PhabricatorContentSource::newConsoleSource())
       ->setContinueOnNoEffect(true)
       ->applyTransactions($project, $xactions);
   }
 
 }
diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php
index d38f6fc5b3..bb8f02cfd4 100644
--- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php
+++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php
@@ -1,83 +1,83 @@
 <?php
 
 final class PhabricatorProjectDatasource
   extends PhabricatorTypeaheadDatasource {
 
   public function getBrowseTitle() {
     return pht('Browse Projects');
   }
 
   public function getPlaceholderText() {
     return pht('Type a project name...');
   }
 
   public function getDatasourceApplicationClass() {
     return 'PhabricatorProjectApplication';
   }
 
   public function loadResults() {
     $viewer = $this->getViewer();
 
     $raw_query = $this->getRawQuery();
 
     // Allow users to type "#qa" or "qa" to find "Quality Assurance".
     $raw_query = ltrim($raw_query, '#');
     $tokens = self::tokenizeString($raw_query);
 
     $query = id(new PhabricatorProjectQuery())
       ->needImages(true)
       ->needSlugs(true);
 
     if ($tokens) {
       $query->withNameTokens($tokens);
     }
 
     $projs = $this->executeQuery($query);
 
     $projs = mpull($projs, null, 'getPHID');
 
     $must_have_cols = $this->getParameter('mustHaveColumns', false);
     if ($must_have_cols) {
       $columns = id(new PhabricatorProjectColumnQuery())
         ->setViewer($viewer)
         ->withProjectPHIDs(array_keys($projs))
         ->execute();
       $has_cols = mgroup($columns, 'getProjectPHID');
     } else {
       $has_cols = array_fill_keys(array_keys($projs), true);
     }
 
     $results = array();
     foreach ($projs as $proj) {
       if (!isset($has_cols[$proj->getPHID()])) {
         continue;
       }
       $closed = null;
       if ($proj->isArchived()) {
         $closed = pht('Archived');
       }
 
       $all_strings = mpull($proj->getSlugs(), 'getSlug');
       $all_strings[] = $proj->getName();
       $all_strings = implode(' ', $all_strings);
 
       $proj_result = id(new PhabricatorTypeaheadResult())
         ->setName($all_strings)
         ->setDisplayName($proj->getName())
-        ->setDisplayType('Project')
+        ->setDisplayType(pht('Project'))
         ->setURI('/tag/'.$proj->getPrimarySlug().'/')
         ->setPHID($proj->getPHID())
         ->setIcon($proj->getIcon())
         ->setColor($proj->getColor())
         ->setPriorityType('proj')
         ->setClosed($closed);
 
       $proj_result->setImageURI($proj->getProfileImageURI());
 
       $results[] = $proj_result;
     }
 
     return $results;
   }
 
 }
diff --git a/src/applications/releeph/controller/request/ReleephRequestEditController.php b/src/applications/releeph/controller/request/ReleephRequestEditController.php
index 8888a01070..279fee4bd6 100644
--- a/src/applications/releeph/controller/request/ReleephRequestEditController.php
+++ b/src/applications/releeph/controller/request/ReleephRequestEditController.php
@@ -1,314 +1,314 @@
 <?php
 
 final class ReleephRequestEditController extends ReleephBranchController {
 
   private $requestID;
   private $branchID;
 
   public function willProcessRequest(array $data) {
     $this->requestID = idx($data, 'requestID');
     $this->branchID = idx($data, 'branchID');
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $viewer = $request->getUser();
 
     if ($this->requestID) {
       $pull = id(new ReleephRequestQuery())
         ->setViewer($viewer)
         ->withIDs(array($this->requestID))
         ->requireCapabilities(
           array(
             PhabricatorPolicyCapability::CAN_VIEW,
             PhabricatorPolicyCapability::CAN_EDIT,
           ))
         ->executeOne();
       if (!$pull) {
         return new Aphront404Response();
       }
 
       $branch = $pull->getBranch();
 
       $is_edit = true;
     } else {
       $branch = id(new ReleephBranchQuery())
         ->setViewer($viewer)
         ->withIDs(array($this->branchID))
         ->executeOne();
       if (!$branch) {
         return new Aphront404Response();
       }
 
       $pull = id(new ReleephRequest())
         ->setRequestUserPHID($viewer->getPHID())
         ->setBranchID($branch->getID())
         ->setInBranch(0)
         ->attachBranch($branch);
 
       $is_edit = false;
     }
     $this->setBranch($branch);
 
     $product = $branch->getProduct();
 
     $request_identifier = $request->getStr('requestIdentifierRaw');
     $e_request_identifier = true;
 
     // Load all the ReleephFieldSpecifications
     $selector = $branch->getProduct()->getReleephFieldSelector();
     $fields = $selector->getFieldSpecifications();
     foreach ($fields as $field) {
       $field
         ->setReleephProject($product)
         ->setReleephBranch($branch)
         ->setReleephRequest($pull);
     }
 
     $field_list = PhabricatorCustomField::getObjectFields(
       $pull,
       PhabricatorCustomField::ROLE_EDIT);
     foreach ($field_list->getFields() as $field) {
       $field
         ->setReleephProject($product)
         ->setReleephBranch($branch)
         ->setReleephRequest($pull);
     }
     $field_list->readFieldsFromStorage($pull);
 
 
     if ($this->branchID) {
       $cancel_uri = $this->getApplicationURI('branch/'.$this->branchID.'/');
     } else {
       $cancel_uri = '/'.$pull->getMonogram();
     }
 
     // Make edits
     $errors = array();
     if ($request->isFormPost()) {
       $xactions = array();
 
       // The commit-identifier being requested...
       if (!$is_edit) {
         if ($request_identifier ===
           ReleephRequestTypeaheadControl::PLACEHOLDER) {
 
           $errors[] = pht('No commit ID was provided.');
           $e_request_identifier = pht('Required');
         } else {
           $pr_commit = null;
           $finder = id(new ReleephCommitFinder())
             ->setUser($viewer)
             ->setReleephProject($product);
           try {
             $pr_commit = $finder->fromPartial($request_identifier);
           } catch (Exception $e) {
             $e_request_identifier = pht('Invalid');
             $errors[] = pht(
               'Request %s is probably not a valid commit.',
               $request_identifier);
             $errors[] = $e->getMessage();
           }
 
           if (!$errors) {
             $object_phid = $finder->getRequestedObjectPHID();
             if (!$object_phid) {
               $object_phid = $pr_commit->getPHID();
             }
 
             $pull->setRequestedObjectPHID($object_phid);
           }
         }
 
         if (!$errors) {
           $existing = id(new ReleephRequest())
             ->loadOneWhere('requestCommitPHID = %s AND branchID = %d',
                 $pr_commit->getPHID(), $branch->getID());
           if ($existing) {
             return id(new AphrontRedirectResponse())
               ->setURI('/releeph/request/edit/'.$existing->getID().
                 '?existing=1');
           }
 
           $xactions[] = id(new ReleephRequestTransaction())
             ->setTransactionType(ReleephRequestTransaction::TYPE_REQUEST)
             ->setNewValue($pr_commit->getPHID());
 
           $xactions[] = id(new ReleephRequestTransaction())
             ->setTransactionType(ReleephRequestTransaction::TYPE_USER_INTENT)
             // To help hide these implicit intents...
             ->setMetadataValue('isRQCreate', true)
             ->setMetadataValue('userPHID', $viewer->getPHID())
             ->setMetadataValue(
               'isAuthoritative',
               $product->isAuthoritative($viewer))
             ->setNewValue(ReleephRequest::INTENT_WANT);
         }
       }
 
       // TODO: This should happen implicitly while building transactions
       // instead.
       foreach ($field_list->getFields() as $field) {
         $field->readValueFromRequest($request);
       }
 
       if (!$errors) {
         foreach ($fields as $field) {
           if ($field->isEditable()) {
             try {
               $data = $request->getRequestData();
               $value = idx($data, $field->getRequiredStorageKey());
               $field->validate($value);
               $xactions[] = id(new ReleephRequestTransaction())
                 ->setTransactionType(ReleephRequestTransaction::TYPE_EDIT_FIELD)
                 ->setMetadataValue('fieldClass', get_class($field))
                 ->setNewValue($value);
             } catch (ReleephFieldParseException $ex) {
               $errors[] = $ex->getMessage();
             }
           }
         }
       }
 
       if (!$errors) {
         $editor = id(new ReleephRequestTransactionalEditor())
           ->setActor($viewer)
           ->setContinueOnNoEffect(true)
           ->setContentSourceFromRequest($request);
         $editor->applyTransactions($pull, $xactions);
         return id(new AphrontRedirectResponse())->setURI($cancel_uri);
       }
     }
 
     $handle_phids = array(
       $pull->getRequestUserPHID(),
       $pull->getRequestCommitPHID(),
     );
     $handle_phids = array_filter($handle_phids);
     if ($handle_phids) {
       $handles = id(new PhabricatorHandleQuery())
         ->setViewer($viewer)
         ->withPHIDs($handle_phids)
         ->execute();
     } else {
       $handles = array();
     }
 
     $age_string = '';
     if ($is_edit) {
       $age_string = phutil_format_relative_time(
         time() - $pull->getDateCreated()).' ago';
     }
 
     // Warn the user if we've been redirected here because we tried to
     // re-request something.
     $notice_view = null;
     if ($request->getInt('existing')) {
       $notice_messages = array(
         pht('You are editing an existing pick request!'),
         pht(
           'Requested %s by %s',
           $age_string,
           $handles[$pull->getRequestUserPHID()]->renderLink()),
       );
       $notice_view = id(new PHUIInfoView())
         ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
         ->setErrors($notice_messages);
     }
 
     $form = id(new AphrontFormView())
       ->setUser($viewer);
 
     if ($is_edit) {
       $form
         ->appendChild(
           id(new AphrontFormMarkupControl())
             ->setLabel(pht('Original Commit'))
             ->setValue(
               $handles[$pull->getRequestCommitPHID()]->renderLink()))
         ->appendChild(
           id(new AphrontFormMarkupControl())
             ->setLabel(pht('Requestor'))
             ->setValue(hsprintf(
               '%s %s',
               $handles[$pull->getRequestUserPHID()]->renderLink(),
               $age_string)));
     } else {
       $origin = null;
       $diff_rev_id = $request->getStr('D');
       if ($diff_rev_id) {
         $diff_rev = id(new DifferentialRevisionQuery())
           ->setViewer($viewer)
           ->withIDs(array($diff_rev_id))
           ->executeOne();
         $origin = '/D'.$diff_rev->getID();
         $title = sprintf(
           'D%d: %s',
           $diff_rev_id,
           $diff_rev->getTitle());
         $form
           ->addHiddenInput('requestIdentifierRaw', 'D'.$diff_rev_id)
           ->appendChild(
             id(new AphrontFormStaticControl())
-              ->setLabel('Diff')
+              ->setLabel(pht('Diff'))
               ->setValue($title));
       } else {
         $origin = $branch->getURI();
         $repo = $product->getRepository();
         $branch_cut_point = id(new PhabricatorRepositoryCommit())
           ->loadOneWhere(
               'phid = %s',
               $branch->getCutPointCommitPHID());
         $form->appendChild(
           id(new ReleephRequestTypeaheadControl())
             ->setName('requestIdentifierRaw')
             ->setLabel(pht('Commit ID'))
             ->setRepo($repo)
             ->setValue($request_identifier)
             ->setError($e_request_identifier)
             ->setStartTime($branch_cut_point->getEpoch())
             ->setCaption(
               pht(
                 'Start typing to autocomplete on commit title, '.
                 'or give a Phabricator commit identifier like rFOO1234.')));
       }
     }
 
     $field_list->appendFieldsToForm($form);
 
     $crumbs = $this->buildApplicationCrumbs();
 
     if ($is_edit) {
       $title = pht('Edit Pull Request');
       $submit_name = pht('Save');
 
       $crumbs->addTextCrumb($pull->getMonogram(), '/'.$pull->getMonogram());
       $crumbs->addTextCrumb(pht('Edit'));
     } else {
       $title = pht('Create Pull Request');
       $submit_name = pht('Create Pull Request');
 
       $crumbs->addTextCrumb(pht('New Pull Request'));
     }
 
     $form->appendChild(
       id(new AphrontFormSubmitControl())
         ->addCancelButton($cancel_uri, pht('Cancel'))
         ->setValue($submit_name));
 
     $box = id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->setFormErrors($errors)
       ->appendChild($form);
 
     return $this->buildApplicationPage(
       array(
         $crumbs,
         $notice_view,
         $box,
       ),
       array(
         'title' => $title,
       ));
   }
 }
diff --git a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php
index 33ae0acc96..3db133a3e0 100644
--- a/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php
+++ b/src/applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php
@@ -1,386 +1,386 @@
 <?php
 
 /**
  * This DifferentialFieldSpecification exists for two reason:
  *
  * 1: To parse "Releeph: picks RQ<nn>" headers in commits created by
  * arc-releeph so that RQs committed by arc-releeph have real
  * PhabricatorRepositoryCommits associated with them (instaed of just the SHA
  * of the commit, as seen by the pusher).
  *
  * 2: If requestors want to commit directly to their release branch, they can
  * use this header to (i) indicate on a differential revision that this
  * differential revision is for the release branch, and (ii) when they land
  * their diff on to the release branch manually, the ReleephRequest is
  * automatically updated (instead of having to use the "Mark Manually Picked"
  * button.)
  *
  */
 final class DifferentialReleephRequestFieldSpecification {
 
   // TODO: This class is essentially dead right now, see T2222.
 
   const ACTION_PICKS    = 'picks';
   const ACTION_REVERTS  = 'reverts';
 
   private $releephAction;
   private $releephPHIDs = array();
 
   public function getStorageKey() {
     return 'releeph:actions';
   }
 
   public function getValueForStorage() {
     return json_encode(array(
       'releephAction' => $this->releephAction,
       'releephPHIDs'  => $this->releephPHIDs,
     ));
   }
 
   public function setValueFromStorage($json) {
     if ($json) {
       $dict = phutil_json_decode($json);
       $this->releephAction = idx($dict, 'releephAction');
       $this->releephPHIDs = idx($dict, 'releephPHIDs');
     }
     return $this;
   }
 
   public function shouldAppearOnRevisionView() {
     return true;
   }
 
   public function renderLabelForRevisionView() {
-    return 'Releeph';
+    return pht('Releeph');
   }
 
   public function getRequiredHandlePHIDs() {
     return mpull($this->loadReleephRequests(), 'getPHID');
   }
 
   public function renderValueForRevisionView() {
     static $tense;
 
     if ($tense === null) {
       $tense = array(
         self::ACTION_PICKS => array(
           'future'  => pht('Will pick'),
           'past'    => pht('Picked'),
         ),
         self::ACTION_REVERTS => array(
           'future'  => pht('Will revert'),
           'past'    => pht('Reverted'),
         ),
       );
     }
 
     $releeph_requests = $this->loadReleephRequests();
     if (!$releeph_requests) {
       return null;
     }
 
     $status = $this->getRevision()->getStatus();
     if ($status == ArcanistDifferentialRevisionStatus::CLOSED) {
       $verb = $tense[$this->releephAction]['past'];
     } else {
       $verb = $tense[$this->releephAction]['future'];
     }
 
     $parts = hsprintf('%s...', $verb);
     foreach ($releeph_requests as $releeph_request) {
       $parts->appendHTML(phutil_tag('br'));
       $parts->appendHTML(
         $this->getHandle($releeph_request->getPHID())->renderLink());
     }
 
     return $parts;
   }
 
   public function shouldAppearOnCommitMessage() {
     return true;
   }
 
   public function getCommitMessageKey() {
     return 'releephActions';
   }
 
   public function setValueFromParsedCommitMessage($dict) {
     $this->releephAction = $dict['releephAction'];
     $this->releephPHIDs = $dict['releephPHIDs'];
     return $this;
   }
 
   public function renderValueForCommitMessage($is_edit) {
     $releeph_requests = $this->loadReleephRequests();
     if (!$releeph_requests) {
       return null;
     }
 
     $parts = array($this->releephAction);
     foreach ($releeph_requests as $releeph_request) {
       $parts[] = 'RQ'.$releeph_request->getID();
     }
 
     return implode(' ', $parts);
   }
 
   /**
    * Releeph fields should look like:
    *
    *   Releeph: picks RQ1 RQ2, RQ3
    *   Releeph: reverts RQ1
    */
   public function parseValueFromCommitMessage($value) {
     /**
      * Releeph commit messages look like this (but with more blank lines,
      * omitted here):
      *
      *   Make CaptainHaddock more reasonable
      *   Releeph: picks RQ1
      *   Requested By: edward
      *   Approved By: edward (requestor)
      *   Request Reason: x
      *   Summary: Make the Haddock implementation more reasonable.
      *   Test Plan: none
      *   Reviewers: user1
      *
      * Some of these fields are recognized by Differential (e.g. "Requested
      * By"). They are folded up into the "Releeph" field, parsed by this
      * class. As such $value includes more than just the first-line:
      *
      *   "picks RQ1\n\nRequested By: edward\n\nApproved By: edward (requestor)"
      *
      * To hack around this, just consider the first line of $value when
      * determining what Releeph actions the parsed commit is performing.
      */
     $first_line = head(array_filter(explode("\n", $value)));
 
     $tokens = preg_split('/\s*,?\s+/', $first_line);
     $raw_action = array_shift($tokens);
     $action = strtolower($raw_action);
 
     if (!$action) {
       return null;
     }
 
     switch ($action) {
       case self::ACTION_REVERTS:
       case self::ACTION_PICKS:
         break;
 
       default:
         throw new DifferentialFieldParseException(
           pht(
             "Commit message contains unknown Releeph action '%s'!",
             $raw_action));
         break;
     }
 
     $releeph_requests = array();
     foreach ($tokens as $token) {
       $match = array();
       if (!preg_match('/^(?:RQ)?(\d+)$/i', $token, $match)) {
         $label = $this->renderLabelForCommitMessage();
         throw new DifferentialFieldParseException(
           pht(
             "Commit message contains unparseable ".
             "Releeph request token '%s'!",
             $token));
       }
 
       $id = (int)$match[1];
       $releeph_request = id(new ReleephRequest())->load($id);
 
       if (!$releeph_request) {
         throw new DifferentialFieldParseException(
           pht(
             'Commit message references non existent Releeph request: %s!',
             $value));
       }
 
       $releeph_requests[] = $releeph_request;
     }
 
     if (count($releeph_requests) > 1) {
       $rqs_seen = array();
       $groups = array();
       foreach ($releeph_requests as $releeph_request) {
         $releeph_branch = $releeph_request->getBranch();
         $branch_name = $releeph_branch->getName();
         $rq_id = 'RQ'.$releeph_request->getID();
 
         if (idx($rqs_seen, $rq_id)) {
           throw new DifferentialFieldParseException(
             pht(
               'Commit message refers to %s multiple times!',
               $rq_id));
         }
         $rqs_seen[$rq_id] = true;
 
         if (!isset($groups[$branch_name])) {
           $groups[$branch_name] = array();
         }
         $groups[$branch_name][] = $rq_id;
       }
 
       if (count($groups) > 1) {
         $lists = array();
         foreach ($groups as $branch_name => $rq_ids) {
           $lists[] = implode(', ', $rq_ids).' in '.$branch_name;
         }
         throw new DifferentialFieldParseException(
           pht(
             'Commit message references multiple Releeph requests, '.
             'but the requests are in different branches: %s',
             implode('; ', $lists)));
       }
     }
 
     $phids = mpull($releeph_requests, 'getPHID');
 
     $data = array(
       'releephAction' => $action,
       'releephPHIDs'  => $phids,
     );
     return $data;
   }
 
   public function renderLabelForCommitMessage() {
-    return 'Releeph';
+    return pht('Releeph');
   }
 
   public function shouldAppearOnCommitMessageTemplate() {
     return false;
   }
 
   public function didParseCommit(
     PhabricatorRepository $repo,
     PhabricatorRepositoryCommit $commit,
     PhabricatorRepositoryCommitData $data) {
 
     // NOTE: This is currently dead code. See T2222.
 
     $releeph_requests = $this->loadReleephRequests();
 
     if (!$releeph_requests) {
       return;
     }
 
     $releeph_branch = head($releeph_requests)->getBranch();
     if (!$this->isCommitOnBranch($repo, $commit, $releeph_branch)) {
       return;
     }
 
     foreach ($releeph_requests as $releeph_request) {
       if ($this->releephAction === self::ACTION_PICKS) {
         $action = 'pick';
       } else {
         $action = 'revert';
       }
 
       $actor_phid = coalesce(
         $data->getCommitDetail('committerPHID'),
         $data->getCommitDetail('authorPHID'));
 
       $actor = id(new PhabricatorUser())
         ->loadOneWhere('phid = %s', $actor_phid);
 
       $xactions = array();
 
       $xactions[] = id(new ReleephRequestTransaction())
         ->setTransactionType(ReleephRequestTransaction::TYPE_DISCOVERY)
         ->setMetadataValue('action', $action)
         ->setMetadataValue('authorPHID',
           $data->getCommitDetail('authorPHID'))
         ->setMetadataValue('committerPHID',
           $data->getCommitDetail('committerPHID'))
         ->setNewValue($commit->getPHID());
 
       $editor = id(new ReleephRequestTransactionalEditor())
         ->setActor($actor)
         ->setContinueOnNoEffect(true)
         ->setContentSource(
           PhabricatorContentSource::newForSource(
             PhabricatorContentSource::SOURCE_UNKNOWN,
             array()));
 
       $editor->applyTransactions($releeph_request, $xactions);
     }
   }
 
   private function loadReleephRequests() {
     if (!$this->releephPHIDs) {
       return array();
     }
 
     return id(new ReleephRequestQuery())
       ->setViewer($this->getViewer())
       ->withPHIDs($this->releephPHIDs)
       ->execute();
   }
 
   private function isCommitOnBranch(
     PhabricatorRepository $repo,
     PhabricatorRepositoryCommit $commit,
     ReleephBranch $releeph_branch) {
 
     switch ($repo->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         list($output) = $repo->execxLocalCommand(
           'branch --all --no-color --contains %s',
           $commit->getCommitIdentifier());
 
         $remote_prefix = 'remotes/origin/';
         $branches = array();
         foreach (array_filter(explode("\n", $output)) as $line) {
           $tokens = explode(' ', $line);
           $ref = last($tokens);
           if (strncmp($ref, $remote_prefix, strlen($remote_prefix)) === 0) {
             $branch = substr($ref, strlen($remote_prefix));
             $branches[$branch] = $branch;
           }
         }
 
         return idx($branches, $releeph_branch->getName());
         break;
 
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
           DiffusionRequest::newFromDictionary(array(
             'user' => $this->getUser(),
             'repository' => $repo,
             'commit' => $commit->getCommitIdentifier(),
           )));
         $path_changes = $change_query->loadChanges();
         $commit_paths = mpull($path_changes, 'getPath');
 
         $branch_path = $releeph_branch->getName();
 
         $in_branch = array();
         $ex_branch = array();
         foreach ($commit_paths as $path) {
           if (strncmp($path, $branch_path, strlen($branch_path)) === 0) {
             $in_branch[] = $path;
           } else {
             $ex_branch[] = $path;
           }
         }
 
         if ($in_branch && $ex_branch) {
           $error = pht(
             'CONFUSION: commit %s in %s contains %d path change(s) that were '.
             'part of a Releeph branch, but also has %d path change(s) not '.
             'part of a Releeph branch!',
             $commit->getCommitIdentifier(),
             $repo->getCallsign(),
             count($in_branch),
             count($ex_branch));
           phlog($error);
         }
 
         return !empty($in_branch);
         break;
     }
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php b/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php
index b917b63ce6..577280fddb 100644
--- a/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephAuthorFieldSpecification.php
@@ -1,33 +1,33 @@
 <?php
 
 final class ReleephAuthorFieldSpecification
   extends ReleephFieldSpecification {
 
   public function getFieldKey() {
     return 'author';
   }
 
   public function getName() {
-    return 'Author';
+    return pht('Author');
   }
 
   public function getRequiredHandlePHIDsForPropertyView() {
     $pull = $this->getReleephRequest();
     $commit = $pull->loadPhabricatorRepositoryCommit();
     if (!$commit) {
       return array();
     }
 
     $author_phid = $commit->getAuthorPHID();
     if (!$author_phid) {
       return array();
     }
 
     return array($author_phid);
   }
 
   public function renderPropertyViewValue(array $handles) {
     return $this->renderHandleList($handles);
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php b/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php
index ea9c334bf9..74f747238d 100644
--- a/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php
@@ -1,28 +1,28 @@
 <?php
 
 final class ReleephBranchCommitFieldSpecification
   extends ReleephFieldSpecification {
 
   public function getFieldKey() {
     return 'commit';
   }
 
   public function getName() {
-    return 'Commit';
+    return pht('Commit');
   }
 
   public function getRequiredHandlePHIDsForPropertyView() {
     $pull = $this->getReleephRequest();
 
     if ($pull->getCommitPHID()) {
       return array($pull->getCommitPHID());
     }
 
     return array();
   }
 
   public function renderPropertyViewValue(array $handles) {
     return $this->renderHandleList($handles);
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php b/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php
index 98c578361c..cde4ab95dd 100644
--- a/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php
@@ -1,42 +1,42 @@
 <?php
 
 final class ReleephDiffMessageFieldSpecification
   extends ReleephFieldSpecification {
 
   public function getFieldKey() {
     return 'commit:message';
   }
 
   public function getName() {
-    return 'Message';
+    return pht('Message');
   }
 
   public function getStyleForPropertyView() {
     return 'block';
   }
 
   public function renderPropertyViewValue(array $handles) {
     return phutil_tag(
       'div',
       array(
         'class' => 'phabricator-remarkup',
       ),
       $this->getMarkupEngineOutput());
   }
 
   public function shouldMarkup() {
     return true;
   }
 
   public function getMarkupText($field) {
     $commit_data = $this
       ->getReleephRequest()
       ->loadPhabricatorRepositoryCommitData();
     if ($commit_data) {
       return $commit_data->getCommitMessage();
     } else {
       return '';
     }
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php b/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php
index 4d199f26cc..833dce0b39 100644
--- a/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephIntentFieldSpecification.php
@@ -1,142 +1,142 @@
 <?php
 
 final class ReleephIntentFieldSpecification
   extends ReleephFieldSpecification {
 
   public function getFieldKey() {
     return 'intent';
   }
 
   public function getName() {
-    return 'Intent';
+    return pht('Intent');
   }
 
   public function getRequiredHandlePHIDsForPropertyView() {
     $pull = $this->getReleephRequest();
     $intents = $pull->getUserIntents();
     return array_keys($intents);
   }
 
   public function renderPropertyViewValue(array $handles) {
     $pull = $this->getReleephRequest();
 
     $intents = $pull->getUserIntents();
     $product = $this->getReleephProject();
 
     if (!$intents) {
       return null;
     }
 
     $pushers = array();
     $others = array();
 
     foreach ($intents as $phid => $intent) {
       if ($product->isAuthoritativePHID($phid)) {
         $pushers[$phid] = $intent;
       } else {
         $others[$phid] = $intent;
       }
     }
 
     $intents = $pushers + $others;
 
     $view = id(new PHUIStatusListView());
     foreach ($intents as $phid => $intent) {
       switch ($intent) {
         case ReleephRequest::INTENT_WANT:
           $icon = PHUIStatusItemView::ICON_ACCEPT;
           $color = 'green';
           $label = pht('Want');
           break;
         case ReleephRequest::INTENT_PASS:
           $icon = PHUIStatusItemView::ICON_REJECT;
           $color = 'red';
           $label = pht('Pass');
           break;
         default:
           $icon = PHUIStatusItemView::ICON_QUESTION;
           $color = 'bluegrey';
           $label = pht('Unknown Intent (%s)', $intent);
           break;
       }
 
       $target = $handles[$phid]->renderLink();
       if ($product->isAuthoritativePHID($phid)) {
         $target = phutil_tag('strong', array(), $target);
       }
 
       $view->addItem(
         id(new PHUIStatusItemView())
           ->setIcon($icon, $color, $label)
           ->setTarget($target));
     }
 
     return $view;
   }
 
   public function shouldAppearOnCommitMessage() {
     return true;
   }
 
   public function shouldAppearOnRevertMessage() {
     return true;
   }
 
   public function renderLabelForCommitMessage() {
     return pht('Approved By');
   }
 
   public function renderLabelForRevertMessage() {
     return pht('Rejected By');
   }
 
   public function renderValueForCommitMessage() {
     return $this->renderIntentsForCommitMessage(ReleephRequest::INTENT_WANT);
   }
 
   public function renderValueForRevertMessage() {
     return $this->renderIntentsForCommitMessage(ReleephRequest::INTENT_PASS);
   }
 
   private function renderIntentsForCommitMessage($print_intent) {
     $intents = $this->getReleephRequest()->getUserIntents();
 
     $requestor = $this->getReleephRequest()->getRequestUserPHID();
     $pusher_phids = $this->getReleephProject()->getPushers();
 
     $phids = array_unique($pusher_phids + array_keys($intents));
     $handles = id(new PhabricatorHandleQuery())
       ->setViewer($this->getUser())
       ->withPHIDs($phids)
       ->execute();
 
     $tokens = array();
     foreach ($phids as $phid) {
       $intent = idx($intents, $phid);
       if ($intent == $print_intent) {
         $name = $handles[$phid]->getName();
         $is_pusher = in_array($phid, $pusher_phids);
         $is_requestor = $phid == $requestor;
 
         if ($is_pusher) {
           if ($is_requestor) {
             $token = pht('%s (pusher and requestor)', $name);
           } else {
             $token = "{$name} (pusher)";
           }
         } else {
           if ($is_requestor) {
             $token = pht('%s (requestor)', $name);
           } else {
             $token = $name;
           }
         }
 
         $tokens[] = $token;
       }
     }
 
     return implode(', ', $tokens);
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php b/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php
index a686eb15c4..7306067dc4 100644
--- a/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephLevelFieldSpecification.php
@@ -1,136 +1,136 @@
 <?php
 
 /**
  * Provides a convenient field for storing a set of levels that you can use to
  * filter requests on.
  *
  * Levels are rendered with names and descriptions in the edit UI, and are
  * automatically documented via the "arc request" interface.
  *
  * See ReleephSeverityFieldSpecification for an example.
  */
 abstract class ReleephLevelFieldSpecification
   extends ReleephFieldSpecification {
 
   private $error;
 
   abstract public function getLevels();
   abstract public function getDefaultLevel();
   abstract public function getNameForLevel($level);
   abstract public function getDescriptionForLevel($level);
 
   public function getStorageKey() {
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
   public function renderPropertyViewValue(array $handles) {
     return $this->getNameForLevel($this->getValue());
   }
 
   public function renderEditControl(array $handles) {
     $control_name = $this->getRequiredStorageKey();
     $all_levels = $this->getLevels();
 
     $level = $this->getValue();
     if (!$level) {
       $level = $this->getDefaultLevel();
     }
 
     $control = id(new AphrontFormRadioButtonControl())
-      ->setLabel('Level')
+      ->setLabel(pht('Level'))
       ->setName($control_name)
       ->setValue($level);
 
     if ($this->error) {
       $control->setError($this->error);
     } else if ($this->getDefaultLevel()) {
       $control->setError(true);
     }
 
     foreach ($all_levels as $level) {
       $name = $this->getNameForLevel($level);
       $description = $this->getDescriptionForLevel($level);
       $control->addButton($level, $name, $description);
     }
 
     return $control;
   }
 
   public function renderHelpForArcanist() {
     $text = '';
     $levels = $this->getLevels();
     $default = $this->getDefaultLevel();
     foreach ($levels as $level) {
       $name = $this->getNameForLevel($level);
       $description = $this->getDescriptionForLevel($level);
       $default_marker = ' ';
       if ($level === $default) {
         $default_marker = '*';
       }
       $text .= "    {$default_marker} **{$name}**\n";
       $text .= phutil_console_wrap($description."\n", 8);
     }
     return $text;
   }
 
   public function validate($value) {
     if ($value === null) {
-      $this->error = 'Required';
+      $this->error = pht('Required');
       $label = $this->getName();
       throw new ReleephFieldParseException(
         $this,
         pht('You must provide a %s level.', $label));
     }
 
     $levels = $this->getLevels();
     if (!in_array($value, $levels)) {
       $label = $this->getName();
       throw new ReleephFieldParseException(
         $this,
         pht(
           "Level '%s' is not a valid %s level in this project.",
           $value,
           $label));
     }
   }
 
   public function setValueFromConduitAPIRequest(ConduitAPIRequest $request) {
     $key = $this->getRequiredStorageKey();
     $label = $this->getName();
     $name = idx($request->getValue('fields', array()), $key);
 
     if (!$name) {
       $level = $this->getDefaultLevel();
       if (!$level) {
         throw new ReleephFieldParseException(
           $this,
           pht(
             'No value given for %s, and no default is given for this level!',
             $label));
       }
     } else {
       $level = $this->getLevelByName($name);
     }
 
     if (!$level) {
       throw new ReleephFieldParseException(
         $this,
         pht("Unknown %s level name '%s'", $label, $name));
     }
     $this->setValue($level);
   }
 
   private $nameMap = array();
 
   public function getLevelByName($name) {
     // Build this once
     if (!$this->nameMap) {
       foreach ($this->getLevels() as $level) {
         $level_name = $this->getNameForLevel($level);
         $this->nameMap[$level_name] = $level;
       }
     }
     return idx($this->nameMap, $name);
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php b/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php
index 0d34c34eb7..57c4209ca7 100644
--- a/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php
@@ -1,25 +1,25 @@
 <?php
 
 final class ReleephOriginalCommitFieldSpecification
   extends ReleephFieldSpecification {
 
   public function getFieldKey() {
     return 'commit:name';
   }
 
   public function getName() {
-    return 'Commit';
+    return pht('Commit');
   }
 
   public function getRequiredHandlePHIDsForPropertyView() {
     return array(
       $this->getReleephRequest()->getRequestCommitPHID(),
     );
   }
 
 
   public function renderPropertyViewValue(array $handles) {
     return $this->renderHandleList($handles);
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php b/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php
index 831e1d60a3..023ea081a1 100644
--- a/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephReasonFieldSpecification.php
@@ -1,86 +1,86 @@
 <?php
 
 final class ReleephReasonFieldSpecification
   extends ReleephFieldSpecification {
 
   public function getFieldKey() {
     return 'reason';
   }
 
   public function getName() {
     return pht('Reason');
   }
 
   public function getStorageKey() {
     return 'reason';
   }
 
   public function getStyleForPropertyView() {
     return 'block';
   }
 
   public function getIconForPropertyView() {
     return PHUIPropertyListView::ICON_SUMMARY;
   }
 
   public function renderPropertyViewValue(array $handles) {
     return phutil_tag(
       'div',
       array(
         'class' => 'phabricator-remarkup',
       ),
       $this->getMarkupEngineOutput());
   }
 
   private $error = true;
 
   public function renderEditControl(array $handles) {
     return id(new AphrontFormTextAreaControl())
       ->setLabel(pht('Reason'))
       ->setName('reason')
       ->setError($this->error)
       ->setValue($this->getValue());
   }
 
   public function validate($reason) {
     if (!$reason) {
-      $this->error = 'Required';
+      $this->error = pht('Required');
       throw new ReleephFieldParseException(
         $this,
         pht('You must give a reason for your request.'));
     }
   }
 
   public function renderHelpForArcanist() {
     $text = pht(
-      "Fully explain why you are requesting this code be included ".
-      "in the next release.\n");
+      'Fully explain why you are requesting this code be included '.
+      'in the next release.')."\n";
     return phutil_console_wrap($text, 8);
   }
 
   public function shouldAppearOnCommitMessage() {
     return true;
   }
 
   public function renderLabelForCommitMessage() {
     return pht('Request Reason');
   }
 
   public function renderValueForCommitMessage() {
     return $this->getValue();
   }
 
   public function shouldMarkup() {
     return true;
   }
 
   public function getMarkupText($field) {
     $reason = $this->getValue();
     if ($reason) {
       return $reason;
     } else {
       return '';
     }
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php b/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php
index 18beb35a10..c73a17e149 100644
--- a/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephSeverityFieldSpecification.php
@@ -1,53 +1,53 @@
 <?php
 
 final class ReleephSeverityFieldSpecification
   extends ReleephLevelFieldSpecification {
 
   const HOTFIX  = 'HOTFIX';
   const RELEASE = 'RELEASE';
 
   public function getFieldKey() {
     return 'severity';
   }
 
   public function getName() {
-    return 'Severity';
+    return pht('Severity');
   }
 
   public function getStorageKey() {
     return 'releeph:severity';
   }
 
   public function getLevels() {
     return array(
       self::HOTFIX,
       self::RELEASE,
     );
   }
 
   public function getDefaultLevel() {
     return self::RELEASE;
   }
 
   public function getNameForLevel($level) {
     static $names = array(
       self::HOTFIX  => 'HOTFIX',
       self::RELEASE => 'RELEASE',
     );
     return idx($names, $level, $level);
   }
 
   public function getDescriptionForLevel($level) {
     static $descriptions;
 
     if ($descriptions === null) {
       $descriptions = array(
         self::HOTFIX => pht('Needs merging and fixing right now.'),
         self::RELEASE => pht('Required for the currently rolling release.'),
       );
     }
 
     return idx($descriptions, $level);
   }
 
 }
diff --git a/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php b/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php
index 95063d2947..2341b0d2b5 100644
--- a/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php
+++ b/src/applications/releeph/field/specification/ReleephSummaryFieldSpecification.php
@@ -1,53 +1,53 @@
 <?php
 
 final class ReleephSummaryFieldSpecification
   extends ReleephFieldSpecification {
 
   const MAX_SUMMARY_LENGTH = 60;
 
   public function shouldAppearInPropertyView() {
     return false;
   }
 
   public function getFieldKey() {
     return 'summary';
   }
 
   public function getName() {
-    return 'Summary';
+    return pht('Summary');
   }
 
   public function getStorageKey() {
     return 'summary';
   }
 
   private $error = false;
 
   public function renderEditControl(array $handles) {
     return id(new AphrontFormTextControl())
-      ->setLabel('Summary')
+      ->setLabel(pht('Summary'))
       ->setName('summary')
       ->setError($this->error)
       ->setValue($this->getValue())
       ->setCaption(pht('Leave this blank to use the original commit title'));
   }
 
   public function renderHelpForArcanist() {
     $text = pht(
       'A one-line title summarizing this request. '.
       'Leave blank to use the original commit title.')."\n";
     return phutil_console_wrap($text, 8);
   }
 
   public function validate($summary) {
     if ($summary && strlen($summary) > self::MAX_SUMMARY_LENGTH) {
       $this->error = pht('Too long!');
       throw new ReleephFieldParseException(
         $this,
         pht(
           'Please keep your summary to under %d characters.',
           self::MAX_SUMMARY_LENGTH));
     }
   }
 
 }
diff --git a/src/applications/releeph/query/ReleephRequestSearchEngine.php b/src/applications/releeph/query/ReleephRequestSearchEngine.php
index 07e5b06dfb..1a07ec478d 100644
--- a/src/applications/releeph/query/ReleephRequestSearchEngine.php
+++ b/src/applications/releeph/query/ReleephRequestSearchEngine.php
@@ -1,175 +1,175 @@
 <?php
 
 final class ReleephRequestSearchEngine
   extends PhabricatorApplicationSearchEngine {
 
   private $branch;
   private $baseURI;
 
   public function getResultTypeDescription() {
     return pht('Releeph Pull Requests');
   }
 
   public function getApplicationClassName() {
     return 'PhabricatorReleephApplication';
   }
 
   public function setBranch(ReleephBranch $branch) {
     $this->branch = $branch;
     return $this;
   }
 
   public function getBranch() {
     return $this->branch;
   }
 
   public function setBaseURI($base_uri) {
     $this->baseURI = $base_uri;
     return $this;
   }
 
   public function buildSavedQueryFromRequest(AphrontRequest $request) {
     $saved = new PhabricatorSavedQuery();
 
     $saved->setParameter('status', $request->getStr('status'));
     $saved->setParameter('severity', $request->getStr('severity'));
     $saved->setParameter(
       'requestorPHIDs',
       $this->readUsersFromRequest($request, 'requestors'));
 
     return $saved;
   }
 
   public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
     $query = id(new ReleephRequestQuery())
       ->withBranchIDs(array($this->getBranch()->getID()));
 
     $status = $saved->getParameter('status');
     $status = idx($this->getStatusValues(), $status);
     if ($status) {
       $query->withStatus($status);
     }
 
     $severity = $saved->getParameter('severity');
     if ($severity) {
       $query->withSeverities(array($severity));
     }
 
     $requestor_phids = $saved->getParameter('requestorPHIDs');
     if ($requestor_phids) {
       $query->withRequestorPHIDs($requestor_phids);
     }
 
     return $query;
   }
 
   public function buildSearchForm(
     AphrontFormView $form,
     PhabricatorSavedQuery $saved_query) {
 
     $requestor_phids = $saved_query->getParameter('requestorPHIDs', array());
 
     $form
       ->appendChild(
         id(new AphrontFormSelectControl())
           ->setName('status')
           ->setLabel(pht('Status'))
           ->setValue($saved_query->getParameter('status'))
           ->setOptions($this->getStatusOptions()))
       ->appendChild(
         id(new AphrontFormSelectControl())
           ->setName('severity')
           ->setLabel(pht('Severity'))
           ->setValue($saved_query->getParameter('severity'))
           ->setOptions($this->getSeverityOptions()))
       ->appendControl(
         id(new AphrontFormTokenizerControl())
           ->setDatasource(new PhabricatorPeopleDatasource())
           ->setName('requestors')
           ->setLabel(pht('Requestors'))
           ->setValue($requestor_phids));
   }
 
   protected function getURI($path) {
     return $this->baseURI.$path;
   }
 
   protected function getBuiltinQueryNames() {
     $names = array(
       'open' => pht('Open Requests'),
       'all' => pht('All Requests'),
     );
 
     if ($this->requireViewer()->isLoggedIn()) {
       $names['requested'] = pht('Requested');
     }
 
     return $names;
   }
 
   public function buildSavedQueryFromBuiltin($query_key) {
 
     $query = $this->newSavedQuery();
     $query->setQueryKey($query_key);
 
     switch ($query_key) {
       case 'open':
         return $query->setParameter('status', 'open');
       case 'all':
         return $query;
       case 'requested':
         return $query->setParameter(
           'requestorPHIDs',
           array($this->requireViewer()->getPHID()));
     }
 
     return parent::buildSavedQueryFromBuiltin($query_key);
   }
 
   private function getStatusOptions() {
     return array(
       ''              => pht('(All Requests)'),
       'open'          => pht('Open Requests'),
       'requested'     => pht('Pull Requested'),
       'needs-pull'    => pht('Needs Pull'),
       'rejected'      => pht('Rejected'),
       'abandoned'     => pht('Abandoned'),
       'pulled'        => pht('Pulled'),
       'needs-revert'  => pht('Needs Revert'),
       'reverted'      => pht('Reverted'),
     );
   }
 
   private function getStatusValues() {
     return array(
       'open'          => ReleephRequestQuery::STATUS_OPEN,
       'requested'     => ReleephRequestQuery::STATUS_REQUESTED,
       'needs-pull'    => ReleephRequestQuery::STATUS_NEEDS_PULL,
       'rejected'      => ReleephRequestQuery::STATUS_REJECTED,
       'abandoned'     => ReleephRequestQuery::STATUS_ABANDONED,
       'pulled'        => ReleephRequestQuery::STATUS_PULLED,
       'needs-revert'  => ReleephRequestQuery::STATUS_NEEDS_REVERT,
       'reverted'      => ReleephRequestQuery::STATUS_REVERTED,
     );
   }
 
   private function getSeverityOptions() {
     if (ReleephDefaultFieldSelector::isFacebook()) {
       return array(
         '' => pht('(All Severities)'),
-        11 => 'HOTFIX',
-        12 => 'PIGGYBACK',
-        13 => 'RELEASE',
-        14 => 'DAILY',
-        15 => 'PARKING',
+        11 => pht('HOTFIX'),
+        12 => pht('PIGGYBACK'),
+        13 => pht('RELEASE'),
+        14 => pht('DAILY'),
+        15 => pht('PARKING'),
       );
     } else {
       return array(
         '' => pht('(All Severities)'),
         ReleephSeverityFieldSpecification::HOTFIX => pht('Hotfix'),
         ReleephSeverityFieldSpecification::RELEASE => pht('Release'),
       );
     }
   }
 
 }
diff --git a/src/applications/repository/constants/PhabricatorRepositoryType.php b/src/applications/repository/constants/PhabricatorRepositoryType.php
index 78e3a7e8ec..a99c76d889 100644
--- a/src/applications/repository/constants/PhabricatorRepositoryType.php
+++ b/src/applications/repository/constants/PhabricatorRepositoryType.php
@@ -1,24 +1,24 @@
 <?php
 
 final class PhabricatorRepositoryType {
 
   const REPOSITORY_TYPE_GIT         = 'git';
   const REPOSITORY_TYPE_SVN         = 'svn';
   const REPOSITORY_TYPE_MERCURIAL   = 'hg';
   const REPOSITORY_TYPE_PERFORCE    = 'p4';
 
   public static function getAllRepositoryTypes() {
     static $map = array(
       self::REPOSITORY_TYPE_GIT       => 'Git',
       self::REPOSITORY_TYPE_SVN       => 'Subversion',
       self::REPOSITORY_TYPE_MERCURIAL => 'Mercurial',
     );
     return $map;
   }
 
   public static function getNameForRepositoryType($type) {
     $map = self::getAllRepositoryTypes();
-    return idx($map, $type, 'Unknown');
+    return idx($map, $type, pht('Unknown'));
   }
 
 }
diff --git a/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php b/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php
index 4d70d3b9e5..33f947e2f8 100644
--- a/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php
+++ b/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php
@@ -1,158 +1,161 @@
 <?php
 
 /**
  * Streaming interface on top of "hg log" that gives us performant access to
  * the Mercurial commit graph with one nonblocking invocation of "hg". See
  * @{class:PhabricatorRepositoryPullLocalDaemon}.
  */
 final class PhabricatorMercurialGraphStream
   extends PhabricatorRepositoryGraphStream {
 
   private $repository;
   private $iterator;
 
   private $parents        = array();
   private $dates          = array();
   private $local          = array();
   private $localParents   = array();
 
   public function __construct(PhabricatorRepository $repository, $commit) {
     $this->repository = $repository;
 
     $future = $repository->getLocalCommandFuture(
       'log --template %s --rev %s',
       '{rev}\1{node}\1{date}\1{parents}\2',
       hgsprintf('reverse(ancestors(%s))', $commit));
 
     $this->iterator = new LinesOfALargeExecFuture($future);
     $this->iterator->setDelimiter("\2");
     $this->iterator->rewind();
   }
 
   public function getParents($commit) {
     if (!isset($this->parents[$commit])) {
       $this->parseUntil('node', $commit);
 
       $local = $this->localParents[$commit];
 
       // The normal parsing pass gives us the local revision numbers of the
       // parents, but since we've decided we care about this data, we need to
       // convert them into full hashes. To do this, we parse to the deepest
       // one and then just look them up.
 
       $parents = array();
       if ($local) {
         $this->parseUntil('rev', min($local));
         foreach ($local as $rev) {
           $parents[] = $this->local[$rev];
         }
       }
 
       $this->parents[$commit] = $parents;
 
       // Throw away the local info for this commit, we no longer need it.
       unset($this->localParents[$commit]);
     }
 
     return $this->parents[$commit];
   }
 
   public function getCommitDate($commit) {
     if (!isset($this->dates[$commit])) {
       $this->parseUntil('node', $commit);
     }
     return $this->dates[$commit];
   }
 
   /**
    * Parse until we have consumed some object. There are two types of parses:
    * parse until we find a commit hash ($until_type = "node"), or parse until we
    * find a local commit number ($until_type = "rev"). We use the former when
    * looking up commits, and the latter when resolving parents.
    */
   private function parseUntil($until_type, $until_name) {
     if ($this->isParsed($until_type, $until_name)) {
       return;
     }
 
     $hglog = $this->iterator;
 
     while ($hglog->valid()) {
       $line = $hglog->current();
       $hglog->next();
 
       $line = trim($line);
       if (!strlen($line)) {
         break;
       }
       list($rev, $node, $date, $parents) = explode("\1", $line);
 
       $rev  = (int)$rev;
       $date = (int)head(explode('.', $date));
 
       $this->dates[$node]        = $date;
       $this->local[$rev]         = $node;
       $this->localParents[$node] = $this->parseParents($parents, $rev);
 
       if ($this->isParsed($until_type, $until_name)) {
         return;
       }
     }
 
     throw new Exception(
-      "No such {$until_type} '{$until_name}' in repository!");
+      pht(
+        "No such %s '%s' in repository!",
+        $until_type,
+        $until_name));
   }
 
 
   /**
    * Parse a {parents} template, returning the local commit numbers.
    */
   private function parseParents($parents, $target_rev) {
 
     // The hg '{parents}' token is empty if there is one "natural" parent
     // (predecessor local commit ID). Othwerwise, it may have one or two
     // parents. The string looks like this:
     //
     //  151:1f6c61a60586 154:1d5f799ebe1e
 
     $parents = trim($parents);
     if (strlen($parents)) {
       $local = array();
 
       $parents = explode(' ', $parents);
       foreach ($parents as $key => $parent) {
         $parent = (int)head(explode(':', $parent));
         if ($parent == -1) {
           // Initial commits will sometimes have "-1" as a parent.
           continue;
         }
         $local[] = $parent;
       }
     } else if ($target_rev) {
       // We have empty parents. If there's a predecessor, that's the local
       // parent number.
       $local = array($target_rev - 1);
     } else {
       // Initial commits will sometimes have no parents.
       $local = array();
     }
 
     return $local;
   }
 
 
   /**
    * Returns true if the object specified by $type ('rev' or 'node') and
    * $name (rev or node name) has been consumed from the hg process.
    */
   private function isParsed($type, $name) {
     switch ($type) {
       case 'rev':
         return isset($this->local[$name]);
       case 'node':
         return isset($this->dates[$name]);
     }
   }
 
 
 }
diff --git a/src/applications/repository/engine/PhabricatorRepositoryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryEngine.php
index 8d858a3d89..d5982473c5 100644
--- a/src/applications/repository/engine/PhabricatorRepositoryEngine.php
+++ b/src/applications/repository/engine/PhabricatorRepositoryEngine.php
@@ -1,162 +1,165 @@
 <?php
 
 /**
  * @task config     Configuring Repository Engines
  * @task internal   Internals
  */
 abstract class PhabricatorRepositoryEngine {
 
   private $repository;
   private $verbose;
 
   /**
    * @task config
    */
   public function setRepository(PhabricatorRepository $repository) {
     $this->repository = $repository;
     return $this;
   }
 
 
   /**
    * @task config
    */
   protected function getRepository() {
     if ($this->repository === null) {
       throw new PhutilInvalidStateException('setRepository');
     }
 
     return $this->repository;
   }
 
 
   /**
    * @task config
    */
   public function setVerbose($verbose) {
     $this->verbose = $verbose;
     return $this;
   }
 
 
   /**
    * @task config
    */
   public function getVerbose() {
     return $this->verbose;
   }
 
 
   public function getViewer() {
     return PhabricatorUser::getOmnipotentUser();
   }
 
   /**
    * Verify that the "origin" remote exists, and points at the correct URI.
    *
    * This catches or corrects some types of misconfiguration, and also repairs
    * an issue where Git 1.7.1 does not create an "origin" for `--bare` clones.
    * See T4041.
    *
    * @param   PhabricatorRepository Repository to verify.
    * @return  void
    */
   protected function verifyGitOrigin(PhabricatorRepository $repository) {
     list($remotes) = $repository->execxLocalCommand(
       'remote show -n origin');
 
     $matches = null;
     if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) {
       throw new Exception(
-        "Expected 'Fetch URL' in 'git remote show -n origin'.");
+        pht(
+          "Expected '%s' in '%s'.",
+          'Fetch URL',
+          'git remote show -n origin'));
     }
 
     $remote_uri = $matches[1];
     $expect_remote = $repository->getRemoteURI();
 
     if ($remote_uri == 'origin') {
       // If a remote does not exist, git pretends it does and prints out a
       // made up remote where the URI is the same as the remote name. This is
       // definitely not correct.
 
       // Possibly, we should use `git remote --verbose` instead, which does not
       // suffer from this problem (but is a little more complicated to parse).
       $valid = false;
       $exists = false;
     } else {
       $normal_type_git = PhabricatorRepositoryURINormalizer::TYPE_GIT;
 
       $remote_normal = id(new PhabricatorRepositoryURINormalizer(
         $normal_type_git,
         $remote_uri))->getNormalizedPath();
 
       $expect_normal = id(new PhabricatorRepositoryURINormalizer(
         $normal_type_git,
         $expect_remote))->getNormalizedPath();
 
       $valid = ($remote_normal == $expect_normal);
       $exists = true;
     }
 
     if (!$valid) {
       if (!$exists) {
         // If there's no "origin" remote, just create it regardless of how
         // strongly we own the working copy. There is almost no conceivable
         // scenario in which this could do damage.
         $this->log(
           pht(
             'Remote "origin" does not exist. Creating "origin", with '.
             'URI "%s".',
             $expect_remote));
         $repository->execxLocalCommand(
           'remote add origin %P',
           $repository->getRemoteURIEnvelope());
 
         // NOTE: This doesn't fetch the origin (it just creates it), so we won't
         // know about origin branches until the next "pull" happens. That's fine
         // for our purposes, but might impact things in the future.
       } else {
         if ($repository->canDestroyWorkingCopy()) {
           // Bad remote, but we can try to repair it.
           $this->log(
             pht(
               'Remote "origin" exists, but is pointed at the wrong URI, "%s". '.
               'Resetting origin URI to "%s.',
               $remote_uri,
               $expect_remote));
           $repository->execxLocalCommand(
             'remote set-url origin %P',
             $repository->getRemoteURIEnvelope());
         } else {
           // Bad remote and we aren't comfortable repairing it.
           $message = pht(
             'Working copy at "%s" has a mismatched origin URI, "%s". '.
             'The expected origin URI is "%s". Fix your configuration, or '.
             'set the remote URI correctly. To avoid breaking anything, '.
             'Phabricator will not automatically fix this.',
             $repository->getLocalPath(),
             $remote_uri,
             $expect_remote);
           throw new Exception($message);
         }
       }
     }
   }
 
 
 
 
   /**
    * @task internal
    */
   protected function log($pattern /* ... */) {
     if ($this->getVerbose()) {
       $console = PhutilConsole::getConsole();
       $argv = func_get_args();
       array_unshift($argv, "%s\n");
       call_user_func_array(array($console, 'writeOut'), $argv);
     }
     return $this;
   }
 
 }
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php
index 7fabffe7bd..4da1e03c9b 100644
--- a/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php
@@ -1,89 +1,89 @@
 <?php
 
 final class PhabricatorRepositoryManagementImportingWorkflow
   extends PhabricatorRepositoryManagementWorkflow {
 
   protected function didConstruct() {
     $this
       ->setName('importing')
       ->setExamples('**importing** __repository__ ...')
       ->setSynopsis(
         pht(
           'Show commits in __repository__, named by callsign, which are '.
           'still importing.'))
       ->setArguments(
         array(
           array(
             'name'        => 'simple',
             'help'        => pht('Show simpler output.'),
           ),
           array(
             'name'        => 'repos',
             'wildcard'    => true,
           ),
         ));
   }
 
   public function execute(PhutilArgumentParser $args) {
     $repos = $this->loadRepositories($args, 'repos');
 
     if (!$repos) {
       throw new PhutilArgumentUsageException(
         pht(
           'Specify one or more repositories to find importing commits for, '.
           'by callsign.'));
     }
 
     $repos = mpull($repos, null, 'getID');
 
     $table = new PhabricatorRepositoryCommit();
     $conn_r = $table->establishConnection('r');
 
     $rows = queryfx_all(
       $conn_r,
       'SELECT repositoryID, commitIdentifier, importStatus FROM %T
         WHERE repositoryID IN (%Ld) AND (importStatus & %d) != %d',
       $table->getTableName(),
       array_keys($repos),
       PhabricatorRepositoryCommit::IMPORTED_ALL,
       PhabricatorRepositoryCommit::IMPORTED_ALL);
 
     $console = PhutilConsole::getConsole();
     if ($rows) {
       foreach ($rows as $row) {
         $repo = $repos[$row['repositoryID']];
         $identifier = $row['commitIdentifier'];
 
         $console->writeOut('%s', 'r'.$repo->getCallsign().$identifier);
 
         if (!$args->getArg('simple')) {
           $status = $row['importStatus'];
           $need = array();
           if (!($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE)) {
-            $need[] = 'Message';
+            $need[] = pht('Message');
           }
           if (!($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE)) {
-            $need[] = 'Change';
+            $need[] = pht('Change');
           }
           if (!($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS)) {
-            $need[] = 'Owners';
+            $need[] = pht('Owners');
           }
           if (!($status & PhabricatorRepositoryCommit::IMPORTED_HERALD)) {
-            $need[] = 'Herald';
+            $need[] = pht('Herald');
           }
 
           $console->writeOut(' %s', implode(', ', $need));
         }
 
         $console->writeOut("\n");
       }
     } else {
       $console->writeErr(
         "%s\n",
         pht('No importing commits found.'));
     }
 
     return 0;
   }
 
 }
diff --git a/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php b/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php
index 4d0384fe93..d3535102de 100644
--- a/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php
+++ b/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php
@@ -1,108 +1,108 @@
 <?php
 
 final class PhabricatorRepositoryURITestCase
   extends PhabricatorTestCase {
 
   protected function getPhabricatorTestCaseConfiguration() {
     return array(
       self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
     );
   }
 
   public function testURIGeneration() {
     $svn = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
     $git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
     $hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
 
     $user = $this->generateNewTestUser();
 
     $http_secret = id(new PassphraseSecret())->setSecretData('quack')->save();
 
     $http_credential = PassphraseCredential::initializeNewCredential($user)
       ->setCredentialType(PassphraseCredentialTypePassword::CREDENTIAL_TYPE)
       ->setProvidesType(PassphraseCredentialTypePassword::PROVIDES_TYPE)
       ->setUsername('duck')
       ->setSecretID($http_secret->getID())
       ->save();
 
     $repo = PhabricatorRepository::initializeNewRepository($user)
       ->setVersionControlSystem($svn)
-      ->setName('Test Repo')
+      ->setName(pht('Test Repo'))
       ->setCallsign('TESTREPO')
       ->setCredentialPHID($http_credential->getPHID())
       ->save();
 
     // Test HTTP URIs.
 
     $repo->setDetail('remote-uri', 'http://example.com/');
     $repo->setVersionControlSystem($svn);
 
     $this->assertEqual('http://example.com/', $repo->getRemoteURI());
     $this->assertEqual('http://example.com/', $repo->getPublicCloneURI());
     $this->assertEqual('http://example.com/',
       $repo->getRemoteURIEnvelope()->openEnvelope());
 
     $repo->setVersionControlSystem($git);
 
     $this->assertEqual('http://example.com/', $repo->getRemoteURI());
     $this->assertEqual('http://example.com/', $repo->getPublicCloneURI());
     $this->assertEqual('http://duck:quack@example.com/',
       $repo->getRemoteURIEnvelope()->openEnvelope());
 
     $repo->setVersionControlSystem($hg);
 
     $this->assertEqual('http://example.com/', $repo->getRemoteURI());
     $this->assertEqual('http://example.com/', $repo->getPublicCloneURI());
     $this->assertEqual('http://duck:quack@example.com/',
       $repo->getRemoteURIEnvelope()->openEnvelope());
 
     // Test SSH URIs.
 
     $repo->setDetail('remote-uri', 'ssh://example.com/');
     $repo->setVersionControlSystem($svn);
 
     $this->assertEqual('ssh://example.com/', $repo->getRemoteURI());
     $this->assertEqual('ssh://example.com/', $repo->getPublicCloneURI());
     $this->assertEqual('ssh://example.com/',
       $repo->getRemoteURIEnvelope()->openEnvelope());
 
     $repo->setVersionControlSystem($git);
 
     $this->assertEqual('ssh://example.com/', $repo->getRemoteURI());
     $this->assertEqual('ssh://example.com/', $repo->getPublicCloneURI());
     $this->assertEqual('ssh://example.com/',
       $repo->getRemoteURIEnvelope()->openEnvelope());
 
     $repo->setVersionControlSystem($hg);
 
     $this->assertEqual('ssh://example.com/', $repo->getRemoteURI());
     $this->assertEqual('ssh://example.com/', $repo->getPublicCloneURI());
     $this->assertEqual('ssh://example.com/',
       $repo->getRemoteURIEnvelope()->openEnvelope());
 
     // Test Git URIs.
 
     $repo->setDetail('remote-uri', 'git@example.com:path.git');
     $repo->setVersionControlSystem($git);
 
     $this->assertEqual('git@example.com:path.git', $repo->getRemoteURI());
     $this->assertEqual('git@example.com:path.git', $repo->getPublicCloneURI());
     $this->assertEqual('git@example.com:path.git',
       $repo->getRemoteURIEnvelope()->openEnvelope());
 
     // Test SVN "Import Only" paths.
 
     $repo->setDetail('remote-uri', 'http://example.com/');
     $repo->setVersionControlSystem($svn);
     $repo->setDetail('svn-subpath', 'projects/example/');
 
     $this->assertEqual('http://example.com/', $repo->getRemoteURI());
     $this->assertEqual(
       'http://example.com/projects/example/',
       $repo->getPublicCloneURI());
     $this->assertEqual('http://example.com/',
       $repo->getRemoteURIEnvelope()->openEnvelope());
 
   }
 
 }
diff --git a/src/applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php b/src/applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php
index 48c3b85f52..c1bf00c111 100644
--- a/src/applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php
+++ b/src/applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php
@@ -1,38 +1,38 @@
 <?php
 
 final class PhabricatorApplicationTransactionNoEffectException
   extends Exception {
 
   private $transactions;
   private $anyEffect;
   private $hasComment;
 
   public function __construct(array $transactions, $any_effect, $has_comment) {
     assert_instances_of($transactions, 'PhabricatorApplicationTransaction');
 
     $this->transactions = $transactions;
     $this->anyEffect = $any_effect;
     $this->hasComment = $has_comment;
 
     $message = array();
-    $message[] = 'Transactions have no effect:';
+    $message[] = pht('Transactions have no effect:');
     foreach ($this->transactions as $transaction) {
       $message[] = '  - '.$transaction->getNoEffectDescription();
     }
 
     parent::__construct(implode("\n", $message));
   }
 
   public function getTransactions() {
     return $this->transactions;
   }
 
   public function hasAnyEffect() {
     return $this->anyEffect;
   }
 
   public function hasComment() {
     return $this->hasComment;
   }
 
 }
diff --git a/src/applications/uiexample/examples/PHUITimelineExample.php b/src/applications/uiexample/examples/PHUITimelineExample.php
index 33b4fe9752..18cd180fd8 100644
--- a/src/applications/uiexample/examples/PHUITimelineExample.php
+++ b/src/applications/uiexample/examples/PHUITimelineExample.php
@@ -1,202 +1,202 @@
 <?php
 
 final class PHUITimelineExample extends PhabricatorUIExample {
 
   public function getName() {
     return pht('Timeline View');
   }
 
   public function getDescription() {
     return pht(
       'Use %s to comments and transactions.',
       hsprintf('<tt>PHUITimelineView</tt>'));
   }
 
   public function renderExample() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $handle = id(new PhabricatorHandleQuery())
       ->setViewer($user)
       ->withPHIDs(array($user->getPHID()))
       ->executeOne();
 
     $events = array();
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setTitle(pht('A major event.'))
       ->appendChild(pht('This is a major timeline event.'));
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setIcon('fa-heart')
       ->setTitle(pht('A minor event.'));
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setIcon('fa-comment')
       ->appendChild(pht('A major event with no title.'));
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setIcon('fa-star')
       ->setTitle(pht('Another minor event.'));
 
     $events[] = id(new PHUITimelineEventView())
       ->setIcon('fa-trophy')
       ->setToken('medal-1')
       ->setUserHandle($handle);
 
     $events[] = id(new PHUITimelineEventView())
       ->setIcon('fa-quote-left')
       ->setToken('medal-1', true)
       ->setUserHandle($handle);
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setTitle(pht('Major Red Event'))
       ->setIcon('fa-heart-o')
       ->appendChild(pht('This event is red!'))
       ->setColor(PhabricatorTransactions::COLOR_RED);
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setIcon('fa-female')
       ->setTitle(pht('Minor Red Event'))
       ->setColor(PhabricatorTransactions::COLOR_RED);
 
     $events[] = id(new PHUITimelineEventView())
       ->setIcon('fa-refresh')
       ->setUserHandle($handle)
       ->setTitle(pht('Minor Not-Red Event'))
       ->setColor(PhabricatorTransactions::COLOR_GREEN);
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setIcon('fa-calendar-o')
       ->setTitle(pht('Minor Red Event'))
       ->setColor(PhabricatorTransactions::COLOR_RED);
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setIcon('fa-check')
       ->setTitle(pht('Historically Important Action'))
       ->setColor(PhabricatorTransactions::COLOR_BLACK)
       ->setReallyMajorEvent(true);
 
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setIcon('fa-circle-o')
       ->setTitle(pht('Major Green Disagreement Action'))
       ->appendChild(pht('This event is green!'))
       ->setColor(PhabricatorTransactions::COLOR_GREEN);
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setIcon('fa-tag')
-      ->setTitle(str_repeat('Long Text Title ', 64))
-      ->appendChild(str_repeat('Long Text Body ', 64))
+      ->setTitle(str_repeat(pht('Long Text Title').' ', 64))
+      ->appendChild(str_repeat(pht('Long Text Body').' ', 64))
       ->setColor(PhabricatorTransactions::COLOR_ORANGE);
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setTitle(str_repeat('LongTextEventNoSpaces', 1024))
       ->appendChild(str_repeat('LongTextNoSpaces', 1024))
       ->setColor(PhabricatorTransactions::COLOR_RED);
 
     $colors = array(
       PhabricatorTransactions::COLOR_RED,
       PhabricatorTransactions::COLOR_ORANGE,
       PhabricatorTransactions::COLOR_YELLOW,
       PhabricatorTransactions::COLOR_GREEN,
       PhabricatorTransactions::COLOR_SKY,
       PhabricatorTransactions::COLOR_BLUE,
       PhabricatorTransactions::COLOR_INDIGO,
       PhabricatorTransactions::COLOR_VIOLET,
       PhabricatorTransactions::COLOR_GREY,
       PhabricatorTransactions::COLOR_BLACK,
     );
 
     $events[] = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setTitle(pht('Colorless'))
       ->setIcon('fa-lock');
 
     foreach ($colors as $color) {
       $events[] = id(new PHUITimelineEventView())
         ->setUserHandle($handle)
         ->setTitle(pht("Color '%s'", $color))
         ->setIcon('fa-paw')
         ->setColor($color);
     }
 
     $vhandle = $handle->renderLink();
 
     $group_event = id(new PHUITimelineEventView())
       ->setUserHandle($handle)
       ->setTitle(pht('%s went to the store.', $vhandle));
 
     $group_event->addEventToGroup(
       id(new PHUITimelineEventView())
         ->setUserHandle($handle)
         ->setTitle(pht('%s bought an apple.', $vhandle))
         ->setColor('green')
         ->setIcon('fa-apple'));
 
     $group_event->addEventToGroup(
       id(new PHUITimelineEventView())
         ->setUserHandle($handle)
         ->setTitle(pht('%s bought a banana.', $vhandle))
         ->setColor('yellow')
         ->setIcon('fa-check'));
 
     $group_event->addEventToGroup(
       id(new PHUITimelineEventView())
         ->setUserHandle($handle)
         ->setTitle(pht('%s bought a cherry.', $vhandle))
         ->setColor('red')
         ->setIcon('fa-check'));
 
     $group_event->addEventToGroup(
       id(new PHUITimelineEventView())
         ->setUserHandle($handle)
         ->setTitle(pht('%s paid for his goods.', $vhandle)));
 
     $group_event->addEventToGroup(
       id(new PHUITimelineEventView())
         ->setUserHandle($handle)
         ->setTitle(pht('%s returned home.', $vhandle))
         ->setIcon('fa-home')
         ->setColor('blue'));
 
     $group_event->addEventToGroup(
       id(new PHUITimelineEventView())
         ->setUserHandle($handle)
         ->setTitle(pht('%s related on his adventures.', $vhandle))
         ->appendChild(
           pht(
             'Today, I went to the store. I bought an apple. I bought a '.
             'banana. I bought a cherry. I paid for my goods, then I returned '.
             'home.')));
 
     $events[] = $group_event;
 
     $anchor = 0;
     foreach ($events as $group) {
       foreach ($group->getEventGroup() as $event) {
         $event->setUser($user);
         $event->setDateCreated(time() + ($anchor * 60 * 8));
         $event->setAnchor(++$anchor);
       }
     }
 
     $timeline = id(new PHUITimelineView());
     foreach ($events as $event) {
       $timeline->addEvent($event);
     }
 
     return $timeline;
   }
 }
diff --git a/src/applications/uiexample/examples/PhabricatorAphrontBarUIExample.php b/src/applications/uiexample/examples/PhabricatorAphrontBarUIExample.php
index d28d145ec6..f66c5c1618 100644
--- a/src/applications/uiexample/examples/PhabricatorAphrontBarUIExample.php
+++ b/src/applications/uiexample/examples/PhabricatorAphrontBarUIExample.php
@@ -1,72 +1,73 @@
 <?php
 
 final class PhabricatorAphrontBarUIExample extends PhabricatorUIExample {
 
   public function getName() {
     return pht('Bars');
   }
 
   public function getDescription() {
     return pht('Like fractions, but more horizontal.');
   }
 
   public function renderExample() {
     $out = array();
     $out[] = $this->renderTestThings('AphrontProgressBarView', 13, 10);
     $out[] = $this->renderTestThings('AphrontGlyphBarView', 13, 10);
     $out[] = $this->renderWeirdOrderGlyphBars();
     $out[] = $this->renderAsciiStarBar();
     return $out;
   }
 
   private function wrap($title, $thing) {
     $thing = phutil_tag_div('ml grouped', $thing);
     return id(new PHUIObjectBoxView())
       ->setHeaderText($title)
       ->appendChild($thing);
   }
 
   private function renderTestThings($class, $max, $incr) {
     $bars = array();
     for ($ii = 0; $ii <= $max; $ii++) {
       $bars[] = newv($class, array())
         ->setValue($ii * $incr)
         ->setMax($max * $incr)
         ->setCaption("{$ii} outta {$max} ain't bad!");
     }
     return $this->wrap("Test {$class}", $bars);
   }
 
   private function renderWeirdOrderGlyphBars() {
     $views = array();
     $indices = array(1, 3, 7, 4, 2, 8, 9, 5, 10, 6);
     $max = count($indices);
     foreach ($indices as $index) {
       $views[] = id(new AphrontGlyphBarView())
         ->setValue($index)
         ->setMax($max)
         ->setNumGlyphs(5)
         ->setCaption("Lol score is {$index}/{$max}")
         ->setGlyph(hsprintf('%s', 'LOL!'))
         ->setBackgroundGlyph(hsprintf('%s', '____'));
       $views[] = hsprintf('<div style="clear:both;"></div>');
     }
 
     return $this->wrap(
-      'Glyph bars in weird order',
+      pht('Glyph bars in weird order'),
       $views);
   }
 
   private function renderAsciiStarBar() {
     $bar = id(new AphrontGlyphBarView())
         ->setValue(50)
         ->setMax(100)
-        ->setCaption('Glyphs!')
+        ->setCaption(pht('Glyphs!'))
         ->setNumGlyphs(10)
         ->setGlyph(hsprintf('%s', '*'));
 
     return $this->wrap(
-      'Ascii star glyph bar', $bar);
+      pht('ASCII star glyph bar'),
+      $bar);
   }
 
 }
diff --git a/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php b/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php
index fb0f7a6362..3f42be9ce1 100644
--- a/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php
+++ b/src/applications/uiexample/examples/PhabricatorPagedFormUIExample.php
@@ -1,71 +1,71 @@
 <?php
 
 final class PhabricatorPagedFormUIExample extends PhabricatorUIExample {
 
   public function getName() {
     return pht('Form (Paged)');
   }
 
   public function getDescription() {
     return pht(
       'Use %s to render forms with multiple pages.',
       hsprintf('<tt>PHUIPagedFormView</tt>'));
   }
 
   public function renderExample() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
 
     $page1 = id(new PHUIFormPageView())
       ->setPageName(pht('Page 1'))
       ->addControl(
         id(new AphrontFormTextControl())
           ->setName('page1')
-          ->setLabel('Page 1'));
+          ->setLabel(pht('Page 1')));
 
     $page2 = id(new PHUIFormPageView())
       ->setPageName(pht('Page 2'))
       ->addControl(
         id(new AphrontFormTextControl())
           ->setName('page2')
-          ->setLabel('Page 2'));
+          ->setLabel(pht('Page 2')));
 
     $page3 = id(new PHUIFormPageView())
       ->setPageName(pht('Page 3'))
       ->addControl(
         id(new AphrontFormTextControl())
           ->setName('page3')
-          ->setLabel('Page 3'));
+          ->setLabel(pht('Page 3')));
 
     $page4 = id(new PHUIFormPageView())
       ->setPageName(pht('Page 4'))
       ->addControl(
         id(new AphrontFormTextControl())
           ->setName('page4')
-          ->setLabel('Page 4'));
+          ->setLabel(pht('Page 4')));
 
     $form = new PHUIPagedFormView();
     $form->setUser($user);
 
     $form->addPage('page1', $page1);
     $form->addPage('page2', $page2);
     $form->addPage('page3', $page3);
     $form->addPage('page4', $page4);
 
     if ($request->isFormPost()) {
       $form->readFromRequest($request);
       if ($form->isComplete()) {
         return id(new AphrontDialogView())
           ->setUser($user)
           ->setTitle(pht('Form Complete'))
           ->appendChild(pht('You submitted the form. Well done!'))
           ->addCancelButton($request->getRequestURI(), pht('Again!'));
       }
     } else {
       $form->readFromObject(null);
     }
 
     return $form;
   }
 }
diff --git a/src/applications/uiexample/examples/PhabricatorPagerUIExample.php b/src/applications/uiexample/examples/PhabricatorPagerUIExample.php
index e2e938a302..26b47728c5 100644
--- a/src/applications/uiexample/examples/PhabricatorPagerUIExample.php
+++ b/src/applications/uiexample/examples/PhabricatorPagerUIExample.php
@@ -1,81 +1,81 @@
 <?php
 
 final class PhabricatorPagerUIExample extends PhabricatorUIExample {
 
   public function getName() {
     return pht('Pager');
   }
 
   public function getDescription() {
     return pht(
       'Use %s to create a control which allows '.
       'users to paginate through large amounts of content.',
       phutil_tag('tt', array(), 'AphrontPagerView'));
   }
 
   public function renderExample() {
     $request = $this->getRequest();
 
     $offset = (int)$request->getInt('offset');
     $page_size = 20;
     $item_count = 173;
 
     $rows = array();
     for ($ii = $offset; $ii < min($item_count, $offset + $page_size); $ii++) {
       $rows[] = array(
-        'Item #'.($ii + 1),
+        pht('Item #%d', $ii + 1),
       );
     }
 
     $table = new AphrontTableView($rows);
     $table->setHeaders(
       array(
         'Item',
       ));
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText(pht('Example'));
     $panel->appendChild($table);
 
     $panel->appendChild(hsprintf(
       '<p class="phabricator-ui-example-note">%s</p>',
       pht(
         'Use %s to render a pager element.',
         phutil_tag('tt', array(), 'AphrontPagerView'))));
 
     $pager = new AphrontPagerView();
     $pager->setPageSize($page_size);
     $pager->setOffset($offset);
     $pager->setCount($item_count);
     $pager->setURI($request->getRequestURI(), 'offset');
     $panel->appendChild($pager);
 
     $panel->appendChild(hsprintf(
       '<p class="phabricator-ui-example-note">%s</p>',
       pht('You can show more or fewer pages of surrounding context.')));
 
     $many_pages_pager = new AphrontPagerView();
     $many_pages_pager->setPageSize($page_size);
     $many_pages_pager->setOffset($offset);
     $many_pages_pager->setCount($item_count);
     $many_pages_pager->setURI($request->getRequestURI(), 'offset');
     $many_pages_pager->setSurroundingPages(7);
     $panel->appendChild($many_pages_pager);
 
     $panel->appendChild(hsprintf(
       '<p class="phabricator-ui-example-note">%s</p>',
       pht(
         'When it is prohibitively expensive or complex to attain a complete '.
         'count of the items, you can select one extra item and set '.
         '%s if it exists, creating an inexact pager.',
         phutil_tag('tt', array(), 'hasMorePages(true)'))));
 
     $inexact_pager = new AphrontPagerView();
     $inexact_pager->setPageSize($page_size);
     $inexact_pager->setOffset($offset);
     $inexact_pager->setHasMorePages($offset < ($item_count - $page_size));
     $inexact_pager->setURI($request->getRequestURI(), 'offset');
     $panel->appendChild($inexact_pager);
 
     return $panel;
   }
 }
diff --git a/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php b/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php
index 7911710815..b784306403 100644
--- a/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php
+++ b/src/applications/uiexample/examples/PhabricatorSortTableUIExample.php
@@ -1,96 +1,96 @@
 <?php
 
 final class PhabricatorSortTableUIExample extends PhabricatorUIExample {
 
   public function getName() {
     return pht('Sortable Tables');
   }
 
   public function getDescription() {
     return pht('Using sortable tables.');
   }
 
   public function renderExample() {
 
     $rows = array(
       array(
         'make'    => 'Honda',
         'model'   => 'Civic',
         'year'    => 2004,
         'price'   => 3199,
-        'color'   => 'Blue',
+        'color'   => pht('Blue'),
       ),
       array(
         'make'    => 'Ford',
         'model'   => 'Focus',
         'year'    => 2001,
         'price'   => 2549,
-        'color'   => 'Red',
+        'color'   => pht('Red'),
       ),
       array(
         'make'    => 'Toyota',
         'model'   => 'Camry',
         'year'    => 2009,
         'price'   => 4299,
-        'color'   => 'Black',
+        'color'   => pht('Black'),
       ),
       array(
         'make'    => 'NASA',
         'model'   => 'Shuttle',
         'year'    => 1998,
         'price'   => 1000000000,
-        'color'   => 'White',
+        'color'   => pht('White'),
       ),
     );
 
     $request = $this->getRequest();
 
     $orders = array(
       'make',
       'model',
       'year',
       'price',
     );
 
     $sort = $request->getStr('sort');
     list($sort, $reverse) = AphrontTableView::parseSort($sort);
     if (!in_array($sort, $orders)) {
       $sort = 'make';
     }
 
     $rows = isort($rows, $sort);
     if ($reverse) {
       $rows = array_reverse($rows);
     }
 
     $table = new AphrontTableView($rows);
     $table->setHeaders(
       array(
         pht('Make'),
         pht('Model'),
         pht('Year'),
         pht('Price'),
         pht('Color'),
       ));
     $table->setColumnClasses(
       array(
         '',
         'wide',
         'n',
         'n',
         '',
       ));
     $table->makeSortable(
       $request->getRequestURI(),
       'sort',
       $sort,
       $reverse,
       $orders);
 
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText(pht('Sortable Table of Vehicles'));
     $panel->appendChild($table);
 
     return $panel;
   }
 }
diff --git a/src/applications/xhprof/controller/PhabricatorXHProfController.php b/src/applications/xhprof/controller/PhabricatorXHProfController.php
index 6c595d1455..6dda92560c 100644
--- a/src/applications/xhprof/controller/PhabricatorXHProfController.php
+++ b/src/applications/xhprof/controller/PhabricatorXHProfController.php
@@ -1,27 +1,27 @@
 <?php
 
 abstract class PhabricatorXHProfController extends PhabricatorController {
 
   public function buildStandardPageResponse($view, array $data) {
     $page = $this->buildStandardPageView();
 
-    $page->setApplicationName('XHProf');
+    $page->setApplicationName(pht('XHProf'));
     $page->setBaseURI('/xhprof/');
     $page->setTitle(idx($data, 'title'));
     $page->setGlyph("\xE2\x98\x84");
     $page->appendChild($view);
     $page->setDeviceReady(true);
 
     $response = new AphrontWebpageResponse();
 
     if (isset($data['frame'])) {
       $response->setFrameable(true);
       $page->setFrameable(true);
       $page->setShowChrome(false);
       $page->setDisableConsole(true);
     }
 
     return $response->setContent($page->render());
   }
 
 }
diff --git a/src/infrastructure/PhabricatorEditor.php b/src/infrastructure/PhabricatorEditor.php
index 745ce3c156..eee718a23e 100644
--- a/src/infrastructure/PhabricatorEditor.php
+++ b/src/infrastructure/PhabricatorEditor.php
@@ -1,34 +1,34 @@
 <?php
 
 abstract class PhabricatorEditor extends Phobject {
 
   private $actor;
   private $excludeMailRecipientPHIDs = array();
 
   final public function setActor(PhabricatorUser $actor) {
     $this->actor = $actor;
     return $this;
   }
 
   final public function getActor() {
     return $this->actor;
   }
 
   final public function requireActor() {
     $actor = $this->getActor();
     if (!$actor) {
-      throw new Exception('You must setActor()!');
+      throw new PhutilInvalidStateException('setActor');
     }
     return $actor;
   }
 
   final public function setExcludeMailRecipientPHIDs($phids) {
     $this->excludeMailRecipientPHIDs = $phids;
     return $this;
   }
 
   final protected function getExcludeMailRecipientPHIDs() {
     return $this->excludeMailRecipientPHIDs;
   }
 
 }
diff --git a/src/infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php b/src/infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php
index 55640f8ed4..3ab7046e95 100644
--- a/src/infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php
+++ b/src/infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php
@@ -1,26 +1,28 @@
 <?php
 
 final class PhabricatorCustomFieldImplementationIncompleteException
   extends Exception {
 
   public function __construct(
     PhabricatorCustomField $field,
     $field_key_is_incomplete = false) {
 
     if ($field_key_is_incomplete) {
       $key = pht('<incomplete key>');
       $name = pht('<incomplete name>');
     } else {
       $key = $field->getFieldKey();
       $name = $field->getFieldName();
     }
 
-    $class = get_class($field);
-
     parent::__construct(
-      "Custom field '{$name}' (with key '{$key}', of class '{$class}') is ".
-      "incompletely implemented: it claims to support a feature, but does not ".
-      "implement all of the required methods for that feature.");
+      pht(
+        "Custom field '%s' (with key '%s', of class '%s') is incompletely ".
+        "implemented: it claims to support a feature, but does not ".
+        "implement all of the required methods for that feature.",
+        $name,
+        $key,
+        get_class($field)));
   }
 
 }
diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php
index 18c112516b..4b0f68e93a 100644
--- a/src/infrastructure/customfield/field/PhabricatorCustomField.php
+++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php
@@ -1,1358 +1,1362 @@
 <?php
 
 /**
  * @task apps         Building Applications with Custom Fields
  * @task core         Core Properties and Field Identity
  * @task proxy        Field Proxies
  * @task context      Contextual Data
  * @task render       Rendering Utilities
  * @task storage      Field Storage
  * @task edit         Integration with Edit Views
  * @task view         Integration with Property Views
  * @task list         Integration with List views
  * @task appsearch    Integration with ApplicationSearch
  * @task appxaction   Integration with ApplicationTransactions
  * @task xactionmail  Integration with Transaction Mail
  * @task globalsearch Integration with Global Search
  * @task herald       Integration with Herald
  */
 abstract class PhabricatorCustomField {
 
   private $viewer;
   private $object;
   private $proxy;
 
   const ROLE_APPLICATIONTRANSACTIONS  = 'ApplicationTransactions';
   const ROLE_TRANSACTIONMAIL          = 'ApplicationTransactions.mail';
   const ROLE_APPLICATIONSEARCH        = 'ApplicationSearch';
   const ROLE_STORAGE                  = 'storage';
   const ROLE_DEFAULT                  = 'default';
   const ROLE_EDIT                     = 'edit';
   const ROLE_VIEW                     = 'view';
   const ROLE_LIST                     = 'list';
   const ROLE_GLOBALSEARCH             = 'GlobalSearch';
   const ROLE_CONDUIT                  = 'conduit';
   const ROLE_HERALD                   = 'herald';
 
 
 /* -(  Building Applications with Custom Fields  )--------------------------- */
 
 
   /**
    * @task apps
    */
   public static function getObjectFields(
     PhabricatorCustomFieldInterface $object,
     $role) {
 
     try {
       $attachment = $object->getCustomFields();
     } catch (PhabricatorDataNotAttachedException $ex) {
       $attachment = new PhabricatorCustomFieldAttachment();
       $object->attachCustomFields($attachment);
     }
 
     try {
       $field_list = $attachment->getCustomFieldList($role);
     } catch (PhabricatorCustomFieldNotAttachedException $ex) {
       $base_class = $object->getCustomFieldBaseClass();
 
       $spec = $object->getCustomFieldSpecificationForRole($role);
       if (!is_array($spec)) {
-        $obj_class = get_class($object);
         throw new Exception(
-          "Expected an array from getCustomFieldSpecificationForRole() for ".
-          "object of class '{$obj_class}'.");
+          pht(
+            "Expected an array from %s for object of class '%s'.",
+            'getCustomFieldSpecificationForRole()',
+            get_class($object)));
       }
 
       $fields = self::buildFieldList(
         $base_class,
         $spec,
         $object);
 
       foreach ($fields as $key => $field) {
         if (!$field->shouldEnableForRole($role)) {
           unset($fields[$key]);
         }
       }
 
       foreach ($fields as $field) {
         $field->setObject($object);
       }
 
       $field_list = new PhabricatorCustomFieldList($fields);
       $attachment->addCustomFieldList($role, $field_list);
     }
 
     return $field_list;
   }
 
 
   /**
    * @task apps
    */
   public static function getObjectField(
     PhabricatorCustomFieldInterface $object,
     $role,
     $field_key) {
 
     $fields = self::getObjectFields($object, $role)->getFields();
 
     return idx($fields, $field_key);
   }
 
 
   /**
    * @task apps
    */
   public static function buildFieldList(
     $base_class,
     array $spec,
     $object,
     array $options = array()) {
 
     PhutilTypeSpec::checkMap(
       $options,
       array(
         'withDisabled' => 'optional bool',
       ));
 
     $field_objects = id(new PhutilSymbolLoader())
       ->setAncestorClass($base_class)
       ->loadObjects();
 
     $fields = array();
     $from_map = array();
     foreach ($field_objects as $field_object) {
       $current_class = get_class($field_object);
       foreach ($field_object->createFields($object) as $field) {
         $key = $field->getFieldKey();
         if (isset($fields[$key])) {
-          $original_class = $from_map[$key];
           throw new Exception(
-            "Both '{$original_class}' and '{$current_class}' define a custom ".
-            "field with field key '{$key}'. Field keys must be unique.");
+            pht(
+              "Both '%s' and '%s' define a custom field with ".
+              "field key '%s'. Field keys must be unique.",
+              $from_map[$key],
+              $current_class,
+              $key));
         }
         $from_map[$key] = $current_class;
         $fields[$key] = $field;
       }
     }
 
     foreach ($fields as $key => $field) {
       if (!$field->isFieldEnabled()) {
         unset($fields[$key]);
       }
     }
 
     $fields = array_select_keys($fields, array_keys($spec)) + $fields;
 
     if (empty($options['withDisabled'])) {
       foreach ($fields as $key => $field) {
         $config = idx($spec, $key, array()) + array(
           'disabled' => $field->shouldDisableByDefault(),
         );
 
         if (!empty($config['disabled'])) {
           if ($field->canDisableField()) {
             unset($fields[$key]);
           }
         }
       }
     }
 
     return $fields;
   }
 
 
 /* -(  Core Properties and Field Identity  )--------------------------------- */
 
 
   /**
    * Return a key which uniquely identifies this field, like
    * "mycompany:dinosaur:count". Normally you should provide some level of
    * namespacing to prevent collisions.
    *
    * @return string String which uniquely identifies this field.
    * @task core
    */
   public function getFieldKey() {
     if ($this->proxy) {
       return $this->proxy->getFieldKey();
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException(
       $this,
       $field_key_is_incomplete = true);
   }
 
 
   /**
    * Return a human-readable field name.
    *
    * @return string Human readable field name.
    * @task core
    */
   public function getFieldName() {
     if ($this->proxy) {
       return $this->proxy->getFieldName();
     }
     return $this->getFieldKey();
   }
 
 
   /**
    * Return a short, human-readable description of the field's behavior. This
    * provides more context to administrators when they are customizing fields.
    *
    * @return string|null Optional human-readable description.
    * @task core
    */
   public function getFieldDescription() {
     if ($this->proxy) {
       return $this->proxy->getFieldDescription();
     }
     return null;
   }
 
 
   /**
    * Most field implementations are unique, in that one class corresponds to
    * one field. However, some field implementations are general and a single
    * implementation may drive several fields.
    *
    * For general implementations, the general field implementation can return
    * multiple field instances here.
    *
    * @param object The object to create fields for.
    * @return list<PhabricatorCustomField> List of fields.
    * @task core
    */
   public function createFields($object) {
     return array($this);
   }
 
 
   /**
    * You can return `false` here if the field should not be enabled for any
    * role. For example, it might depend on something (like an application or
    * library) which isn't installed, or might have some global configuration
    * which allows it to be disabled.
    *
    * @return bool False to completely disable this field for all roles.
    * @task core
    */
   public function isFieldEnabled() {
     if ($this->proxy) {
       return $this->proxy->isFieldEnabled();
     }
     return true;
   }
 
 
   /**
    * Low level selector for field availability. Fields can appear in different
    * roles (like an edit view, a list view, etc.), but not every field needs
    * to appear everywhere. Fields that are disabled in a role won't appear in
    * that context within applications.
    *
    * Normally, you do not need to override this method. Instead, override the
    * methods specific to roles you want to enable. For example, implement
    * @{method:shouldUseStorage()} to activate the `'storage'` role.
    *
    * @return bool True to enable the field for the given role.
    * @task core
    */
   public function shouldEnableForRole($role) {
 
     // NOTE: All of these calls proxy individually, so we don't need to
     // proxy this call as a whole.
 
     switch ($role) {
       case self::ROLE_APPLICATIONTRANSACTIONS:
         return $this->shouldAppearInApplicationTransactions();
       case self::ROLE_APPLICATIONSEARCH:
         return $this->shouldAppearInApplicationSearch();
       case self::ROLE_STORAGE:
         return $this->shouldUseStorage();
       case self::ROLE_EDIT:
         return $this->shouldAppearInEditView();
       case self::ROLE_VIEW:
         return $this->shouldAppearInPropertyView();
       case self::ROLE_LIST:
         return $this->shouldAppearInListView();
       case self::ROLE_GLOBALSEARCH:
         return $this->shouldAppearInGlobalSearch();
       case self::ROLE_CONDUIT:
         return $this->shouldAppearInConduitDictionary();
       case self::ROLE_TRANSACTIONMAIL:
         return $this->shouldAppearInTransactionMail();
       case self::ROLE_HERALD:
         return $this->shouldAppearInHerald();
       case self::ROLE_DEFAULT:
         return true;
       default:
-        throw new Exception("Unknown field role '{$role}'!");
+        throw new Exception(pht("Unknown field role '%s'!", $role));
     }
   }
 
 
   /**
    * Allow administrators to disable this field. Most fields should allow this,
    * but some are fundamental to the behavior of the application and can be
    * locked down to avoid chaos, disorder, and the decline of civilization.
    *
    * @return bool False to prevent this field from being disabled through
    *              configuration.
    * @task core
    */
   public function canDisableField() {
     return true;
   }
 
   public function shouldDisableByDefault() {
     return false;
   }
 
 
   /**
    * Return an index string which uniquely identifies this field.
    *
    * @return string Index string which uniquely identifies this field.
    * @task core
    */
   final public function getFieldIndex() {
     return PhabricatorHash::digestForIndex($this->getFieldKey());
   }
 
 
 /* -(  Field Proxies  )------------------------------------------------------ */
 
 
   /**
    * Proxies allow a field to use some other field's implementation for most
    * of their behavior while still subclassing an application field. When a
    * proxy is set for a field with @{method:setProxy}, all of its methods will
    * call through to the proxy by default.
    *
    * This is most commonly used to implement configuration-driven custom fields
    * using @{class:PhabricatorStandardCustomField}.
    *
    * This method must be overridden to return `true` before a field can accept
    * proxies.
    *
    * @return bool True if you can @{method:setProxy} this field.
    * @task proxy
    */
   public function canSetProxy() {
     if ($this instanceof PhabricatorStandardCustomFieldInterface) {
       return true;
     }
     return false;
   }
 
 
   /**
    * Set the proxy implementation for this field. See @{method:canSetProxy} for
    * discussion of field proxies.
    *
    * @param PhabricatorCustomField Field implementation.
    * @return this
    */
   final public function setProxy(PhabricatorCustomField $proxy) {
     if (!$this->canSetProxy()) {
       throw new PhabricatorCustomFieldNotProxyException($this);
     }
 
     $this->proxy = $proxy;
     return $this;
   }
 
 
   /**
    * Get the field's proxy implementation, if any. For discussion, see
    * @{method:canSetProxy}.
    *
    * @return PhabricatorCustomField|null  Proxy field, if one is set.
    */
   final public function getProxy() {
     return $this->proxy;
   }
 
 
 /* -(  Contextual Data  )---------------------------------------------------- */
 
 
   /**
    * Sets the object this field belongs to.
    *
    * @param PhabricatorCustomFieldInterface The object this field belongs to.
    * @return this
    * @task context
    */
   final public function setObject(PhabricatorCustomFieldInterface $object) {
     if ($this->proxy) {
       $this->proxy->setObject($object);
       return $this;
     }
 
     $this->object = $object;
     $this->didSetObject($object);
     return $this;
   }
 
 
   /**
    * Read object data into local field storage, if applicable.
    *
    * @param PhabricatorCustomFieldInterface The object this field belongs to.
    * @return this
    * @task context
    */
   public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
     if ($this->proxy) {
       $this->proxy->readValueFromObject($object);
     }
     return $this;
   }
 
 
   /**
    * Get the object this field belongs to.
    *
    * @return PhabricatorCustomFieldInterface The object this field belongs to.
    * @task context
    */
   final public function getObject() {
     if ($this->proxy) {
       return $this->proxy->getObject();
     }
 
     return $this->object;
   }
 
 
   /**
    * This is a hook, primarily for subclasses to load object data.
    *
    * @return PhabricatorCustomFieldInterface The object this field belongs to.
    * @return void
    */
   protected function didSetObject(PhabricatorCustomFieldInterface $object) {
     return;
   }
 
 
   /**
    * @task context
    */
   final public function setViewer(PhabricatorUser $viewer) {
     if ($this->proxy) {
       $this->proxy->setViewer($viewer);
       return $this;
     }
 
     $this->viewer = $viewer;
     return $this;
   }
 
 
   /**
    * @task context
    */
   final public function getViewer() {
     if ($this->proxy) {
       return $this->proxy->getViewer();
     }
 
     return $this->viewer;
   }
 
 
   /**
    * @task context
    */
   final protected function requireViewer() {
     if ($this->proxy) {
       return $this->proxy->requireViewer();
     }
 
     if (!$this->viewer) {
       throw new PhabricatorCustomFieldDataNotAvailableException($this);
     }
     return $this->viewer;
   }
 
 
 /* -(  Rendering Utilities  )------------------------------------------------ */
 
 
   /**
    * @task render
    */
   protected function renderHandleList(array $handles) {
     if (!$handles) {
       return null;
     }
 
     $out = array();
     foreach ($handles as $handle) {
       $out[] = $handle->renderLink();
     }
 
     return phutil_implode_html(phutil_tag('br'), $out);
   }
 
 
 /* -(  Storage  )------------------------------------------------------------ */
 
 
   /**
    * Return true to use field storage.
    *
    * Fields which can be edited by the user will most commonly use storage,
    * while some other types of fields (for instance, those which just display
    * information in some stylized way) may not. Many builtin fields do not use
    * storage because their data is available on the object itself.
    *
    * If you implement this, you must also implement @{method:getValueForStorage}
    * and @{method:setValueFromStorage}.
    *
    * @return bool True to use storage.
    * @task storage
    */
   public function shouldUseStorage() {
     if ($this->proxy) {
       return $this->proxy->shouldUseStorage();
     }
     return false;
   }
 
 
   /**
    * Return a new, empty storage object. This should be a subclass of
    * @{class:PhabricatorCustomFieldStorage} which is bound to the application's
    * database.
    *
    * @return PhabricatorCustomFieldStorage New empty storage object.
    * @task storage
    */
   public function newStorageObject() {
     if ($this->proxy) {
       return $this->proxy->newStorageObject();
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * Return a serialized representation of the field value, appropriate for
    * storing in auxiliary field storage. You must implement this method if
    * you implement @{method:shouldUseStorage}.
    *
    * If the field value is a scalar, it can be returned unmodiifed. If not,
    * it should be serialized (for example, using JSON).
    *
    * @return string Serialized field value.
    * @task storage
    */
   public function getValueForStorage() {
     if ($this->proxy) {
       return $this->proxy->getValueForStorage();
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * Set the field's value given a serialized storage value. This is called
    * when the field is loaded; if no data is available, the value will be
    * null. You must implement this method if you implement
    * @{method:shouldUseStorage}.
    *
    * Usually, the value can be loaded directly. If it isn't a scalar, you'll
    * need to undo whatever serialization you applied in
    * @{method:getValueForStorage}.
    *
    * @param string|null Serialized field representation (from
    *                    @{method:getValueForStorage}) or null if no value has
    *                    ever been stored.
    * @return this
    * @task storage
    */
   public function setValueFromStorage($value) {
     if ($this->proxy) {
       return $this->proxy->setValueFromStorage($value);
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
 /* -(  ApplicationSearch  )-------------------------------------------------- */
 
 
   /**
    * Appearing in ApplicationSearch allows a field to be indexed and searched
    * for.
    *
    * @return bool True to appear in ApplicationSearch.
    * @task appsearch
    */
   public function shouldAppearInApplicationSearch() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInApplicationSearch();
     }
     return false;
   }
 
 
   /**
    * Return one or more indexes which this field can meaningfully query against
    * to implement ApplicationSearch.
    *
    * Normally, you should build these using @{method:newStringIndex} and
    * @{method:newNumericIndex}. For example, if a field holds a numeric value
    * it might return a single numeric index:
    *
    *   return array($this->newNumericIndex($this->getValue()));
    *
    * If a field holds a more complex value (like a list of users), it might
    * return several string indexes:
    *
    *   $indexes = array();
    *   foreach ($this->getValue() as $phid) {
    *     $indexes[] = $this->newStringIndex($phid);
    *   }
    *   return $indexes;
    *
    * @return list<PhabricatorCustomFieldIndexStorage> List of indexes.
    * @task appsearch
    */
   public function buildFieldIndexes() {
     if ($this->proxy) {
       return $this->proxy->buildFieldIndexes();
     }
     return array();
   }
 
 
   /**
    * Return an index against which this field can be meaningfully ordered
    * against to implement ApplicationSearch.
    *
    * This should be a single index, normally built using
    * @{method:newStringIndex} and @{method:newNumericIndex}.
    *
    * The value of the index is not used.
    *
    * Return null from this method if the field can not be ordered.
    *
    * @return PhabricatorCustomFieldIndexStorage A single index to order by.
    * @task appsearch
    */
   public function buildOrderIndex() {
     if ($this->proxy) {
       return $this->proxy->buildOrderIndex();
     }
     return null;
   }
 
 
   /**
    * Build a new empty storage object for storing string indexes. Normally,
    * this should be a concrete subclass of
    * @{class:PhabricatorCustomFieldStringIndexStorage}.
    *
    * @return PhabricatorCustomFieldStringIndexStorage Storage object.
    * @task appsearch
    */
   protected function newStringIndexStorage() {
     // NOTE: This intentionally isn't proxied, to avoid call cycles.
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * Build a new empty storage object for storing string indexes. Normally,
    * this should be a concrete subclass of
    * @{class:PhabricatorCustomFieldStringIndexStorage}.
    *
    * @return PhabricatorCustomFieldStringIndexStorage Storage object.
    * @task appsearch
    */
   protected function newNumericIndexStorage() {
     // NOTE: This intentionally isn't proxied, to avoid call cycles.
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * Build and populate storage for a string index.
    *
    * @param string String to index.
    * @return PhabricatorCustomFieldStringIndexStorage Populated storage.
    * @task appsearch
    */
   protected function newStringIndex($value) {
     if ($this->proxy) {
       return $this->proxy->newStringIndex();
     }
 
     $key = $this->getFieldIndex();
     return $this->newStringIndexStorage()
       ->setIndexKey($key)
       ->setIndexValue($value);
   }
 
 
   /**
    * Build and populate storage for a numeric index.
    *
    * @param string Numeric value to index.
    * @return PhabricatorCustomFieldNumericIndexStorage Populated storage.
    * @task appsearch
    */
   protected function newNumericIndex($value) {
     if ($this->proxy) {
       return $this->proxy->newNumericIndex();
     }
     $key = $this->getFieldIndex();
     return $this->newNumericIndexStorage()
       ->setIndexKey($key)
       ->setIndexValue($value);
   }
 
 
   /**
    * Read a query value from a request, for storage in a saved query. Normally,
    * this method should, e.g., read a string out of the request.
    *
    * @param PhabricatorApplicationSearchEngine Engine building the query.
    * @param AphrontRequest Request to read from.
    * @return wild
    * @task appsearch
    */
   public function readApplicationSearchValueFromRequest(
     PhabricatorApplicationSearchEngine $engine,
     AphrontRequest $request) {
     if ($this->proxy) {
       return $this->proxy->readApplicationSearchValueFromRequest(
         $engine,
         $request);
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * Constrain a query, given a field value. Generally, this method should
    * use `with...()` methods to apply filters or other constraints to the
    * query.
    *
    * @param PhabricatorApplicationSearchEngine Engine executing the query.
    * @param PhabricatorCursorPagedPolicyAwareQuery Query to constrain.
    * @param wild Constraint provided by the user.
    * @return void
    * @task appsearch
    */
   public function applyApplicationSearchConstraintToQuery(
     PhabricatorApplicationSearchEngine $engine,
     PhabricatorCursorPagedPolicyAwareQuery $query,
     $value) {
     if ($this->proxy) {
       return $this->proxy->applyApplicationSearchConstraintToQuery(
         $engine,
         $query,
         $value);
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * Append search controls to the interface.
    *
    * @param PhabricatorApplicationSearchEngine Engine constructing the form.
    * @param AphrontFormView The form to update.
    * @param wild Value from the saved query.
    * @return void
    * @task appsearch
    */
   public function appendToApplicationSearchForm(
     PhabricatorApplicationSearchEngine $engine,
     AphrontFormView $form,
     $value) {
     if ($this->proxy) {
       return $this->proxy->appendToApplicationSearchForm(
         $engine,
         $form,
         $value);
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
 /* -(  ApplicationTransactions  )-------------------------------------------- */
 
 
   /**
    * Appearing in ApplicationTrasactions allows a field to be edited using
    * standard workflows.
    *
    * @return bool True to appear in ApplicationTransactions.
    * @task appxaction
    */
   public function shouldAppearInApplicationTransactions() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInApplicationTransactions();
     }
     return false;
   }
 
 
   /**
    * @task appxaction
    */
   public function getApplicationTransactionType() {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionType();
     }
     return PhabricatorTransactions::TYPE_CUSTOMFIELD;
   }
 
 
   /**
    * @task appxaction
    */
   public function getApplicationTransactionMetadata() {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionMetadata();
     }
     return array();
   }
 
 
   /**
    * @task appxaction
    */
   public function getOldValueForApplicationTransactions() {
     if ($this->proxy) {
       return $this->proxy->getOldValueForApplicationTransactions();
     }
     return $this->getValueForStorage();
   }
 
 
   /**
    * @task appxaction
    */
   public function getNewValueForApplicationTransactions() {
     if ($this->proxy) {
       return $this->proxy->getNewValueForApplicationTransactions();
     }
     return $this->getValueForStorage();
   }
 
 
   /**
    * @task appxaction
    */
   public function setValueFromApplicationTransactions($value) {
     if ($this->proxy) {
       return $this->proxy->setValueFromApplicationTransactions($value);
     }
     return $this->setValueFromStorage($value);
   }
 
 
   /**
    * @task appxaction
    */
   public function getNewValueFromApplicationTransactions(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->getNewValueFromApplicationTransactions($xaction);
     }
     return $xaction->getNewValue();
   }
 
 
   /**
    * @task appxaction
    */
   public function getApplicationTransactionHasEffect(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionHasEffect($xaction);
     }
     return ($xaction->getOldValue() !== $xaction->getNewValue());
   }
 
 
   /**
    * @task appxaction
    */
   public function applyApplicationTransactionInternalEffects(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->applyApplicationTransactionInternalEffects($xaction);
     }
     return;
   }
 
 
   /**
    * @task appxaction
    */
   public function getApplicationTransactionRemarkupBlocks(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionRemarkupBlocks($xaction);
     }
     return array();
   }
 
 
   /**
    * @task appxaction
    */
   public function applyApplicationTransactionExternalEffects(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->applyApplicationTransactionExternalEffects($xaction);
     }
 
     if (!$this->shouldEnableForRole(self::ROLE_STORAGE)) {
       return;
     }
 
     $this->setValueFromApplicationTransactions($xaction->getNewValue());
     $value = $this->getValueForStorage();
 
     $table = $this->newStorageObject();
     $conn_w = $table->establishConnection('w');
 
     if ($value === null) {
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE objectPHID = %s AND fieldIndex = %s',
         $table->getTableName(),
         $this->getObject()->getPHID(),
         $this->getFieldIndex());
     } else {
       queryfx(
         $conn_w,
         'INSERT INTO %T (objectPHID, fieldIndex, fieldValue)
           VALUES (%s, %s, %s)
           ON DUPLICATE KEY UPDATE fieldValue = VALUES(fieldValue)',
         $table->getTableName(),
         $this->getObject()->getPHID(),
         $this->getFieldIndex(),
         $value);
     }
 
     return;
   }
 
 
   /**
    * Validate transactions for an object. This allows you to raise an error
    * when a transaction would set a field to an invalid value, or when a field
    * is required but no transactions provide value.
    *
    * @param PhabricatorLiskDAO Editor applying the transactions.
    * @param string Transaction type. This type is always
    *   `PhabricatorTransactions::TYPE_CUSTOMFIELD`, it is provided for
    *   convenience when constructing exceptions.
    * @param list<PhabricatorApplicationTransaction> Transactions being applied,
    *   which may be empty if this field is not being edited.
    * @return list<PhabricatorApplicationTransactionValidationError> Validation
    *   errors.
    *
    * @task appxaction
    */
   public function validateApplicationTransactions(
     PhabricatorApplicationTransactionEditor $editor,
     $type,
     array $xactions) {
     if ($this->proxy) {
       return $this->proxy->validateApplicationTransactions(
         $editor,
         $type,
         $xactions);
     }
     return array();
   }
 
   public function getApplicationTransactionTitle(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionTitle(
         $xaction);
     }
 
     $author_phid = $xaction->getAuthorPHID();
     return pht(
       '%s updated this object.',
       $xaction->renderHandleLink($author_phid));
   }
 
   public function getApplicationTransactionTitleForFeed(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionTitleForFeed(
         $xaction);
     }
 
     $author_phid = $xaction->getAuthorPHID();
     $object_phid = $xaction->getObjectPHID();
     return pht(
       '%s updated %s.',
       $xaction->renderHandleLink($author_phid),
       $xaction->renderHandleLink($object_phid));
   }
 
 
   public function getApplicationTransactionHasChangeDetails(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionHasChangeDetails(
         $xaction);
     }
     return false;
   }
 
   public function getApplicationTransactionChangeDetails(
     PhabricatorApplicationTransaction $xaction,
     PhabricatorUser $viewer) {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionChangeDetails(
         $xaction,
         $viewer);
     }
     return null;
   }
 
   public function getApplicationTransactionRequiredHandlePHIDs(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->getApplicationTransactionRequiredHandlePHIDs(
         $xaction);
     }
     return array();
   }
 
   public function shouldHideInApplicationTransactions(
     PhabricatorApplicationTransaction $xaction) {
     if ($this->proxy) {
       return $this->proxy->shouldHideInApplicationTransactions($xaction);
     }
     return false;
   }
 
 
 /* -(  Transaction Mail  )--------------------------------------------------- */
 
 
   /**
    * @task xactionmail
    */
   public function shouldAppearInTransactionMail() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInTransactionMail();
     }
     return false;
   }
 
 
   /**
    * @task xactionmail
    */
   public function updateTransactionMailBody(
     PhabricatorMetaMTAMailBody $body,
     PhabricatorApplicationTransactionEditor $editor,
     array $xactions) {
     if ($this->proxy) {
       return $this->proxy->updateTransactionMailBody($body, $editor, $xactions);
     }
     return;
   }
 
 
 /* -(  Edit View  )---------------------------------------------------------- */
 
 
   /**
    * @task edit
    */
   public function shouldAppearInEditView() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInEditView();
     }
     return false;
   }
 
 
   /**
    * @task edit
    */
   public function readValueFromRequest(AphrontRequest $request) {
     if ($this->proxy) {
       return $this->proxy->readValueFromRequest($request);
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * @task edit
    */
   public function getRequiredHandlePHIDsForEdit() {
     if ($this->proxy) {
       return $this->proxy->getRequiredHandlePHIDsForEdit();
     }
     return array();
   }
 
 
   /**
    * @task edit
    */
   public function getInstructionsForEdit() {
     if ($this->proxy) {
       return $this->proxy->getInstructionsForEdit();
     }
     return null;
   }
 
 
   /**
    * @task edit
    */
   public function renderEditControl(array $handles) {
     if ($this->proxy) {
       return $this->proxy->renderEditControl($handles);
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
 /* -(  Property View  )------------------------------------------------------ */
 
 
   /**
    * @task view
    */
   public function shouldAppearInPropertyView() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInPropertyView();
     }
     return false;
   }
 
 
   /**
    * @task view
    */
   public function renderPropertyViewLabel() {
     if ($this->proxy) {
       return $this->proxy->renderPropertyViewLabel();
     }
     return $this->getFieldName();
   }
 
 
   /**
    * @task view
    */
   public function renderPropertyViewValue(array $handles) {
     if ($this->proxy) {
       return $this->proxy->renderPropertyViewValue($handles);
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * @task view
    */
   public function getStyleForPropertyView() {
     if ($this->proxy) {
       return $this->proxy->getStyleForPropertyView();
     }
     return 'property';
   }
 
 
   /**
    * @task view
    */
   public function getIconForPropertyView() {
     if ($this->proxy) {
       return $this->proxy->getIconForPropertyView();
     }
     return null;
   }
 
 
   /**
    * @task view
    */
   public function getRequiredHandlePHIDsForPropertyView() {
     if ($this->proxy) {
       return $this->proxy->getRequiredHandlePHIDsForPropertyView();
     }
     return array();
   }
 
 
 /* -(  List View  )---------------------------------------------------------- */
 
 
   /**
    * @task list
    */
   public function shouldAppearInListView() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInListView();
     }
     return false;
   }
 
 
   /**
    * @task list
    */
   public function renderOnListItem(PHUIObjectItemView $view) {
     if ($this->proxy) {
       return $this->proxy->renderOnListItem($view);
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
 /* -(  Global Search  )------------------------------------------------------ */
 
 
   /**
    * @task globalsearch
    */
   public function shouldAppearInGlobalSearch() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInGlobalSearch();
     }
     return false;
   }
 
 
   /**
    * @task globalsearch
    */
   public function updateAbstractDocument(
     PhabricatorSearchAbstractDocument $document) {
     if ($this->proxy) {
       return $this->proxy->updateAbstractDocument($document);
     }
     return $document;
   }
 
 
 /* -(  Conduit  )------------------------------------------------------------ */
 
 
   /**
    * @task conduit
    */
   public function shouldAppearInConduitDictionary() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInConduitDictionary();
     }
     return false;
   }
 
 
   /**
    * @task conduit
    */
   public function getConduitDictionaryValue() {
     if ($this->proxy) {
       return $this->proxy->getConduitDictionaryValue();
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
 /* -(  Herald  )------------------------------------------------------------- */
 
 
   /**
    * Return `true` to make this field available in Herald.
    *
    * @return bool True to expose the field in Herald.
    * @task herald
    */
   public function shouldAppearInHerald() {
     if ($this->proxy) {
       return $this->proxy->shouldAppearInHerald();
     }
     return false;
   }
 
 
   /**
    * Get the name of the field in Herald. By default, this uses the
    * normal field name.
    *
    * @return string Herald field name.
    * @task herald
    */
   public function getHeraldFieldName() {
     if ($this->proxy) {
       return $this->proxy->getHeraldFieldName();
     }
     return $this->getFieldName();
   }
 
 
   /**
    * Get the field value for evaluation by Herald.
    *
    * @return wild Field value.
    * @task herald
    */
   public function getHeraldFieldValue() {
     if ($this->proxy) {
       return $this->proxy->getHeraldFieldValue();
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * Get the available conditions for this field in Herald.
    *
    * @return list<const> List of Herald condition constants.
    * @task herald
    */
   public function getHeraldFieldConditions() {
     if ($this->proxy) {
       return $this->proxy->getHeraldFieldConditions();
     }
     throw new PhabricatorCustomFieldImplementationIncompleteException($this);
   }
 
 
   /**
    * Get the Herald value type for the given condition.
    *
    * @param   const       Herald condition constant.
    * @return  const|null  Herald value type, or null to use the default.
    * @task herald
    */
   public function getHeraldFieldValueType($condition) {
     if ($this->proxy) {
       return $this->proxy->getHeraldFieldValueType($condition);
     }
     return null;
   }
 
 
 }
diff --git a/src/infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php b/src/infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php
index 36a8a5bd8b..068763527b 100644
--- a/src/infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php
+++ b/src/infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php
@@ -1,28 +1,30 @@
 <?php
 
 /**
  * Convenience class which simplifies the implementation of
  * @{interface:PhabricatorCustomFieldInterface} by obscuring the details of how
  * custom fields are stored.
  *
  * Generally, you should not use this class directly. It is used by
  * @{class:PhabricatorCustomField} to manage field storage on objects.
  */
 final class PhabricatorCustomFieldAttachment {
 
   private $lists = array();
 
   public function addCustomFieldList($role, PhabricatorCustomFieldList $list) {
     $this->lists[$role] = $list;
     return $this;
   }
 
   public function getCustomFieldList($role) {
     if (empty($this->lists[$role])) {
       throw new PhabricatorCustomFieldNotAttachedException(
-        "Role list '{$role}' is not available!");
+        pht(
+          "Role list '%s' is not available!",
+          $role));
     }
     return $this->lists[$role];
   }
 
 }
diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
index f226d0ad76..804e5b27bb 100644
--- a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
+++ b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
@@ -1,200 +1,200 @@
 <?php
 
 /**
  * Looks for Dxxxx, Txxxx and links to them.
  */
 final class PhabricatorBotObjectNameHandler extends PhabricatorBotHandler {
 
   /**
    * Map of PHIDs to the last mention of them (as an epoch timestamp); prevents
    * us from spamming chat when a single object is discussed.
    */
   private $recentlyMentioned = array();
 
   public function receiveMessage(PhabricatorBotMessage $original_message) {
     switch ($original_message->getCommand()) {
       case 'MESSAGE':
         $message = $original_message->getBody();
         $matches = null;
 
         $paste_ids = array();
         $commit_names = array();
         $vote_ids = array();
         $file_ids = array();
         $object_names = array();
         $output = array();
 
         $pattern =
           '@'.
           '(?<![/:#-])(?:^|\b)'.
           '(R2D2)'.
           '(?:\b|$)'.
           '@';
 
         if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
           foreach ($matches as $match) {
             switch ($match[1]) {
               case 'R2D2':
                 $output[$match[1]] = pht('beep boop bop');
                 break;
             }
           }
         }
 
         // Use a negative lookbehind to prevent matching "/D123", "#D123",
         // ":D123", etc.
         $pattern =
           '@'.
           '(?<![/:#-])(?:^|\b)'.
           '([A-Z])(\d+)'.
           '(?:\b|$)'.
           '@';
 
         if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
           foreach ($matches as $match) {
             switch ($match[1]) {
               case 'P':
                 $paste_ids[] = $match[2];
                 break;
               case 'V':
                 $vote_ids[] = $match[2];
                 break;
               case 'F':
                 $file_ids[] = $match[2];
                 break;
               default:
                 $name = $match[1].$match[2];
                 switch ($name) {
                   case 'T1000':
                     $output[$name] = pht(
                       'T1000: A mimetic poly-alloy assassin controlled by '.
                       'Skynet');
                     break;
                   default:
                     $object_names[] = $name;
                     break;
                 }
                 break;
             }
           }
         }
 
         $pattern =
           '@'.
           '(?<!/)(?:^|\b)'.
           '(r[A-Z]+)([0-9a-z]{0,40})'.
           '(?:\b|$)'.
           '@';
         if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
           foreach ($matches as $match) {
             if ($match[2]) {
               $commit_names[] = $match[1].$match[2];
             } else {
               $object_names[] = $match[1];
             }
           }
         }
 
         if ($object_names) {
           $objects = $this->getConduit()->callMethodSynchronous(
             'phid.lookup',
             array(
               'names' => $object_names,
             ));
           foreach ($objects as $object) {
             $output[$object['phid']] = $object['fullName'].' - '.$object['uri'];
           }
         }
 
         if ($vote_ids) {
           foreach ($vote_ids as $vote_id) {
             $vote = $this->getConduit()->callMethodSynchronous(
               'slowvote.info',
               array(
                 'poll_id' => $vote_id,
               ));
             $output[$vote['phid']] = 'V'.$vote['id'].': '.$vote['question'].
-              ' Come Vote '.$vote['uri'];
+              ' '.pht('Come Vote').' '.$vote['uri'];
           }
         }
 
         if ($file_ids) {
           foreach ($file_ids as $file_id) {
             $file = $this->getConduit()->callMethodSynchronous(
               'file.info',
               array(
                 'id' => $file_id,
               ));
             $output[$file['phid']] = $file['objectName'].': '.
               $file['uri'].' - '.$file['name'];
           }
         }
 
         if ($paste_ids) {
           foreach ($paste_ids as $paste_id) {
             $paste = $this->getConduit()->callMethodSynchronous(
               'paste.query',
               array(
                 'ids' => array($paste_id),
               ));
             $paste = head($paste);
 
             $output[$paste['phid']] = 'P'.$paste['id'].': '.$paste['uri'].' - '.
               $paste['title'];
 
             if ($paste['language']) {
               $output[$paste['phid']] .= ' ('.$paste['language'].')';
             }
 
             $user = $this->getConduit()->callMethodSynchronous(
               'user.query',
               array(
                 'phids' => array($paste['authorPHID']),
               ));
             $user = head($user);
             if ($user) {
               $output[$paste['phid']] .= ' by '.$user['userName'];
             }
           }
         }
 
         if ($commit_names) {
           $commits = $this->getConduit()->callMethodSynchronous(
             'diffusion.querycommits',
             array(
               'names' => $commit_names,
             ));
           foreach ($commits['data'] as $commit) {
             $output[$commit['phid']] = $commit['uri'];
           }
         }
 
         foreach ($output as $phid => $description) {
 
           // Don't mention the same object more than once every 10 minutes
           // in public channels, so we avoid spamming the chat over and over
           // again for discsussions of a specific revision, for example.
 
           $target_name = $original_message->getTarget()->getName();
           if (empty($this->recentlyMentioned[$target_name])) {
             $this->recentlyMentioned[$target_name] = array();
           }
 
           $quiet_until = idx(
             $this->recentlyMentioned[$target_name],
             $phid,
             0) + (60 * 10);
 
           if (time() < $quiet_until) {
             // Remain quiet on this channel.
             continue;
           }
 
           $this->recentlyMentioned[$target_name][$phid] = time();
           $this->replyTo($original_message, $description);
         }
         break;
     }
   }
 
 }
diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
index 88cc4808cc..2b54b9ab51 100644
--- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
+++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
@@ -1,55 +1,55 @@
 <?php
 
 final class PhabricatorTaskmasterDaemon extends PhabricatorDaemon {
 
   protected function run() {
     do {
       PhabricatorCaches::destroyRequestCache();
 
       $tasks = id(new PhabricatorWorkerLeaseQuery())
         ->setLimit(1)
         ->execute();
 
       if ($tasks) {
         $this->willBeginWork();
 
         foreach ($tasks as $task) {
           $id = $task->getID();
           $class = $task->getTaskClass();
 
           $this->log(pht('Working on task %d (%s)...', $id, $class));
 
           $task = $task->executeTask();
           $ex = $task->getExecutionException();
           if ($ex) {
             if ($ex instanceof PhabricatorWorkerPermanentFailureException) {
               throw new PhutilProxyException(
                 pht('Permanent failure while executing Task ID %d.', $id),
                 $ex);
             } else if ($ex instanceof PhabricatorWorkerYieldException) {
               $this->log(pht('Task %s yielded.', $id));
             } else {
-              $this->log("Task {$id} failed!");
+              $this->log(pht('Task %d failed!', $id));
               throw new PhutilProxyException(
                 pht('Error while executing Task ID %d.', $id),
                 $ex);
             }
           } else {
             $this->log(pht('Task %s complete! Moved to archive.', $id));
           }
         }
 
         $sleep = 0;
       } else {
         // When there's no work, sleep for one second. The pool will
         // autoscale down if we're continuously idle for an extended period
         // of time.
         $this->willBeginIdle();
         $sleep = 1;
       }
 
       $this->sleep($sleep);
     } while (!$this->shouldExit());
   }
 
 }
diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php
index 45f5832124..3826075276 100644
--- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php
+++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php
@@ -1,61 +1,62 @@
 <?php
 
 final class PhabricatorWorkerManagementExecuteWorkflow
   extends PhabricatorWorkerManagementWorkflow {
 
   protected function didConstruct() {
     $this
       ->setName('execute')
       ->setExamples('**execute** --id __id__')
       ->setSynopsis(
         pht(
           'Execute a task explicitly. This command ignores leases, is '.
           'dangerous, and may cause work to be performed twice.'))
       ->setArguments($this->getTaskSelectionArguments());
   }
 
   public function execute(PhutilArgumentParser $args) {
     $console = PhutilConsole::getConsole();
     $tasks = $this->loadTasks($args);
 
     foreach ($tasks as $task) {
       $can_execute = !$task->isArchived();
       if (!$can_execute) {
         $console->writeOut(
           "**<bg:yellow> %s </bg>** %s\n",
           pht('ARCHIVED'),
           pht(
             '%s is already archived, and can not be executed.',
             $this->describeTask($task)));
         continue;
       }
 
       // NOTE: This ignores leases, maybe it should respect them without
       // a parameter like --force?
 
       $task->setLeaseOwner(null);
       $task->setLeaseExpires(PhabricatorTime::getNow());
       $task->save();
 
       $task_data = id(new PhabricatorWorkerTaskData())->loadOneWhere(
         'id = %d',
         $task->getDataID());
       $task->setData($task_data->getData());
 
-      $id = $task->getID();
-      $class = $task->getTaskClass();
-
-      $console->writeOut("Executing task {$id} ({$class})...");
+      $console->writeOut(
+        pht(
+          'Executing task %d (%s)...',
+          $task->getID(),
+          $task->getTaskClass()));
 
       $task = $task->executeTask();
       $ex = $task->getExecutionException();
 
       if ($ex) {
         throw $ex;
       }
     }
 
     return 0;
   }
 
 }
diff --git a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php
index 8d4a10c853..bff0331c02 100644
--- a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php
+++ b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php
@@ -1,304 +1,304 @@
 <?php
 
 final class PhabricatorStorageManagementAPI {
 
   private $host;
   private $user;
   private $port;
   private $password;
   private $namespace;
   private $conns = array();
   private $disableUTF8MB4;
 
   const CHARSET_DEFAULT = 'CHARSET';
   const CHARSET_SORT = 'CHARSET_SORT';
   const CHARSET_FULLTEXT = 'CHARSET_FULLTEXT';
   const COLLATE_TEXT = 'COLLATE_TEXT';
   const COLLATE_SORT = 'COLLATE_SORT';
   const COLLATE_FULLTEXT = 'COLLATE_FULLTEXT';
 
   public function setDisableUTF8MB4($disable_utf8_mb4) {
     $this->disableUTF8MB4 = $disable_utf8_mb4;
     return $this;
   }
 
   public function getDisableUTF8MB4() {
     return $this->disableUTF8MB4;
   }
 
   public function setNamespace($namespace) {
     $this->namespace = $namespace;
     PhabricatorLiskDAO::pushStorageNamespace($namespace);
     return $this;
   }
 
   public function getNamespace() {
     return $this->namespace;
   }
 
   public function setUser($user) {
     $this->user = $user;
     return $this;
   }
 
   public function getUser() {
     return $this->user;
   }
 
   public function setPassword($password) {
     $this->password = $password;
     return $this;
   }
 
   public function getPassword() {
     return $this->password;
   }
 
   public function setHost($host) {
     $this->host = $host;
     return $this;
   }
 
   public function getHost() {
     return $this->host;
   }
 
   public function setPort($port) {
     $this->port = $port;
     return $this;
   }
 
   public function getPort() {
     return $this->port;
   }
 
   public function getDatabaseName($fragment) {
     return $this->namespace.'_'.$fragment;
   }
 
   public function getDatabaseList(array $patches, $only_living = false) {
     assert_instances_of($patches, 'PhabricatorStoragePatch');
 
     $list = array();
 
     foreach ($patches as $patch) {
       if ($patch->getType() == 'db') {
         if ($only_living && $patch->isDead()) {
           continue;
         }
         $list[] = $this->getDatabaseName($patch->getName());
       }
     }
 
     return $list;
   }
 
   public function getConn($fragment) {
     $database = $this->getDatabaseName($fragment);
     $return = &$this->conns[$this->host][$this->user][$database];
     if (!$return) {
       $return = PhabricatorEnv::newObjectFromConfig(
       'mysql.implementation',
       array(
         array(
           'user'      => $this->user,
           'pass'      => $this->password,
           'host'      => $this->host,
           'port'      => $this->port,
           'database'  => $fragment
             ? $database
             : null,
         ),
       ));
     }
     return $return;
   }
 
   public function getAppliedPatches() {
     try {
       $applied = queryfx_all(
         $this->getConn('meta_data'),
         'SELECT patch FROM patch_status');
       return ipull($applied, 'patch');
     } catch (AphrontQueryException $ex) {
       return null;
     }
   }
 
   public function createDatabase($fragment) {
     $info = $this->getCharsetInfo();
 
     queryfx(
       $this->getConn(null),
       'CREATE DATABASE IF NOT EXISTS %T COLLATE %T',
       $this->getDatabaseName($fragment),
       $info[self::COLLATE_TEXT]);
   }
 
   public function createTable($fragment, $table, array $cols) {
     queryfx(
       $this->getConn($fragment),
       'CREATE TABLE IF NOT EXISTS %T.%T (%Q) '.
       'ENGINE=InnoDB, COLLATE utf8_general_ci',
       $this->getDatabaseName($fragment),
       $table,
       implode(', ', $cols));
   }
 
   public function getLegacyPatches(array $patches) {
     assert_instances_of($patches, 'PhabricatorStoragePatch');
 
     try {
       $row = queryfx_one(
         $this->getConn('meta_data'),
         'SELECT version FROM %T',
         'schema_version');
       $version = $row['version'];
     } catch (AphrontQueryException $ex) {
       return array();
     }
 
     $legacy = array();
     foreach ($patches as $key => $patch) {
       if ($patch->getLegacy() !== false && $patch->getLegacy() <= $version) {
         $legacy[] = $key;
       }
     }
 
     return $legacy;
   }
 
   public function markPatchApplied($patch) {
     queryfx(
       $this->getConn('meta_data'),
       'INSERT INTO %T (patch, applied) VALUES (%s, %d)',
       'patch_status',
       $patch,
       time());
   }
 
   public function applyPatch(PhabricatorStoragePatch $patch) {
     $type = $patch->getType();
     $name = $patch->getName();
     switch ($type) {
       case 'db':
         $this->createDatabase($name);
         break;
       case 'sql':
         $this->applyPatchSQL($name);
         break;
       case 'php':
         $this->applyPatchPHP($name);
         break;
       default:
-        throw new Exception("Unable to apply patch of type '{$type}'.");
+        throw new Exception(pht("Unable to apply patch of type '%s'.", $type));
     }
   }
 
   public function applyPatchSQL($sql) {
     $sql = Filesystem::readFile($sql);
     $queries = preg_split('/;\s+/', $sql);
     $queries = array_filter($queries);
 
     $conn = $this->getConn(null);
 
     $charset_info = $this->getCharsetInfo();
     foreach ($charset_info as $key => $value) {
       $charset_info[$key] = qsprintf($conn, '%T', $value);
     }
 
     foreach ($queries as $query) {
       $query = str_replace('{$NAMESPACE}', $this->namespace, $query);
 
       foreach ($charset_info as $key => $value) {
         $query = str_replace('{$'.$key.'}', $value, $query);
       }
 
       queryfx(
         $conn,
         '%Q',
         $query);
     }
   }
 
   public function applyPatchPHP($script) {
     $schema_conn = $this->getConn(null);
     require_once $script;
   }
 
   public function isCharacterSetAvailable($character_set) {
     if ($character_set == 'utf8mb4') {
       if ($this->getDisableUTF8MB4()) {
         return false;
       }
     }
 
     $conn = $this->getConn(null);
     return self::isCharacterSetAvailableOnConnection($character_set, $conn);
   }
 
   public static function isCharacterSetAvailableOnConnection(
     $character_set,
     AphrontDatabaseConnection $conn) {
     $result = queryfx_one(
       $conn,
       'SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.CHARACTER_SETS
         WHERE CHARACTER_SET_NAME = %s',
       $character_set);
 
     return (bool)$result;
   }
 
   public function getCharsetInfo() {
     if ($this->isCharacterSetAvailable('utf8mb4')) {
       // If utf8mb4 is available, we use it with the utf8mb4_unicode_ci
       // collation. This is most correct, and will sort properly.
 
       $charset = 'utf8mb4';
       $charset_sort = 'utf8mb4';
       $charset_full = 'utf8mb4';
       $collate_text = 'utf8mb4_bin';
       $collate_sort = 'utf8mb4_unicode_ci';
       $collate_full = 'utf8mb4_unicode_ci';
     } else {
       // If utf8mb4 is not available, we use binary for most data. This allows
       // us to store 4-byte unicode characters.
       //
       // It's possible that strings will be truncated in the middle of a
       // character on insert. We encourage users to set STRICT_ALL_TABLES
       // to prevent this.
       //
       // For "fulltext" and "sort" columns, we don't use binary.
       //
       // With "fulltext", we can not use binary because MySQL won't let us.
       // We use 3-byte utf8 instead and accept being unable to index 4-byte
       // characters.
       //
       // With "sort", if we use binary we lose case insensitivity (for
       // example, "ALincoln@logcabin.com" and "alincoln@logcabin.com" would no
       // longer be identified as the same email address). This can be very
       // confusing and is far worse overall than not supporting 4-byte unicode
       // characters, so we use 3-byte utf8 and accept limited 4-byte support as
       // a tradeoff to get sensible collation behavior. Many columns where
       // collation is important rarely contain 4-byte characters anyway, so we
       // are not giving up too much.
 
       $charset = 'binary';
       $charset_sort = 'utf8';
       $charset_full = 'utf8';
       $collate_text = 'binary';
       $collate_sort = 'utf8_general_ci';
       $collate_full = 'utf8_general_ci';
     }
 
     return array(
       self::CHARSET_DEFAULT => $charset,
       self::CHARSET_SORT => $charset_sort,
       self::CHARSET_FULLTEXT => $charset_full,
       self::COLLATE_TEXT => $collate_text,
       self::COLLATE_SORT => $collate_sort,
       self::COLLATE_FULLTEXT => $collate_full,
     );
   }
 
 }
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php
index f6cf6ad473..55cf3b223e 100644
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php
@@ -1,225 +1,226 @@
 <?php
 
 final class PhabricatorStorageManagementUpgradeWorkflow
   extends PhabricatorStorageManagementWorkflow {
 
   protected function didConstruct() {
     $this
       ->setName('upgrade')
       ->setExamples('**upgrade** [__options__]')
       ->setSynopsis(pht('Upgrade database schemata.'))
       ->setArguments(
         array(
           array(
             'name'  => 'apply',
             'param' => 'patch',
             'help'  => pht(
               'Apply __patch__ explicitly. This is an advanced feature for '.
               'development and debugging; you should not normally use this '.
               'flag. This skips adjustment.'),
           ),
           array(
             'name'  => 'no-quickstart',
             'help'  => pht(
               'Build storage patch-by-patch from scatch, even if it could '.
               'be loaded from the quickstart template.'),
           ),
           array(
             'name'  => 'init-only',
             'help'  => pht(
               'Initialize storage only; do not apply patches or adjustments.'),
           ),
           array(
             'name' => 'no-adjust',
             'help' => pht(
               'Do not apply storage adjustments after storage upgrades.'),
           ),
         ));
   }
 
   public function execute(PhutilArgumentParser $args) {
     $is_dry = $args->getArg('dryrun');
     $is_force = $args->getArg('force');
 
     $api = $this->getAPI();
     $patches = $this->getPatches();
 
     if (!$is_dry && !$is_force) {
       echo phutil_console_wrap(
         pht(
           'Before running storage upgrades, you should take down the '.
           'Phabricator web interface and stop any running Phabricator '.
           'daemons (you can disable this warning with %s).',
           '--force'));
 
       if (!phutil_console_confirm(pht('Are you ready to continue?'))) {
         echo pht('Cancelled.')."\n";
         return 1;
       }
     }
 
     $apply_only = $args->getArg('apply');
     if ($apply_only) {
       if (empty($patches[$apply_only])) {
         throw new PhutilArgumentUsageException(
           pht(
             "%s argument '%s' is not a valid patch. ".
             "Use '%s' to show patch status.",
             '--apply',
             $apply_only,
             'storage status'));
       }
     }
 
     $no_quickstart = $args->getArg('no-quickstart');
     $init_only = $args->getArg('init-only');
     $no_adjust = $args->getArg('no-adjust');
 
     $applied = $api->getAppliedPatches();
     if ($applied === null) {
 
       if ($is_dry) {
         echo pht(
           "DRYRUN: Patch metadata storage doesn't exist yet, ".
           "it would be created.\n");
         return 0;
       }
 
       if ($apply_only) {
         throw new PhutilArgumentUsageException(
           pht(
             'Storage has not been initialized yet, you must initialize '.
             'storage before selectively applying patches.'));
         return 1;
       }
 
       $legacy = $api->getLegacyPatches($patches);
       if ($legacy || $no_quickstart || $init_only) {
 
         // If we have legacy patches, we can't quickstart.
 
         $api->createDatabase('meta_data');
         $api->createTable(
           'meta_data',
           'patch_status',
           array(
             'patch VARCHAR(255) NOT NULL PRIMARY KEY COLLATE utf8_general_ci',
             'applied INT UNSIGNED NOT NULL',
           ));
 
         foreach ($legacy as $patch) {
           $api->markPatchApplied($patch);
         }
       } else {
         echo pht('Loading quickstart template...')."\n";
         $root = dirname(phutil_get_library_root('phabricator'));
         $sql  = $root.'/resources/sql/quickstart.sql';
         $api->applyPatchSQL($sql);
       }
     }
 
     if ($init_only) {
       echo pht('Storage initialized.')."\n";
       return 0;
     }
 
     $applied = $api->getAppliedPatches();
     $applied = array_fuse($applied);
 
     $skip_mark = false;
     if ($apply_only) {
       if (isset($applied[$apply_only])) {
 
         unset($applied[$apply_only]);
         $skip_mark = true;
 
         if (!$is_force && !$is_dry) {
           echo phutil_console_wrap(
             pht(
               "Patch '%s' has already been applied. Are you sure you want ".
               "to apply it again? This may put your storage in a state ".
               "that the upgrade scripts can not automatically manage.",
               $apply_only));
           if (!phutil_console_confirm(pht('Apply patch again?'))) {
             echo pht('Cancelled.')."\n";
             return 1;
           }
         }
       }
     }
 
     while (true) {
       $applied_something = false;
       foreach ($patches as $key => $patch) {
         if (isset($applied[$key])) {
           unset($patches[$key]);
           continue;
         }
 
         if ($apply_only && $apply_only != $key) {
           unset($patches[$key]);
           continue;
         }
 
         $can_apply = true;
         foreach ($patch->getAfter() as $after) {
           if (empty($applied[$after])) {
             if ($apply_only) {
               echo pht(
                 "Unable to apply patch '%s' because it depends ".
                 "on patch '%s', which has not been applied.\n",
                 $apply_only,
                 $after);
               return 1;
             }
             $can_apply = false;
             break;
           }
         }
 
         if (!$can_apply) {
           continue;
         }
 
         $applied_something = true;
 
         if ($is_dry) {
           echo pht("DRYRUN: Would apply patch '%s'.", $key)."\n";
         } else {
           echo pht("Applying patch '%s'...", $key)."\n";
           $api->applyPatch($patch);
           if (!$skip_mark) {
             $api->markPatchApplied($key);
           }
         }
 
         unset($patches[$key]);
         $applied[$key] = true;
       }
 
       if (!$applied_something) {
         if (count($patches)) {
           throw new Exception(
-            'Some patches could not be applied: '.
-            implode(', ', array_keys($patches)));
+            pht(
+              'Some patches could not be applied: %s',
+              implode(', ', array_keys($patches))));
         } else if (!$is_dry && !$apply_only) {
           echo pht(
-            "Storage is up to date. Use '%s' for details.\n",
-            'storage status');
+            "Storage is up to date. Use '%s' for details.",
+            'storage status')."\n";
         }
         break;
       }
     }
 
     $console = PhutilConsole::getConsole();
     if ($no_adjust || $init_only || $apply_only) {
       $console->writeOut(
         "%s\n",
         pht('Declining to apply storage adjustments.'));
       return 0;
     } else {
       return $this->adjustSchemata($is_force, $unsafe = false, $is_dry);
     }
   }
 
 }
diff --git a/src/infrastructure/testing/PhabricatorTestCase.php b/src/infrastructure/testing/PhabricatorTestCase.php
index ad88ca360f..32fe4f3bee 100644
--- a/src/infrastructure/testing/PhabricatorTestCase.php
+++ b/src/infrastructure/testing/PhabricatorTestCase.php
@@ -1,227 +1,229 @@
 <?php
 
 abstract class PhabricatorTestCase extends PhutilTestCase {
 
   const NAMESPACE_PREFIX = 'phabricator_unittest_';
 
   /**
    * If true, put Lisk in process-isolated mode for the duration of the tests so
    * that it will establish only isolated, side-effect-free database
    * connections. Defaults to true.
    *
    * NOTE: You should disable this only in rare circumstances. Unit tests should
    * not rely on external resources like databases, and should not produce
    * side effects.
    */
   const PHABRICATOR_TESTCONFIG_ISOLATE_LISK           = 'isolate-lisk';
 
   /**
    * If true, build storage fixtures before running tests, and connect to them
    * during test execution. This will impose a performance penalty on test
    * execution (currently, it takes roughly one second to build the fixture)
    * but allows you to perform tests which require data to be read from storage
    * after writes. The fixture is shared across all test cases in this process.
    * Defaults to false.
    *
    * NOTE: All connections to fixture storage open transactions when established
    * and roll them back when tests complete. Each test must independently
    * write data it relies on; data will not persist across tests.
    *
    * NOTE: Enabling this implies disabling process isolation.
    */
   const PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES = 'storage-fixtures';
 
   private $configuration;
   private $env;
 
   private static $storageFixtureReferences = 0;
   private static $storageFixture;
   private static $storageFixtureObjectSeed = 0;
   private static $testsAreRunning = 0;
 
   protected function getPhabricatorTestCaseConfiguration() {
     return array();
   }
 
   private function getComputedConfiguration() {
     $config = $this->getPhabricatorTestCaseConfiguration() + array(
       self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK             => true,
       self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES   => false,
     );
 
     if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {
       // Fixtures don't make sense with process isolation.
       $config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK] = false;
     }
 
     return $config;
   }
 
   public function willRunTestCases(array $test_cases) {
     $root = dirname(phutil_get_library_root('phabricator'));
     require_once $root.'/scripts/__init_script__.php';
 
     $config = $this->getComputedConfiguration();
 
     if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {
       ++self::$storageFixtureReferences;
       if (!self::$storageFixture) {
         self::$storageFixture = $this->newStorageFixture();
       }
     }
 
     ++self::$testsAreRunning;
   }
 
   public function didRunTestCases(array $test_cases) {
     if (self::$storageFixture) {
       self::$storageFixtureReferences--;
       if (!self::$storageFixtureReferences) {
         self::$storageFixture = null;
       }
     }
 
     --self::$testsAreRunning;
   }
 
   protected function willRunTests() {
     $config = $this->getComputedConfiguration();
 
     if ($config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK]) {
       LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess();
     }
 
     $this->env = PhabricatorEnv::beginScopedEnv();
 
     // NOTE: While running unit tests, we act as though all applications are
     // installed, regardless of the install's configuration. Tests which need
     // to uninstall applications are responsible for adjusting state themselves
     // (such tests are exceedingly rare).
 
     $this->env->overrideEnvConfig(
       'phabricator.uninstalled-applications',
       array());
     $this->env->overrideEnvConfig(
       'phabricator.show-prototypes',
       true);
 
     // Reset application settings to defaults, particularly policies.
     $this->env->overrideEnvConfig(
       'phabricator.application-settings',
       array());
 
     // We can't stub this service right now, and it's not generally useful
     // to publish notifications about test execution.
     $this->env->overrideEnvConfig(
       'notification.enabled',
       false);
 
     $this->env->overrideEnvConfig(
       'phabricator.base-uri',
       'http://phabricator.example.com');
 
     // Tests do their own stubbing/voiding for events.
     $this->env->overrideEnvConfig('phabricator.silent', false);
   }
 
   protected function didRunTests() {
     $config = $this->getComputedConfiguration();
 
     if ($config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK]) {
       LiskDAO::endIsolateAllLiskEffectsToCurrentProcess();
     }
 
     try {
       if (phutil_is_hiphop_runtime()) {
         $this->env->__destruct();
       }
       unset($this->env);
     } catch (Exception $ex) {
       throw new Exception(
         pht(
           'Some test called %s, but is still holding '.
           'a reference to the scoped environment!',
           'PhabricatorEnv::beginScopedEnv()'));
     }
   }
 
   protected function willRunOneTest($test) {
     $config = $this->getComputedConfiguration();
 
     if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {
       LiskDAO::beginIsolateAllLiskEffectsToTransactions();
     }
   }
 
   protected function didRunOneTest($test) {
     $config = $this->getComputedConfiguration();
 
     if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {
       LiskDAO::endIsolateAllLiskEffectsToTransactions();
     }
   }
 
   protected function newStorageFixture() {
     $bytes = Filesystem::readRandomCharacters(24);
     $name = self::NAMESPACE_PREFIX.$bytes;
 
     return new PhabricatorStorageFixtureScopeGuard($name);
   }
 
   /**
    * Returns an integer seed to use when building unique identifiers (e.g.,
    * non-colliding usernames). The seed is unstable and its value will change
    * between test runs, so your tests must not rely on it.
    *
    * @return int A unique integer.
    */
   protected function getNextObjectSeed() {
     self::$storageFixtureObjectSeed += mt_rand(1, 100);
     return self::$storageFixtureObjectSeed;
   }
 
   protected function generateNewTestUser() {
     $seed = $this->getNextObjectSeed();
 
     $user = id(new PhabricatorUser())
-      ->setRealName("Test User {$seed}}")
+      ->setRealName(pht('Test User %s', $seed))
       ->setUserName("test{$seed}")
       ->setIsApproved(1);
 
     $email = id(new PhabricatorUserEmail())
       ->setAddress("testuser{$seed}@example.com")
       ->setIsVerified(1);
 
     $editor = new PhabricatorUserEditor();
     $editor->setActor($user);
     $editor->createNewUser($user, $email);
 
     return $user;
   }
 
 
   /**
    * Throws unless tests are currently executing. This method can be used to
    * guard code which is specific to unit tests and should not normally be
    * reachable.
    *
    * If tests aren't currently being executed, throws an exception.
    */
   public static function assertExecutingUnitTests() {
     if (!self::$testsAreRunning) {
       throw new Exception(
         pht(
-          'Executing test code outside of test execution! This code path can '.
-          'only be run during unit tests.'));
+          'Executing test code outside of test execution! '.
+          'This code path can only be run during unit tests.'));
     }
   }
 
   protected function requireBinaryForTest($binary) {
     if (!Filesystem::binaryExists($binary)) {
       $this->assertSkipped(
-        pht('No binary "%s" found on this system, skipping test.', $binary));
+        pht(
+          'No binary "%s" found on this system, skipping test.',
+          $binary));
     }
   }
 
 
 }
diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php
index b551c14f35..b2811717db 100644
--- a/src/view/AphrontDialogView.php
+++ b/src/view/AphrontDialogView.php
@@ -1,358 +1,361 @@
 <?php
 
 final class AphrontDialogView extends AphrontView {
 
   private $title;
   private $shortTitle;
   private $submitButton;
   private $cancelURI;
   private $cancelText = 'Cancel';
   private $submitURI;
   private $hidden = array();
   private $class;
   private $renderAsForm = true;
   private $formID;
   private $headerColor = PHUIActionHeaderView::HEADER_LIGHTBLUE;
   private $footers = array();
   private $isStandalone;
   private $method = 'POST';
   private $disableWorkflowOnSubmit;
   private $disableWorkflowOnCancel;
   private $width      = 'default';
   private $errors = array();
   private $flush;
   private $validationException;
 
 
   const WIDTH_DEFAULT = 'default';
   const WIDTH_FORM    = 'form';
   const WIDTH_FULL    = 'full';
 
   public function setMethod($method) {
     $this->method = $method;
     return $this;
   }
 
   public function setIsStandalone($is_standalone) {
     $this->isStandalone = $is_standalone;
     return $this;
   }
 
   public function setErrors(array $errors) {
     $this->errors = $errors;
     return $this;
   }
 
   public function getIsStandalone() {
     return $this->isStandalone;
   }
 
   public function setSubmitURI($uri) {
     $this->submitURI = $uri;
     return $this;
   }
 
   public function setTitle($title) {
     $this->title = $title;
     return $this;
   }
 
   public function getTitle() {
     return $this->title;
   }
 
   public function setShortTitle($short_title) {
     $this->shortTitle = $short_title;
     return $this;
   }
 
   public function getShortTitle() {
     return $this->shortTitle;
   }
 
   public function addSubmitButton($text = null) {
     if (!$text) {
       $text = pht('Okay');
     }
 
     $this->submitButton = $text;
     return $this;
   }
 
   public function addCancelButton($uri, $text = null) {
     if (!$text) {
       $text = pht('Cancel');
     }
 
     $this->cancelURI = $uri;
     $this->cancelText = $text;
     return $this;
   }
 
   public function addFooter($footer) {
     $this->footers[] = $footer;
     return $this;
   }
 
   public function addHiddenInput($key, $value) {
     if (is_array($value)) {
       foreach ($value as $hidden_key => $hidden_value) {
         $this->hidden[] = array($key.'['.$hidden_key.']', $hidden_value);
       }
     } else {
       $this->hidden[] = array($key, $value);
     }
     return $this;
   }
 
   public function setClass($class) {
     $this->class = $class;
     return $this;
   }
 
   public function setFlush($flush) {
     $this->flush = $flush;
     return $this;
   }
 
   public function setRenderDialogAsDiv() {
     // TODO: This API is awkward.
     $this->renderAsForm = false;
     return $this;
   }
 
   public function setFormID($id) {
     $this->formID = $id;
     return $this;
   }
 
   public function setWidth($width) {
     $this->width = $width;
     return $this;
   }
 
   public function setHeaderColor($color) {
     $this->headerColor = $color;
     return $this;
   }
 
   public function appendParagraph($paragraph) {
     return $this->appendChild(
       phutil_tag(
         'p',
         array(
           'class' => 'aphront-dialog-view-paragraph',
         ),
         $paragraph));
   }
 
   public function appendForm(AphrontFormView $form) {
     return $this->appendChild($form->buildLayoutView());
   }
 
   public function setDisableWorkflowOnSubmit($disable_workflow_on_submit) {
     $this->disableWorkflowOnSubmit = $disable_workflow_on_submit;
     return $this;
   }
 
   public function getDisableWorkflowOnSubmit() {
     return $this->disableWorkflowOnSubmit;
   }
 
   public function setDisableWorkflowOnCancel($disable_workflow_on_cancel) {
     $this->disableWorkflowOnCancel = $disable_workflow_on_cancel;
     return $this;
   }
 
   public function getDisableWorkflowOnCancel() {
     return $this->disableWorkflowOnCancel;
   }
 
   public function setValidationException(
     PhabricatorApplicationTransactionValidationException $ex = null) {
     $this->validationException = $ex;
     return $this;
   }
 
   public function render() {
     require_celerity_resource('aphront-dialog-view-css');
 
     $buttons = array();
     if ($this->submitButton) {
       $meta = array();
       if ($this->disableWorkflowOnSubmit) {
         $meta['disableWorkflow'] = true;
       }
 
       $buttons[] = javelin_tag(
         'button',
         array(
           'name' => '__submit__',
           'sigil' => '__default__',
           'type' => 'submit',
           'meta' => $meta,
         ),
         $this->submitButton);
     }
 
     if ($this->cancelURI) {
       $meta = array();
       if ($this->disableWorkflowOnCancel) {
         $meta['disableWorkflow'] = true;
       }
 
       $buttons[] = javelin_tag(
         'a',
         array(
           'href'  => $this->cancelURI,
           'class' => 'button grey',
           'name'  => '__cancel__',
           'sigil' => 'jx-workflow-button',
           'meta' => $meta,
         ),
         $this->cancelText);
     }
 
     if (!$this->user) {
       throw new Exception(
         pht(
           'You must call %s when rendering an %s.',
           'setUser()',
           __CLASS__));
     }
 
     $more = $this->class;
     if ($this->flush) {
       $more .= ' aphront-dialog-flush';
     }
 
     switch ($this->width) {
       case self::WIDTH_FORM:
       case self::WIDTH_FULL:
         $more .= ' aphront-dialog-view-width-'.$this->width;
         break;
       case self::WIDTH_DEFAULT:
         break;
       default:
-        throw new Exception("Unknown dialog width '{$this->width}'!");
+        throw new Exception(
+          pht(
+            "Unknown dialog width '%s'!",
+            $this->width));
     }
 
     if ($this->isStandalone) {
       $more .= ' aphront-dialog-view-standalone';
     }
 
     $attributes = array(
       'class'   => 'aphront-dialog-view '.$more,
       'sigil'   => 'jx-dialog',
     );
 
     $form_attributes = array(
       'action'  => $this->submitURI,
       'method'  => $this->method,
       'id'      => $this->formID,
     );
 
     $hidden_inputs = array();
     $hidden_inputs[] = phutil_tag(
       'input',
       array(
         'type' => 'hidden',
         'name' => '__dialog__',
         'value' => '1',
       ));
 
     foreach ($this->hidden as $desc) {
       list($key, $value) = $desc;
       $hidden_inputs[] = javelin_tag(
         'input',
         array(
           'type' => 'hidden',
           'name' => $key,
           'value' => $value,
           'sigil' => 'aphront-dialog-application-input',
         ));
     }
 
     if (!$this->renderAsForm) {
       $buttons = array(phabricator_form(
         $this->user,
         $form_attributes,
         array_merge($hidden_inputs, $buttons)),
       );
     }
 
     $children = $this->renderChildren();
 
     $errors = $this->errors;
 
     $ex = $this->validationException;
     $exception_errors = null;
     if ($ex) {
       foreach ($ex->getErrors() as $error) {
         $errors[] = $error->getMessage();
       }
     }
 
     if ($errors) {
       $children = array(
         id(new PHUIInfoView())->setErrors($errors),
         $children,
       );
     }
 
     $header = new PHUIActionHeaderView();
     $header->setHeaderTitle($this->title);
     $header->setHeaderColor($this->headerColor);
 
     $footer = null;
     if ($this->footers) {
       $footer = phutil_tag(
         'div',
         array(
           'class' => 'aphront-dialog-foot',
         ),
         $this->footers);
     }
 
     $tail = null;
     if ($buttons || $footer) {
       $tail = phutil_tag(
         'div',
         array(
           'class' => 'aphront-dialog-tail grouped',
         ),
         array(
           $buttons,
           $footer,
         ));
     }
 
     $content = array(
       phutil_tag(
         'div',
         array(
           'class' => 'aphront-dialog-head',
         ),
         $header),
       phutil_tag('div',
         array(
           'class' => 'aphront-dialog-body grouped',
         ),
         $children),
       $tail,
     );
 
     if ($this->renderAsForm) {
       return phabricator_form(
         $this->user,
         $form_attributes + $attributes,
         array($hidden_inputs, $content));
     } else {
       return javelin_tag(
         'div',
         $attributes,
         $content);
     }
   }
 
 }
diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php
index 7a742efaf1..6f826cf830 100644
--- a/src/view/form/control/AphrontFormDateControl.php
+++ b/src/view/form/control/AphrontFormDateControl.php
@@ -1,339 +1,339 @@
 <?php
 
 final class AphrontFormDateControl extends AphrontFormControl {
 
   private $initialTime;
   private $zone;
 
   private $valueDate;
   private $valueTime;
   private $allowNull;
   private $continueOnInvalidDate = false;
   private $isTimeDisabled;
   private $isDisabled;
   private $endDateID;
 
   public function setAllowNull($allow_null) {
     $this->allowNull = $allow_null;
     return $this;
   }
 
   public function setIsTimeDisabled($is_disabled) {
     $this->isTimeDisabled = $is_disabled;
     return $this;
   }
 
   public function setIsDisabled($is_datepicker_disabled) {
     $this->isDisabled = $is_datepicker_disabled;
     return $this;
   }
 
   public function setEndDateID($value) {
     $this->endDateID = $value;
     return $this;
   }
 
   const TIME_START_OF_DAY         = 'start-of-day';
   const TIME_END_OF_DAY           = 'end-of-day';
   const TIME_START_OF_BUSINESS    = 'start-of-business';
   const TIME_END_OF_BUSINESS      = 'end-of-business';
 
   public function setInitialTime($time) {
     $this->initialTime = $time;
     return $this;
   }
 
   public function readValueFromRequest(AphrontRequest $request) {
     $date = $request->getStr($this->getDateInputName());
     $time = $request->getStr($this->getTimeInputName());
     $enabled = $request->getBool($this->getCheckboxInputName());
 
     if ($this->allowNull && !$enabled) {
       $this->setError(null);
       $this->setValue(null);
       return;
     }
 
     $err = $this->getError();
 
     if ($date || $time) {
       $this->valueDate = $date;
       $this->valueTime = $time;
 
       // Assume invalid.
-      $err = 'Invalid';
+      $err = pht('Invalid');
 
       $zone = $this->getTimezone();
 
       try {
         $datetime = new DateTime("{$date} {$time}", $zone);
         $value = $datetime->format('U');
       } catch (Exception $ex) {
         $value = null;
       }
 
       if ($value) {
         $this->setValue($value);
         $err = null;
       } else {
         $this->setValue(null);
       }
     } else {
       $value = $this->getInitialValue();
       if ($value) {
         $this->setValue($value);
       } else {
         $this->setValue(null);
       }
     }
 
     $this->setError($err);
 
     return $this->getValue();
   }
 
   protected function getCustomControlClass() {
     return 'aphront-form-control-date';
   }
 
   public function setValue($epoch) {
     if ($epoch instanceof AphrontFormDateControlValue) {
       $this->continueOnInvalidDate = true;
       $this->valueDate = $epoch->getValueDate();
       $this->valueTime  = $epoch->getValueTime();
       $this->allowNull = $epoch->getOptional();
       $this->isDisabled = $epoch->isDisabled();
 
       return parent::setValue($epoch->getEpoch());
     }
 
     $result = parent::setValue($epoch);
 
     if ($epoch === null) {
       return $result;
     }
 
     $readable = $this->formatTime($epoch, 'Y!m!d!g:i A');
     $readable = explode('!', $readable, 4);
 
     $year  = $readable[0];
     $month = $readable[1];
     $day   = $readable[2];
 
     $this->valueDate = $month.'/'.$day.'/'.$year;
     $this->valueTime  = $readable[3];
 
     return $result;
   }
 
   private function getDateInputValue() {
     return $this->valueDate;
   }
 
   private function getTimeInputValue() {
     return $this->valueTime;
   }
 
   private function formatTime($epoch, $fmt) {
     return phabricator_format_local_time(
       $epoch,
       $this->user,
       $fmt);
   }
 
   private function getDateInputName() {
     return $this->getName().'_d';
   }
 
   private function getTimeInputName() {
     return $this->getName().'_t';
   }
 
   private function getCheckboxInputName() {
     return $this->getName().'_e';
   }
 
   protected function renderInput() {
 
     $disabled = null;
     if ($this->getValue() === null && !$this->continueOnInvalidDate) {
       $this->setValue($this->getInitialValue());
       if ($this->allowNull) {
         $disabled = 'disabled';
       }
     }
 
     if ($this->isDisabled) {
       $disabled = 'disabled';
     }
 
     $checkbox = null;
     if ($this->allowNull) {
       $checkbox = javelin_tag(
         'input',
         array(
           'type' => 'checkbox',
           'name' => $this->getCheckboxInputName(),
           'sigil' => 'calendar-enable',
           'class' => 'aphront-form-date-enabled-input',
           'value' => 1,
           'checked' => ($disabled === null ? 'checked' : null),
         ));
     }
 
     $date_sel = javelin_tag(
       'input',
       array(
         'autocomplete' => 'off',
         'name'  => $this->getDateInputName(),
         'sigil' => 'date-input',
         'value' => $this->getDateInputValue(),
         'type'  => 'text',
         'class' => 'aphront-form-date-input',
       ),
       '');
 
     $date_div = javelin_tag(
       'div',
       array(
         'class' => 'aphront-form-date-input-container',
       ),
       $date_sel);
 
     $cicon = id(new PHUIIconView())
       ->setIconFont('fa-calendar');
 
     $cal_icon = javelin_tag(
       'a',
       array(
         'href'  => '#',
         'class' => 'calendar-button',
         'sigil' => 'calendar-button',
       ),
       $cicon);
 
     $values = $this->getTimeTypeaheadValues();
 
     $time_id = celerity_generate_unique_node_id();
     Javelin::initBehavior('time-typeahead', array(
       'startTimeID' => $time_id,
       'endTimeID' => $this->endDateID,
       'timeValues' => $values,
       ));
 
 
     $time_sel = javelin_tag(
       'input',
       array(
         'autocomplete' => 'off',
         'name'  => $this->getTimeInputName(),
         'sigil' => 'time-input',
         'value' => $this->getTimeInputValue(),
         'type'  => 'text',
         'class' => 'aphront-form-time-input',
       ),
       '');
 
     $time_div = javelin_tag(
       'div',
       array(
         'id' => $time_id,
         'class' => 'aphront-form-time-input-container',
       ),
       $time_sel);
 
     Javelin::initBehavior('fancy-datepicker', array());
 
     $classes = array();
     $classes[] = 'aphront-form-date-container';
     if ($disabled) {
       $classes[] = 'datepicker-disabled';
     }
     if ($this->isTimeDisabled) {
       $classes[] = 'no-time';
     }
 
     return javelin_tag(
       'div',
       array(
         'class' => implode(' ', $classes),
         'sigil' => 'phabricator-date-control',
         'meta'  => array(
           'disabled' => (bool)$disabled,
         ),
         'id' => $this->getID(),
       ),
       array(
         $checkbox,
         $date_div,
         $cal_icon,
         $time_div,
       ));
   }
 
   private function getTimezone() {
     if ($this->zone) {
       return $this->zone;
     }
 
     $user = $this->getUser();
     if (!$this->getUser()) {
       throw new PhutilInvalidStateException('setUser');
     }
 
     $user_zone = $user->getTimezoneIdentifier();
     $this->zone = new DateTimeZone($user_zone);
     return $this->zone;
   }
 
   private function getInitialValue() {
     $zone = $this->getTimezone();
 
     // TODO: We could eventually allow these to be customized per install or
     // per user or both, but let's wait and see.
     switch ($this->initialTime) {
       case self::TIME_START_OF_DAY:
       default:
         $time = '12:00 AM';
         break;
       case self::TIME_START_OF_BUSINESS:
         $time = '9:00 AM';
         break;
       case self::TIME_END_OF_BUSINESS:
         $time = '5:00 PM';
         break;
       case self::TIME_END_OF_DAY:
         $time = '11:59 PM';
         break;
     }
 
     $today = $this->formatTime(time(), 'Y-m-d');
     try {
       $date = new DateTime("{$today} {$time}", $zone);
       $value = $date->format('U');
     } catch (Exception $ex) {
       $value = null;
     }
 
     return $value;
   }
 
   private function getTimeTypeaheadValues() {
     $times = array();
     $am_pm_list = array('AM', 'PM');
 
     foreach ($am_pm_list as $am_pm) {
       for ($hour = 0; $hour < 12; $hour++) {
         $actual_hour = ($hour == 0) ? 12 : $hour;
         $times[] = $actual_hour.':00 '.$am_pm;
         $times[] = $actual_hour.':30 '.$am_pm;
       }
     }
 
     foreach ($times as $key => $time) {
       $times[$key] = array($key, $time);
     }
     return $times;
   }
 
 }
diff --git a/src/view/layout/__tests__/PHUIListViewTestCase.php b/src/view/layout/__tests__/PHUIListViewTestCase.php
index 78e1e6ea54..cfaf64a574 100644
--- a/src/view/layout/__tests__/PHUIListViewTestCase.php
+++ b/src/view/layout/__tests__/PHUIListViewTestCase.php
@@ -1,141 +1,141 @@
 <?php
 
 final class PHUIListViewTestCase extends PhabricatorTestCase {
 
   public function testAppend() {
     $menu = $this->newABCMenu();
 
     $this->assertMenuKeys(
       array(
         'a',
         'b',
         'c',
       ),
       $menu);
   }
 
   public function testAppendAfter() {
     $menu = $this->newABCMenu();
 
     $caught = null;
     try {
       $menu->addMenuItemAfter('x', $this->newLink('test1'));
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue($caught instanceof Exception);
 
     $menu->addMenuItemAfter('a', $this->newLink('test2'));
     $menu->addMenuItemAfter(null, $this->newLink('test3'));
     $menu->addMenuItemAfter('a', $this->newLink('test4'));
     $menu->addMenuItemAfter('test3', $this->newLink('test5'));
 
     $this->assertMenuKeys(
       array(
         'a',
         'test4',
         'test2',
         'b',
         'c',
         'test3',
         'test5',
       ),
       $menu);
   }
 
   public function testAppendBefore() {
     $menu = $this->newABCMenu();
 
     $caught = null;
     try {
       $menu->addMenuItemBefore('x', $this->newLink('test1'));
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue($caught instanceof Exception);
 
     $menu->addMenuItemBefore('b', $this->newLink('test2'));
     $menu->addMenuItemBefore(null, $this->newLink('test3'));
     $menu->addMenuItemBefore('a', $this->newLink('test4'));
     $menu->addMenuItemBefore('test3', $this->newLink('test5'));
 
     $this->assertMenuKeys(
       array(
         'test5',
         'test3',
         'test4',
         'a',
         'test2',
         'b',
         'c',
       ),
       $menu);
   }
 
   public function testAppendLabel() {
     $menu = new PHUIListView();
     $menu->addMenuItem($this->newLabel('fruit'));
     $menu->addMenuItem($this->newLabel('animals'));
 
     $caught = null;
     try {
       $menu->addMenuItemToLabel('x', $this->newLink('test1'));
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue($caught instanceof Exception);
 
     $menu->addMenuItemToLabel('fruit', $this->newLink('apple'));
     $menu->addMenuItemToLabel('fruit', $this->newLink('banana'));
 
     $menu->addMenuItemToLabel('animals', $this->newLink('dog'));
     $menu->addMenuItemToLabel('animals', $this->newLink('cat'));
 
     $menu->addMenuItemToLabel('fruit', $this->newLink('cherry'));
 
     $this->assertMenuKeys(
       array(
         'fruit',
           'apple',
           'banana',
           'cherry',
         'animals',
           'dog',
           'cat',
       ),
       $menu);
   }
 
   private function newLink($key) {
     return id(new PHUIListItemView())
       ->setKey($key)
       ->setHref('#')
-      ->setName('Link');
+      ->setName(pht('Link'));
   }
 
   private function newLabel($key) {
     return id(new PHUIListItemView())
       ->setType(PHUIListItemView::TYPE_LABEL)
       ->setKey($key)
-      ->setName('Label');
+      ->setName(pht('Label'));
   }
 
   private function newABCMenu() {
     $menu = new PHUIListView();
 
     $menu->addMenuItem($this->newLink('a'));
     $menu->addMenuItem($this->newLink('b'));
     $menu->addMenuItem($this->newLink('c'));
 
     return $menu;
   }
 
   private function assertMenuKeys(array $expect, PHUIListView $menu) {
     $items = $menu->getItems();
     $keys = mpull($items, 'getKey');
     $keys = array_values($keys);
 
     $this->assertEqual($expect, $keys);
   }
 
 }