diff --git a/.divinerconfig b/.divinerconfig index 6590534..4ea4cf6 100644 --- a/.divinerconfig +++ b/.divinerconfig @@ -1,33 +1,34 @@ { "name" : "libphutil", "src_link" : "https://secure.phabricator.com/diffusion/PHU/browse/master/%f$%l", "groups" : { "overview" : "Overview", "contrib" : "Contributing to libphutil", "working" : "Working with libphutil", "util" : "Core Utilities", "library" : "Phutil Module System", "utf8" : "UTF-8", + "internationalization" : "Internationalization", "filesystem" : "Filesystem", "exec" : "Command Execution", "futures" : "Futures", "channel" : "Channels (I/O Wrappers)", "aws" : "Amazon Web Services", "error" : "Error Handling", "markup" : "Markup", "console" : "Console Utilities", "aast" : "Abstract Abstract Syntax Tree", "xhpast" : "XHPAST (PHP/XHP Parser)", "conduit" : "Conduit (Service API)", "event" : "Events", "daemon" : "Daemons", "parser" : "Other Parsers", "testcase" : "Test Cases" }, "engines" : [ ["DivinerArticleEngine", {}], ["DivinerXHPEngine", {}] ] } diff --git a/src/internationalization/PhutilPerson.php b/src/internationalization/PhutilPerson.php index 2e5d95e..116fc7a 100644 --- a/src/internationalization/PhutilPerson.php +++ b/src/internationalization/PhutilPerson.php @@ -1,27 +1,30 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * @group internationalization + */ interface PhutilPerson { const SEX_MALE = 'm'; const SEX_FEMALE = 'f'; const SEX_UNKNOWN = ''; function getSex(); function __toString(); } diff --git a/src/internationalization/PhutilTranslator.php b/src/internationalization/PhutilTranslator.php index 0ebfe56..7fe649d 100644 --- a/src/internationalization/PhutilTranslator.php +++ b/src/internationalization/PhutilTranslator.php @@ -1,119 +1,122 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * @group internationalization + */ final class PhutilTranslator { static private $instance; private $language = 'en'; private $translations = array(); public static function getInstance() { if (self::$instance === null) { self::$instance = new PhutilTranslator(); } return self::$instance; } public static function setInstance(PhutilTranslator $instance) { self::$instance = $instance; } public function setLanguage($language) { $this->language = $language; return $this; } /** * Add translations which will be later used by @{method:translate}. * The parameter is an array of strings (for simple translations) or arrays * (for translastions with variants). The number of items in the array is * language specific. It is `array($singular, $plural)` for English. * * array( * 'color' => 'colour', * '%d beer(s)' => array('%d beer', '%d beers'), * ); * * The arrays can be nested for strings with more variant parts: * * array( * '%d char(s) on %d row(s)' => array( * array('%d char on %d row', '%d char on %d rows'), * array('%d chars on %d row', '%d chars on %d rows'), * ), * ); * * The translation should have the same placeholders as originals. Swapping * parameter order is possible: * * array( * '%s owns %s.' => '%2$s is owned by %1$s.', * ); * * @param array Identifier in key, translation in value. * @return PhutilTranslator Provides fluent interface. */ public function addTranslations(array $translations) { $this->translations = array_merge($this->translations, $translations); return $this; } public function translate($text /*, ... */) { $translation = idx($this->translations, $text, $text); $args = func_get_args(); while (is_array($translation)) { $translation = $this->chooseVariant($translation, next($args)); } array_shift($args); return vsprintf($translation, $args); } private function chooseVariant(array $translations, $variant) { switch ($this->language) { case 'en': list($singular, $plural) = $translations; if ($variant == 1) { return $singular; } return $plural; case 'cs': if ($variant instanceof PhutilPerson) { list($male, $female) = $translations; if ($variant->getSex() == PhutilPerson::SEX_FEMALE) { return $female; } return $male; } list($singular, $paucal, $plural) = $translations; if ($variant == 1) { return $singular; } if ($variant >= 2 && $variant <= 4) { return $paucal; } return $plural; default: throw new Exception("Unknown language '{$this->language}'."); } } } diff --git a/src/internationalization/__tests__/PhutilPersonTest.php b/src/internationalization/__tests__/PhutilPersonTest.php index 98095f9..61c7d5d 100644 --- a/src/internationalization/__tests__/PhutilPersonTest.php +++ b/src/internationalization/__tests__/PhutilPersonTest.php @@ -1,35 +1,38 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * @group testcase + */ final class PhutilPersonTest implements PhutilPerson { private $sex = PhutilPerson::SEX_UNKNOWN; public function getSex() { return $this->sex; } public function setSex($value) { $this->sex = $value; return $this; } public function __toString() { return 'Test ('.$this->sex.')'; } } diff --git a/src/internationalization/__tests__/PhutilTranslatorTestCase.php b/src/internationalization/__tests__/PhutilTranslatorTestCase.php index 3841dbe..7f96244 100644 --- a/src/internationalization/__tests__/PhutilTranslatorTestCase.php +++ b/src/internationalization/__tests__/PhutilTranslatorTestCase.php @@ -1,105 +1,108 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * @group testcase + */ final class PhutilTranslatorTestCase extends ArcanistPhutilTestCase { public function testEnglish() { $translator = new PhutilTranslator(); $translator->addTranslations( array( '%d line(s)' => array('%d line', '%d lines'), '%d char(s) on %d row(s)' => array( array('%d char on %d row', '%d char on %d rows'), array('%d chars on %d row', '%d chars on %d rows'), ), )); $this->assertEqual('line', $translator->translate('line')); $this->assertEqual('param', $translator->translate('%s', 'param')); $this->assertEqual('0 lines', $translator->translate('%d line(s)', 0)); $this->assertEqual('1 line', $translator->translate('%d line(s)', 1)); $this->assertEqual('2 lines', $translator->translate('%d line(s)', 2)); $this->assertEqual( '1 char on 1 row', $translator->translate('%d char(s) on %d row(s)', 1, 1)); $this->assertEqual( '5 chars on 2 rows', $translator->translate('%d char(s) on %d row(s)', 5, 2)); $this->assertEqual('1 beer(s)', $translator->translate('%d beer(s)', 1)); } public function testCzech() { $translator = new PhutilTranslator(); $translator->setLanguage('cs'); $translator->addTranslations( array( '%d beer(s)' => array('%d pivo', '%d piva', '%d piv'), )); $this->assertEqual('0 piv', $translator->translate('%d beer(s)', 0)); $this->assertEqual('1 pivo', $translator->translate('%d beer(s)', 1)); $this->assertEqual('2 piva', $translator->translate('%d beer(s)', 2)); $this->assertEqual('5 piv', $translator->translate('%d beer(s)', 5)); $this->assertEqual('1 line(s)', $translator->translate('%d line(s)', 1)); } public function testPerson() { $translator = new PhutilTranslator(); $translator->setLanguage('cs'); $translator->addTranslations( array( '%s wrote.' => array('%s napsal.', '%s napsala.'), )); $person = new PhutilPersonTest(); $this->assertEqual( 'Test () napsal.', $translator->translate('%s wrote.', $person)); $person->setSex(PhutilPerson::SEX_MALE); $this->assertEqual( 'Test (m) napsal.', $translator->translate('%s wrote.', $person)); $person->setSex(PhutilPerson::SEX_FEMALE); $this->assertEqual( 'Test (f) napsala.', $translator->translate('%s wrote.', $person)); } public function testSetInstance() { $original = PhutilTranslator::getInstance(); $this->assertEqual('color', pht('color')); $british = new PhutilTranslator(); $british->addTranslations( array( 'color' => 'colour', )); PhutilTranslator::setInstance($british); $this->assertEqual('colour', pht('color')); PhutilTranslator::setInstance($original); $this->assertEqual('color', pht('color')); } } diff --git a/src/internationalization/pht.php b/src/internationalization/pht.php index 5f02348..f04f577 100644 --- a/src/internationalization/pht.php +++ b/src/internationalization/pht.php @@ -1,34 +1,36 @@ <?php /* * Copyright 2012 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Translate a string. It uses a translator set by * `PhutilTranslator::setInstance()` or translations specified by * `PhutilTranslator::getInstance()->addTranslations()` and language rules set * by `PhutilTranslator::getInstance()->setLanguage()`. * * @param string Translation identifier with sprintf() placeholders. * @param mixed Value to select the variant from (e.g. singular or plural). * @param ... Next values referenced from $text. * @return string Translated string with substituted values. + * + * @group internationalization */ function pht($text, $variant = null /*, ... */) { $args = func_get_args(); $translator = PhutilTranslator::getInstance(); return call_user_func_array(array($translator, 'translate'), $args); }