diff --git a/src/applications/config/check/PhabricatorBinariesSetupCheck.php b/src/applications/config/check/PhabricatorBinariesSetupCheck.php index 6987da1b18..fb45f2ed7c 100644 --- a/src/applications/config/check/PhabricatorBinariesSetupCheck.php +++ b/src/applications/config/check/PhabricatorBinariesSetupCheck.php @@ -1,233 +1,281 @@ raiseWarning($bin_name, $message); // We need to return here if we can't find the 'which' / 'where' binary // because the other tests won't be valid. return; } if (!Filesystem::binaryExists('diff')) { $message = pht( "Without 'diff', Phabricator will not be able to generate or render ". "diffs in multiple applications."); $this->raiseWarning('diff', $message); } else { $tmp_a = new TempFile(); $tmp_b = new TempFile(); $tmp_c = new TempFile(); Filesystem::writeFile($tmp_a, 'A'); Filesystem::writeFile($tmp_b, 'A'); Filesystem::writeFile($tmp_c, 'B'); list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_b); if ($err) { $this->newIssue('bin.diff.same') ->setName(pht("Unexpected 'diff' Behavior")) ->setMessage( pht( "The 'diff' binary on this system has unexpected behavior: ". "it was expected to exit without an error code when passed ". "identical files, but exited with code %d.", $err)); } list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_c); if (!$err) { $this->newIssue('bin.diff.diff') ->setName(pht("Unexpected 'diff' Behavior")) ->setMessage( pht( "The 'diff' binary on this system has unexpected behavior: ". "it was expected to exit with a nonzero error code when passed ". "differing files, but did not.")); } } $table = new PhabricatorRepository(); $vcses = queryfx_all( $table->establishConnection('r'), 'SELECT DISTINCT versionControlSystem FROM %T', $table->getTableName()); foreach ($vcses as $vcs) { switch ($vcs['versionControlSystem']) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binary = 'git'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $binary = 'svn'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binary = 'hg'; break; default: $binary = null; break; } if (!$binary) { continue; } if (!Filesystem::binaryExists($binary)) { $message = pht( 'You have at least one repository configured which uses this '. 'version control system. It will not work without the VCS binary.'); $this->raiseWarning($binary, $message); } + $version = null; switch ($binary) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $minimum_version = null; $bad_versions = array(); list($err, $stdout, $stderr) = exec_manual('git --version'); $version = trim(substr($stdout, strlen('git version '))); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $minimum_version = null; $bad_versions = array( '1.7.1' => pht('This version of Subversion has a bug where '. '"svn diff -c N" does not work for files added '. 'in rN (Subverison issue #2873), fixed in 1.7.2.'),); list($err, $stdout, $stderr) = exec_manual('svn --version --quiet'); $version = trim($stdout); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $minimum_version = '1.9'; $bad_versions = array( '2.1' => pht('This version of Mercurial returns a bad exit code '. 'after a successful pull.'), '2.2' => pht('This version of Mercurial has a significant memory '. 'leak, fixed in 2.2.1. Pushing fails with this '. 'version as well; see T3046#54922.'),); list($err, $stdout, $stderr) = exec_manual('hg --version --quiet'); - $version = rtrim( - substr($stdout, strlen('Mercurial Distributed SCM (version ')), - ")\n"); - break; - } - if ($minimum_version && - version_compare($version, $minimum_version, '<')) { - $this->raiseMinimumVersionWarning( - $binary, - $minimum_version, - $version); + // NOTE: At least on OSX, recent versions of Mercurial report this + // string in this format: + // + // Mercurial Distributed SCM (version 3.1.1+20140916) + + $matches = null; + $pattern = '/^Mercurial Distributed SCM \(version ([\d.]+)/m'; + if (preg_match($pattern, $stdout, $matches)) { + $version = $matches[1]; + } + break; } - foreach ($bad_versions as $bad_version => $details) { - if ($bad_version === $version) { - $this->raiseBadVersionWarning( + if ($version === null) { + $this->raiseUnknownVersionWarning($binary); + } else { + if ($minimum_version && + version_compare($version, $minimum_version, '<')) { + $this->raiseMinimumVersionWarning( $binary, - $bad_version); + $minimum_version, + $version); + } + + foreach ($bad_versions as $bad_version => $details) { + if ($bad_version === $version) { + $this->raiseBadVersionWarning( + $binary, + $bad_version); + } } } } } private function raiseWarning($bin, $message) { if (phutil_is_windows()) { $preamble = pht( "The '%s' binary could not be found. Set the webserver's %s ". "environmental variable to include the directory where it resides, or ". "add that directory to '%s' in the Phabricator configuration.", $bin, 'PATH', 'environment.append-paths'); } else { $preamble = pht( "The '%s' binary could not be found. Symlink it into '%s', or set the ". "webserver's %s environmental variable to include the directory where ". "it resides, or add that directory to '%s' in the Phabricator ". "configuration.", $bin, 'phabricator/support/bin/', 'PATH', 'environment.append-paths'); } $this->newIssue('bin.'.$bin) ->setShortName(pht("'%s' Missing", $bin)) ->setName(pht("Missing '%s' Binary", $bin)) ->setSummary( pht("The '%s' binary could not be located or executed.", $bin)) ->setMessage($preamble.' '.$message) ->addPhabricatorConfig('environment.append-paths'); } + private function raiseUnknownVersionWarning($binary) { + $summary = pht( + 'Unable to determine the version number of "%s".', + $binary); + + $message = pht( + 'Unable to determine the version number of "%s". Usually, this means '. + 'the program changed its version format string recently and Phabricator '. + 'does not know how to parse the new one yet, but might indicate that '. + 'you have a very old (or broken) binary.'. + "\n\n". + 'Because we can not determine the version number, checks against '. + 'minimum and known-bad versions will be skipped, so we might fail '. + 'to detect an incompatible binary.'. + "\n\n". + 'You may be able to resolve this issue by updating Phabricator, since '. + 'a newer version of Phabricator is likely to be able to parse the '. + 'newer version string.'. + "\n\n". + 'If updating Phabricator does not fix this, you can report the issue '. + 'to the upstream so we can adjust the parser.'. + "\n\n". + 'If you are confident you have a recent version of "%s" installed and '. + 'working correctly, it is usually safe to ignore this warning.', + $binary, + $binary); + + $this->newIssue('bin.'.$binary.'.unknown-version') + ->setShortName(pht("Unknown '%s' Version", $binary)) + ->setName(pht("Unknown '%s' Version", $binary)) + ->setSummary($summary) + ->setMessage($message) + ->addLink( + PhabricatorEnv::getDoclink('Contributing Bug Reports'), + pht('Report this Issue to the Upstream')); + } + private function raiseMinimumVersionWarning( $binary, $minimum_version, $version) { switch ($binary) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $summary = pht( "The '%s' binary is version %s and Phabricator requires version ". "%s or higher.", $binary, $version, $minimum_version); $message = pht( "Please upgrade the '%s' binary to a more modern version.", $binary); $this->newIssue('bin.'.$binary) ->setShortName(pht("Unsupported '%s' Version", $binary)) ->setName(pht("Unsupported '%s' Version", $binary)) ->setSummary($summary) ->setMessage($summary.' '.$message); break; } - - } private function raiseBadVersionWarning($binary, $bad_version) { switch ($binary) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $summary = pht( "The '%s' binary is version %s which has bugs that break ". "Phabricator.", $binary, $bad_version); $message = pht( "Please upgrade the '%s' binary to a more modern version.", $binary); $this->newIssue('bin.'.$binary) ->setShortName(pht("Unsupported '%s' Version", $binary)) ->setName(pht("Unsupported '%s' Version", $binary)) ->setSummary($summary) ->setMessage($summary.' '.$message); break; } } } diff --git a/src/applications/config/issue/PhabricatorSetupIssue.php b/src/applications/config/issue/PhabricatorSetupIssue.php index b02f8b6bce..133f55e017 100644 --- a/src/applications/config/issue/PhabricatorSetupIssue.php +++ b/src/applications/config/issue/PhabricatorSetupIssue.php @@ -1,165 +1,179 @@ commands[] = $command; return $this; } public function getCommands() { return $this->commands; } public function setShortName($short_name) { $this->shortName = $short_name; return $this; } public function getShortName() { if ($this->shortName === null) { return $this->getName(); } return $this->shortName; } public function setName($name) { $this->name = $name; return $this; } public function getName() { return $this->name; } public function setSummary($summary) { $this->summary = $summary; return $this; } public function getSummary() { if ($this->summary === null) { return $this->getMessage(); } return $this->summary; } public function setIssueKey($issue_key) { $this->issueKey = $issue_key; return $this; } public function getIssueKey() { return $this->issueKey; } public function setIsFatal($is_fatal) { $this->isFatal = $is_fatal; return $this; } public function getIsFatal() { return $this->isFatal; } public function addPHPConfig($php_config) { $this->phpConfig[] = $php_config; return $this; } /** * Set an explicit value to display when showing the user PHP configuration * values. * * If Phabricator has changed a value by the time a config issue is raised, * you can provide the original value here so the UI makes sense. For example, * we alter `memory_limit` during startup, so if the original value is not * provided it will look like it is always set to `-1`. * * @param string PHP configuration option to provide a value for. * @param string Explicit value to show in the UI. * @return this */ public function addPHPConfigOriginalValue($php_config, $value) { $this->originalPHPConfigValues[$php_config] = $value; return $this; } public function getPHPConfigOriginalValue($php_config, $default = null) { return idx($this->originalPHPConfigValues, $php_config, $default); } public function getPHPConfig() { return $this->phpConfig; } public function addMySQLConfig($mysql_config) { $this->mysqlConfig[] = $mysql_config; return $this; } public function getMySQLConfig() { return $this->mysqlConfig; } public function addPhabricatorConfig($phabricator_config) { $this->phabricatorConfig[] = $phabricator_config; return $this; } public function getPhabricatorConfig() { return $this->phabricatorConfig; } public function addRelatedPhabricatorConfig($phabricator_config) { $this->relatedPhabricatorConfig[] = $phabricator_config; return $this; } public function getRelatedPhabricatorConfig() { return $this->relatedPhabricatorConfig; } public function addPHPExtension($php_extension) { $this->phpExtensions[] = $php_extension; return $this; } public function getPHPExtensions() { return $this->phpExtensions; } public function setMessage($message) { $this->message = $message; return $this; } public function getMessage() { return $this->message; } public function setIsIgnored($is_ignored) { $this->isIgnored = $is_ignored; return $this; } public function getIsIgnored() { return $this->isIgnored; } + + public function addLink($href, $name) { + $this->links[] = array( + 'href' => $href, + 'name' => $name, + ); + return $this; + } + + public function getLinks() { + return $this->links; + } + } diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php index dfef336bad..a5ccd6797e 100644 --- a/src/applications/config/view/PhabricatorSetupIssueView.php +++ b/src/applications/config/view/PhabricatorSetupIssueView.php @@ -1,511 +1,549 @@ issue = $issue; return $this; } public function getIssue() { return $this->issue; } public function render() { $issue = $this->getIssue(); $description = array(); $description[] = phutil_tag( 'div', array( 'class' => 'setup-issue-instructions', ), phutil_escape_html_newlines($issue->getMessage())); $configs = $issue->getPHPConfig(); if ($configs) { $description[] = $this->renderPHPConfig($configs, $issue); } $configs = $issue->getMySQLConfig(); if ($configs) { $description[] = $this->renderMySQLConfig($configs); } $configs = $issue->getPhabricatorConfig(); if ($configs) { $description[] = $this->renderPhabricatorConfig($configs); } $related_configs = $issue->getRelatedPhabricatorConfig(); if ($related_configs) { $description[] = $this->renderPhabricatorConfig($related_configs, $related = true); } $commands = $issue->getCommands(); if ($commands) { $run_these = pht('Run these %d command(s):', count($commands)); $description[] = phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( phutil_tag('p', array(), $run_these), phutil_tag('pre', array(), phutil_implode_html("\n", $commands)), )); } $extensions = $issue->getPHPExtensions(); if ($extensions) { $install_these = pht( 'Install these %d PHP extension(s):', count($extensions)); $install_info = pht( 'You can usually install a PHP extension using %s or %s. Common '. 'package names are %s or %s. Try commands like these:', phutil_tag('tt', array(), 'apt-get'), phutil_tag('tt', array(), 'yum'), hsprintf('php-%s', pht('extname')), hsprintf('php5-%s', pht('extname'))); // TODO: We should do a better job of detecting how to install extensions // on the current system. $install_commands = hsprintf( "\$ sudo apt-get install php5-extname ". "# Debian / Ubuntu\n". "\$ sudo yum install php-extname ". "# Red Hat / Derivatives"); $fallback_info = pht( "If those commands don't work, try Google. The process of installing ". "PHP extensions is not specific to Phabricator, and any instructions ". "you can find for installing them on your system should work. On Mac ". "OS X, you might want to try Homebrew."); $restart_info = pht( 'After installing new PHP extensions, restart your webserver '. 'for the changes to take effect.', hsprintf('')); $description[] = phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( phutil_tag('p', array(), $install_these), phutil_tag('pre', array(), implode("\n", $extensions)), phutil_tag('p', array(), $install_info), phutil_tag('pre', array(), $install_commands), phutil_tag('p', array(), $fallback_info), phutil_tag('p', array(), $restart_info), )); } + $related_links = $issue->getLinks(); + if ($related_links) { + $description[] = $this->renderRelatedLinks($related_links); + } + $actions = array(); if (!$issue->getIsFatal()) { if ($issue->getIsIgnored()) { $actions[] = javelin_tag( 'a', array( 'href' => '/config/unignore/'.$issue->getIssueKey().'/', 'sigil' => 'workflow', 'class' => 'button grey', ), pht('Unignore Setup Issue')); } else { $actions[] = javelin_tag( 'a', array( 'href' => '/config/ignore/'.$issue->getIssueKey().'/', 'sigil' => 'workflow', 'class' => 'button grey', ), pht('Ignore Setup Issue')); } $actions[] = javelin_tag( 'a', array( 'href' => '/config/issue/'.$issue->getIssueKey().'/', 'class' => 'button grey', 'style' => 'float: right', ), pht('Reload Page')); } if ($actions) { $actions = phutil_tag( 'div', array( 'class' => 'setup-issue-actions', ), $actions); } if ($issue->getIsIgnored()) { $status = phutil_tag( 'div', array( 'class' => 'setup-issue-status', ), pht( 'This issue is currently ignored, and does not show a global '. 'warning.')); $next = null; } else { $status = null; $next = phutil_tag( 'div', array( 'class' => 'setup-issue-next', ), pht('To continue, resolve this problem and reload the page.')); } $name = phutil_tag( 'div', array( 'class' => 'setup-issue-name', ), $issue->getName()); $head = phutil_tag( 'div', array( 'class' => 'setup-issue-head', ), array($name, $status)); $tail = phutil_tag( 'div', array( 'class' => 'setup-issue-tail', ), array($actions, $next)); $issue = phutil_tag( 'div', array( 'class' => 'setup-issue', ), array( $head, $description, $tail, )); $debug_info = phutil_tag( 'div', array( 'class' => 'setup-issue-debug', ), pht('Host: %s', php_uname('n'))); return phutil_tag( 'div', array( 'class' => 'setup-issue-shell', ), array( $issue, $debug_info, )); } private function renderPhabricatorConfig(array $configs, $related = false) { $issue = $this->getIssue(); $table_info = phutil_tag( 'p', array(), pht( 'The current Phabricator configuration has these %d value(s):', count($configs))); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); $hidden = array(); foreach ($options as $key => $option) { if ($option->getHidden()) { $hidden[$key] = true; } } $table = null; $dict = array(); foreach ($configs as $key) { if (isset($hidden[$key])) { $dict[$key] = null; } else { $dict[$key] = PhabricatorEnv::getUnrepairedEnvConfig($key); } } $table = $this->renderValueTable($dict, $hidden); if ($this->getIssue()->getIsFatal()) { $update_info = phutil_tag( 'p', array(), pht( 'To update these %d value(s), run these command(s) from the command '. 'line:', count($configs))); $update = array(); foreach ($configs as $key) { $update[] = hsprintf( 'phabricator/ $ ./bin/config set %s value', $key); } $update = phutil_tag('pre', array(), phutil_implode_html("\n", $update)); } else { $update = array(); foreach ($configs as $config) { if (idx($options, $config) && $options[$config]->getLocked()) { continue; } $link = phutil_tag( 'a', array( 'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(), ), pht('Edit %s', $config)); $update[] = phutil_tag('li', array(), $link); } if ($update) { $update = phutil_tag('ul', array(), $update); if (!$related) { $update_info = phutil_tag( 'p', array(), pht('You can update these %d value(s) here:', count($configs))); } else { $update_info = phutil_tag( 'p', array(), pht('These %d configuration value(s) are related:', count($configs))); } } else { $update = null; $update_info = null; } } return phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( $table_info, $table, $update_info, $update, )); } private function renderPHPConfig(array $configs, $issue) { $table_info = phutil_tag( 'p', array(), pht( 'The current PHP configuration has these %d value(s):', count($configs))); $dict = array(); foreach ($configs as $key) { $dict[$key] = $issue->getPHPConfigOriginalValue( $key, ini_get($key)); } $table = $this->renderValueTable($dict); ob_start(); phpinfo(); $phpinfo = ob_get_clean(); $rex = '@Loaded Configuration File\s*(.*?)@i'; $matches = null; $ini_loc = null; if (preg_match($rex, $phpinfo, $matches)) { $ini_loc = trim($matches[1]); } $rex = '@Additional \.ini files parsed\s*(.*?)@i'; $more_loc = array(); if (preg_match($rex, $phpinfo, $matches)) { $more_loc = trim($matches[1]); if ($more_loc == '(none)') { $more_loc = array(); } else { $more_loc = preg_split('/\s*,\s*/', $more_loc); } } $info = array(); if (!$ini_loc) { $info[] = phutil_tag( 'p', array(), pht( 'To update these %d value(s), edit your PHP configuration file.', count($configs))); } else { $info[] = phutil_tag( 'p', array(), pht( 'To update these %d value(s), edit your PHP configuration file, '. 'located here:', count($configs))); $info[] = phutil_tag( 'pre', array(), $ini_loc); } if ($more_loc) { $info[] = phutil_tag( 'p', array(), pht( 'PHP also loaded these configuration file(s):', count($more_loc))); $info[] = phutil_tag( 'pre', array(), implode("\n", $more_loc)); } $info[] = phutil_tag( 'p', array(), pht( 'You can find more information about PHP configuration values in the '. '%s.', phutil_tag( 'a', array( 'href' => 'http://php.net/manual/ini.list.php', 'target' => '_blank', ), pht('PHP Documentation')))); $info[] = phutil_tag( 'p', array(), pht( 'After editing the PHP configuration, restart your '. 'webserver for the changes to take effect.', hsprintf(''))); return phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( $table_info, $table, $info, )); } private function renderMySQLConfig(array $config) { $values = array(); foreach ($config as $key) { $value = PhabricatorMySQLSetupCheck::loadRawConfigValue($key); if ($value === null) { $value = phutil_tag( 'em', array(), pht('(Not Supported)')); } $values[$key] = $value; } $table = $this->renderValueTable($values); $doc_href = PhabricatorEnv::getDoclink('User Guide: Amazon RDS'); $doc_link = phutil_tag( 'a', array( 'href' => $doc_href, 'target' => '_blank', ), pht('User Guide: Amazon RDS')); $info = array(); $info[] = phutil_tag( 'p', array(), pht( 'If you are using Amazon RDS, some of the instructions above may '. 'not apply to you. See %s for discussion of Amazon RDS.', $doc_link)); $table_info = phutil_tag( 'p', array(), pht( 'The current MySQL configuration has these %d value(s):', count($config))); return phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( $table_info, $table, $info, )); } private function renderValueTable(array $dict, array $hidden = array()) { $rows = array(); foreach ($dict as $key => $value) { if (isset($hidden[$key])) { $value = phutil_tag('em', array(), 'hidden'); } else { $value = $this->renderValueForDisplay($value); } $cols = array( phutil_tag('th', array(), $key), phutil_tag('td', array(), $value), ); $rows[] = phutil_tag('tr', array(), $cols); } return phutil_tag('table', array(), $rows); } private function renderValueForDisplay($value) { if ($value === null) { return phutil_tag('em', array(), 'null'); } else if ($value === false) { return phutil_tag('em', array(), 'false'); } else if ($value === true) { return phutil_tag('em', array(), 'true'); } else if ($value === '') { return phutil_tag('em', array(), 'empty string'); } else if ($value instanceof PhutilSafeHTML) { return $value; } else { return PhabricatorConfigJSON::prettyPrintJSON($value); } } + private function renderRelatedLinks(array $links) { + $link_info = phutil_tag( + 'p', + array(), + pht( + '%d related link(s):', + count($links))); + + $link_list = array(); + foreach ($links as $link) { + $link_tag = phutil_tag( + 'a', + array( + 'target' => '_blank', + 'href' => $link['href'], + ), + $link['name']); + $link_item = phutil_tag('li', array(), $link_tag); + $link_list[] = $link_item; + } + $link_list = phutil_tag('ul', array(), $link_list); + + return phutil_tag( + 'div', + array( + 'class' => 'setup-issue-config', + ), + array( + $link_info, + $link_list, + )); + } + } diff --git a/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php index c086d72ef9..bb694cd117 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorBaseEnglishTranslation.php @@ -1,891 +1,896 @@ array( 'No daemon with id %s exists!', 'No daemons with ids %s exist!', ), 'These %d configuration value(s) are related:' => array( 'This configuration value is related:', 'These configuration values are related:', ), 'Task(s)' => array('Task', 'Tasks'), '%d Error(s)' => array('%d Error', '%d Errors'), '%d Warning(s)' => array('%d Warning', '%d Warnings'), '%d Auto-Fix(es)' => array('%d Auto-Fix', '%d Auto-Fixes'), '%d Advice(s)' => array('%d Advice', '%d Pieces of Advice'), '%d Detail(s)' => array('%d Detail', '%d Details'), '(%d line(s))' => array('(%d line)', '(%d lines)'), '%d line(s)' => array('%d line', '%d lines'), '%d path(s)' => array('%d path', '%d paths'), '%d diff(s)' => array('%d diff', '%d diffs'), 'There are %d raw fact(s) in storage.' => array( 'There is %d raw fact in storage.', 'There are %d raw facts in storage.', ), 'There are %d aggregate fact(s) in storage.' => array( 'There is %d aggregate fact in storage.', 'There are %d aggregate facts in storage.', ), '%d Commit(s) Awaiting Audit' => array( '%d Commit Awaiting Audit', '%d Commits Awaiting Audit', ), '%d Problem Commit(s)' => array( '%d Problem Commit', '%d Problem Commits', ), '%d Review(s) Blocking Others' => array( '%d Review Blocking Others', '%d Reviews Blocking Others', ), '%d Review(s) Need Attention' => array( '%d Review Needs Attention', '%d Reviews Need Attention', ), '%d Review(s) Waiting on Others' => array( '%d Review Waiting on Others', '%d Reviews Waiting on Others', ), '%d Active Review(s)' => array( '%d Active Review', '%d Active Reviews', ), '%d Flagged Object(s)' => array( '%d Flagged Object', '%d Flagged Objects', ), '%d Object(s) Tracked' => array( '%d Object Tracked', '%d Objects Tracked', ), '%d Assigned Task(s)' => array( '%d Assigned Task', '%d Assigned Tasks', ), 'Show %d Lint Message(s)' => array( 'Show %d Lint Message', 'Show %d Lint Messages', ), 'Hide %d Lint Message(s)' => array( 'Hide %d Lint Message', 'Hide %d Lint Messages', ), 'This is a binary file. It is %s byte(s) in length.' => array( 'This is a binary file. It is %s byte in length.', 'This is a binary file. It is %s bytes in length.', ), '%d Action(s) Have No Effect' => array( 'Action Has No Effect', 'Actions Have No Effect', ), '%d Action(s) With No Effect' => array( 'Action With No Effect', 'Actions With No Effect', ), 'Some of your %d action(s) have no effect:' => array( 'One of your actions has no effect:', 'Some of your actions have no effect:', ), 'Apply remaining %d action(s)?' => array( 'Apply remaining action?', 'Apply remaining actions?', ), 'Apply %d Other Action(s)' => array( 'Apply Remaining Action', 'Apply Remaining Actions', ), 'The %d action(s) you are taking have no effect:' => array( 'The action you are taking has no effect:', 'The actions you are taking have no effect:', ), '%s edited member(s), added %d: %s; removed %d: %s.' => '%s edited members, added: %3$s; removed: %5$s.', '%s added %s member(s): %s.' => array( array( '%s added a member: %3$s.', '%s added members: %3$s.', ), ), '%s removed %s member(s): %s.' => array( array( '%s removed a member: %3$s.', '%s removed members: %3$s.', ), ), '%s edited project(s), added %s: %s; removed %s: %s.' => '%s edited projects, added: %3$s; removed: %5$s.', '%s added %s project(s): %s.' => array( array( '%s added a project: %3$s.', '%s added projects: %3$s.', ), ), '%s removed %s project(s): %s.' => array( array( '%s removed a project: %3$s.', '%s removed projects: %3$s.', ), ), '%s merged %d task(s): %s.' => array( array( '%s merged a task: %3$s.', '%s merged tasks: %3$s.', ), ), '%s merged %d task(s) %s into %s.' => array( array( '%s merged %3$s into %4$s.', '%s merged tasks %3$s into %4$s.', ), ), '%s added %s voting user(s): %s.' => array( array( '%s added a voting user: %3$s.', '%s added voting users: %3$s.', ), ), '%s removed %s voting user(s): %s.' => array( array( '%s removed a voting user: %3$s.', '%s removed voting users: %3$s.', ), ), '%s added %s blocking task(s): %s.' => array( array( '%s added a blocking task: %3$s.', '%s added blocking tasks: %3$s.', ), ), '%s added %s blocked task(s): %s.' => array( array( '%s added a blocked task: %3$s.', '%s added blocked tasks: %3$s.', ), ), '%s removed %s blocking task(s): %s.' => array( array( '%s removed a blocking task: %3$s.', '%s removed blocking tasks: %3$s.', ), ), '%s removed %s blocked task(s): %s.' => array( array( '%s removed a blocked task: %3$s.', '%s removed blocked tasks: %3$s.', ), ), '%s added %s blocking task(s) for %s: %s.' => array( array( '%s added a blocking task for %3$s: %4$s.', '%s added blocking tasks for %3$s: %4$s.', ), ), '%s added %s blocked task(s) for %s: %s.' => array( array( '%s added a blocked task for %3$s: %4$s.', '%s added blocked tasks for %3$s: %4$s.', ), ), '%s removed %s blocking task(s) for %s: %s.' => array( array( '%s removed a blocking task for %3$s: %4$s.', '%s removed blocking tasks for %3$s: %4$s.', ), ), '%s removed %s blocked task(s) for %s: %s.' => array( array( '%s removed a blocked task for %3$s: %4$s.', '%s removed blocked tasks for %3$s: %4$s.', ), ), '%s edited blocking task(s), added %s: %s; removed %s: %s.' => '%s edited blocking tasks, added: %3$s; removed: %5$s.', '%s edited blocking task(s) for %s, added %s: %s; removed %s: %s.' => '%s edited blocking tasks for %s, added: %4$s; removed: %6$s.', '%s edited blocked task(s), added %s: %s; removed %s: %s.' => '%s edited blocked tasks, added: %3$s; removed: %5$s.', '%s edited blocked task(s) for %s, added %s: %s; removed %s: %s.' => '%s edited blocked tasks for %s, added: %4$s; removed: %6$s.', '%s edited answer(s), added %s: %s; removed %d: %s.' => '%s edited answers, added: %3$s; removed: %5$s.', '%s added %s answer(s): %s.' => array( array( '%s added an answer: %3$s.', '%s added answers: %3$s.', ), ), '%s removed %s answer(s): %s.' => array( array( '%s removed a answer: %3$s.', '%s removed answers: %3$s.', ), ), '%s edited question(s), added %s: %s; removed %s: %s.' => '%s edited questions, added: %3$s; removed: %5$s.', '%s added %s question(s): %s.' => array( array( '%s added a question: %3$s.', '%s added questions: %3$s.', ), ), '%s removed %s question(s): %s.' => array( array( '%s removed a question: %3$s.', '%s removed questions: %3$s.', ), ), '%s edited mock(s), added %s: %s; removed %s: %s.' => '%s edited mocks, added: %3$s; removed: %5$s.', '%s added %s mock(s): %s.' => array( array( '%s added a mock: %3$s.', '%s added mocks: %3$s.', ), ), '%s removed %s mock(s): %s.' => array( array( '%s removed a mock: %3$s.', '%s removed mocks: %3$s.', ), ), '%s added %s task(s): %s.' => array( array( '%s added a task: %3$s.', '%s added tasks: %3$s.', ), ), '%s removed %s task(s): %s.' => array( array( '%s removed a task: %3$s.', '%s removed tasks: %3$s.', ), ), '%s edited file(s), added %s: %s; removed %s: %s.' => '%s edited files, added: %3$s; removed: %5$s.', '%s added %s file(s): %s.' => array( array( '%s added a file: %3$s.', '%s added files: %3$s.', ), ), '%s removed %s file(s): %s.' => array( array( '%s removed a file: %3$s.', '%s removed files: %3$s.', ), ), '%s edited contributor(s), added %s: %s; removed %s: %s.' => '%s edited contributors, added: %3$s; removed: %5$s.', '%s added %s contributor(s): %s.' => array( array( '%s added a contributor: %3$s.', '%s added contributors: %3$s.', ), ), '%s removed %s contributor(s): %s.' => array( array( '%s removed a contributor: %3$s.', '%s removed contributors: %3$s.', ), ), '%s edited %s reviewer(s), added %s: %s; removed %s: %s.' => '%s edited reviewers, added: %4$s; removed: %6$s.', '%s edited %s reviewer(s) for %s, added %s: %s; removed %s: %s.' => '%s edited reviewers for %3$s, added: %5$s; removed: %7$s.', '%s added %s reviewer(s): %s.' => array( array( '%s added a reviewer: %3$s.', '%s added reviewers: %3$s.', ), ), '%s removed %s reviewer(s): %s.' => array( array( '%s removed a reviewer: %3$s.', '%s removed reviewers: %3$s.', ), ), '%d other(s)' => array( '1 other', '%d others', ), '%s edited subscriber(s), added %d: %s; removed %d: %s.' => '%s edited subscribers, added: %3$s; removed: %5$s.', '%s added %d subscriber(s): %s.' => array( array( '%s added a subscriber: %3$s.', '%s added subscribers: %3$s.', ), ), '%s removed %d subscriber(s): %s.' => array( array( '%s removed a subscriber: %3$s.', '%s removed subscribers: %3$s.', ), ), '%s edited participant(s), added %d: %s; removed %d: %s.' => '%s edited participants, added: %3$s; removed: %5$s.', '%s added %d participant(s): %s.' => array( array( '%s added a participant: %3$s.', '%s added participants: %3$s.', ), ), '%s removed %d participant(s): %s.' => array( array( '%s removed a participant: %3$s.', '%s removed participants: %3$s.', ), ), '%s edited image(s), added %d: %s; removed %d: %s.' => '%s edited images, added: %3$s; removed: %5$s', '%s added %d image(s): %s.' => array( array( '%s added an image: %3$s.', '%s added images: %3$s.', ), ), '%s removed %d image(s): %s.' => array( array( '%s removed an image: %3$s.', '%s removed images: %3$s.', ), ), '%s Line(s)' => array( '%s Line', '%s Lines', ), 'Indexing %d object(s) of type %s.' => array( 'Indexing %d object of type %s.', 'Indexing %d object of type %s.', ), 'Run these %d command(s):' => array( 'Run this command:', 'Run these commands:', ), 'Install these %d PHP extension(s):' => array( 'Install this PHP extension:', 'Install these PHP extensions:', ), 'The current Phabricator configuration has these %d value(s):' => array( 'The current Phabricator configuration has this value:', 'The current Phabricator configuration has these values:', ), 'The current MySQL configuration has these %d value(s):' => array( 'The current MySQL configuration has this value:', 'The current MySQL configuration has these values:', ), 'You can update these %d value(s) here:' => array( 'You can update this value here:', 'You can update these values here:', ), 'The current PHP configuration has these %d value(s):' => array( 'The current PHP configuration has this value:', 'The current PHP configuration has these values:', ), 'To update these %d value(s), edit your PHP configuration file.' => array( 'To update this %d value, edit your PHP configuration file.', 'To update these %d values, edit your PHP configuration file.', ), 'To update these %d value(s), edit your PHP configuration file, located '. 'here:' => array( 'To update this value, edit your PHP configuration file, located '. 'here:', 'To update these values, edit your PHP configuration file, located '. 'here:', ), 'PHP also loaded these configuration file(s):' => array( 'PHP also loaded this configuration file:', 'PHP also loaded these configuration files:', ), 'You have %d unresolved setup issue(s)...' => array( 'You have an unresolved setup issue...', 'You have %d unresolved setup issues...', ), '%s added %d inline comment(s).' => array( array( '%s added an inline comment.', '%s added inline comments.', ), ), '%d comment(s)' => array('%d comment', '%d comments'), '%d rejection(s)' => array('%d rejection', '%d rejections'), '%d update(s)' => array('%d update', '%d updates'), 'This configuration value is defined in these %d '. 'configuration source(s): %s.' => array( 'This configuration value is defined in this '. 'configuration source: %2$s.', 'This configuration value is defined in these %d '. 'configuration sources: %s.', ), '%d Open Pull Request(s)' => array( '%d Open Pull Request', '%d Open Pull Requests', ), 'Stale (%s day(s))' => array( 'Stale (%s day)', 'Stale (%s days)', ), 'Old (%s day(s))' => array( 'Old (%s day)', 'Old (%s days)', ), '%s Commit(s)' => array( '%s Commit', '%s Commits', ), '%s attached %d file(s): %s.' => array( array( '%s attached a file: %3$s.', '%s attached files: %3$s.', ), ), '%s detached %d file(s): %s.' => array( array( '%s detached a file: %3$s.', '%s detached files: %3$s.', ), ), '%s changed file(s), attached %d: %s; detached %d: %s.' => '%s changed files, attached: %3$s; detached: %5$s.', '%s added %s dependencie(s): %s.' => array( array( '%s added a dependency: %3$s.', '%s added dependencies: %3$s.', ), ), '%s removed %s dependencie(s): %s.' => array( array( '%s removed a dependency: %3$s.', '%s removed dependencies: %3$s.', ), ), '%s added %s dependent revision(s): %s.' => array( array( '%s added a dependent revision: %3$s.', '%s added dependent revisions: %3$s.', ), ), '%s removed %s dependent revision(s): %s.' => array( array( '%s removed a dependent revision: %3$s.', '%s removed dependent revisions: %3$s.', ), ), '%s added %s commit(s): %s.' => array( array( '%s added a commit: %3$s.', '%s added commits: %3$s.', ), ), '%s removed %s commit(s): %s.' => array( array( '%s removed a commit: %3$s.', '%s removed commits: %3$s.', ), ), '%s edited commit(s), added %s: %s; removed %s: %s.' => '%s edited commits, added %3$s; removed %5$s.', '%s added %s reverted commit(s): %s.' => array( array( '%s added a reverted commit: %3$s.', '%s added reverted commits: %3$s.', ), ), '%s removed %s reverted commit(s): %s.' => array( array( '%s removed a reverted commit: %3$s.', '%s removed reverted commits: %3$s.', ), ), '%s edited reverted commit(s), added %s: %s; removed %s: %s.' => '%s edited reverted commits, added %3$s; removed %5$s.', '%s added %s reverting commit(s): %s.' => array( array( '%s added a reverting commit: %3$s.', '%s added reverting commits: %3$s.', ), ), '%s removed %s reverting commit(s): %s.' => array( array( '%s removed a reverting commit: %3$s.', '%s removed reverting commits: %3$s.', ), ), '%s edited reverting commit(s), added %s: %s; removed %s: %s.' => '%s edited reverting commits, added %3$s; removed %5$s.', '%s changed project member(s), added %d: %s; removed %d: %s.' => '%s changed project members, added %3$s; removed %5$s.', '%s added %d project member(s): %s.' => array( array( '%s added a member: %3$s.', '%s added members: %3$s.', ), ), '%s removed %d project member(s): %s.' => array( array( '%s removed a member: %3$s.', '%s removed members: %3$s.', ), ), '%d project hashtag(s) are already used: %s.' => array( 'Project hashtag %2$s is already used.', '%d project hashtags are already used: %2$s.', ), '%s changed project hashtag(s), added %d: %s; removed %d: %s.' => '%s changed project hashtags, added %3$s; removed %5$s.', '%s added %d project hashtag(s): %s.' => array( array( '%s added a hashtag: %3$s.', '%s added hashtags: %3$s.', ), ), '%s removed %d project hashtag(s): %s.' => array( array( '%s removed a hashtag: %3$s.', '%s removed hashtags: %3$s.', ), ), '%d User(s) Need Approval' => array( '%d User Needs Approval', '%d Users Need Approval', ), '%s older changes(s) are hidden.' => array( '%d older change is hidden.', '%d older changes are hidden.', ), '%s, %s line(s)' => array( '%s, %s line', '%s, %s lines', ), '%s pushed %d commit(s) to %s.' => array( array( '%s pushed a commit to %3$s.', '%s pushed %d commits to %s.', ), ), '%s commit(s)' => array( '1 commit', '%s commits', ), '%s removed %s JIRA issue(s): %s.' => array( array( '%s removed a JIRA issue: %3$s.', '%s removed JIRA issues: %3$s.', ), ), '%s added %s JIRA issue(s): %s.' => array( array( '%s added a JIRA issue: %3$s.', '%s added JIRA issues: %3$s.', ), ), '%s added %s required legal document(s): %s.' => array( array( '%s added a required legal document: %3$s.', '%s added required legal documents: %3$s.', ), ), '%s updated JIRA issue(s): added %s %s; removed %d %s.' => '%s updated JIRA issues: added %3$s; removed %5$s.', '%s edited %s task(s), added %s: %s; removed %s: %s.' => '%s edited tasks, added %4$s; removed %6$s.', '%s added %s task(s) to %s: %s.' => array( array( '%s added a task to %3$s: %4$s.', '%s added tasks to %3$s: %4$s.', ), ), '%s removed %s task(s) from %s: %s.' => array( array( '%s removed a task from %3$s: %4$s.', '%s removed tasks from %3$s: %4$s.', ), ), '%s edited %s task(s) for %s, added %s: %s; removed %s: %s.' => '%s edited tasks for %3$s, added: %5$s; removed %7$s.', '%s edited %s commit(s), added %s: %s; removed %s: %s.' => '%s edited commits, added %4$s; removed %6$s.', '%s added %s commit(s) to %s: %s.' => array( array( '%s added a commit to %3$s: %4$s.', '%s added commits to %3$s: %4$s.', ), ), '%s removed %s commit(s) from %s: %s.' => array( array( '%s removed a commit from %3$s: %4$s.', '%s removed commits from %3$s: %4$s.', ), ), '%s edited %s commit(s) for %s, added %s: %s; removed %s: %s.' => '%s edited commits for %3$s, added: %5$s; removed %7$s.', '%s added %s revision(s): %s.' => array( array( '%s added a revision: %3$s.', '%s added revisions: %3$s.', ), ), '%s removed %s revision(s): %s.' => array( array( '%s removed a revision: %3$s.', '%s removed revisions: %3$s.', ), ), '%s edited %s revision(s), added %s: %s; removed %s: %s.' => '%s edited revisions, added %4$s; removed %6$s.', '%s added %s revision(s) to %s: %s.' => array( array( '%s added a revision to %3$s: %4$s.', '%s added revisions to %3$s: %4$s.', ), ), '%s removed %s revision(s) from %s: %s.' => array( array( '%s removed a revision from %3$s: %4$s.', '%s removed revisions from %3$s: %4$s.', ), ), '%s edited %s revision(s) for %s, added %s: %s; removed %s: %s.' => '%s edited revisions for %3$s, added: %5$s; removed %7$s.', '%s edited %s project(s), added %s: %s; removed %s: %s.' => '%s edited projects, added %4$s; removed %6$s.', '%s added %s project(s) to %s: %s.' => array( array( '%s added a project to %3$s: %4$s.', '%s added projects to %3$s: %4$s.', ), ), '%s removed %s project(s) from %s: %s.' => array( array( '%s removed a project from %3$s: %4$s.', '%s removed projects from %3$s: %4$s.', ), ), '%s edited %s project(s) for %s, added %s: %s; removed %s: %s.' => '%s edited projects for %3$s, added: %5$s; removed %7$s.', '%s added %s panel(s): %s.' => array( array( '%s added a panel: %3$s.', '%s added panels: %3$s.', ), ), '%s removed %s panel(s): %s.' => array( array( '%s removed a panel: %3$s.', '%s removed panels: %3$s.', ), ), '%s edited %s panel(s), added %s: %s; removed %s: %s.' => '%s edited panels, added %4$s; removed %6$s.', '%s added %s dashboard(s): %s.' => array( array( '%s added a dashboard: %3$s.', '%s added dashboards: %3$s.', ), ), '%s removed %s dashboard(s): %s.' => array( array( '%s removed a dashboard: %3$s.', '%s removed dashboards: %3$s.', ), ), '%s edited %s dashboard(s), added %s: %s; removed %s: %s.' => '%s edited dashboards, added %4$s; removed %6$s.', '%s added %s edge(s): %s.' => array( array( '%s added an edge: %3$s.', '%s added edges: %3$s.', ), ), '%s added %s edge(s) to %s: %s.' => array( array( '%s added an edge to %3$s: %4$s.', '%s added edges to %3$s: %4$s.', ), ), '%s removed %s edge(s): %s.' => array( array( '%s removed an edge: %3$s.', '%s removed edges: %3$s.', ), ), '%s removed %s edge(s) from %s: %s.' => array( array( '%s removed an edge from %3$s: %4$s.', '%s removed edges from %3$s: %4$s.', ), ), '%s edited edge(s), added %s: %s; removed %s: %s.' => '%s edited edges, added: %3$s; removed: %5$s.', '%s edited %s edge(s) for %s, added %s: %s; removed %s: %s.' => '%s edited edges for %3$s, added: %5$s; removed %7$s.', + + '%d related link(s):' => array( + 'Related link:', + 'Related links:', + ), ); } }