diff --git a/engine/classes/Elgg/I18n/ArrayMessageBundle.php b/engine/classes/Elgg/I18n/ArrayMessageBundle.php new file mode 100644 index 00000000000..56d09dec885 --- /dev/null +++ b/engine/classes/Elgg/I18n/ArrayMessageBundle.php @@ -0,0 +1,45 @@ +messages = $messages; + } + + /** @inheritDoc */ + public function get($key, Locale $locale) { + assert(is_string($key), '$key must be a string'); + + if (!isset($this->messages["$locale"]) || !is_array($this->messages["$locale"])) { + return null; + } + + $messages = $this->messages["$locale"]; + if (!is_string($key) || !isset($messages[$key]) || !is_string($messages[$key])) { + return null; + } + + return new SprintfMessageTemplate($messages[$key]); + } +} diff --git a/engine/classes/Elgg/I18n/Locale.php b/engine/classes/Elgg/I18n/Locale.php new file mode 100644 index 00000000000..ff31813f1db --- /dev/null +++ b/engine/classes/Elgg/I18n/Locale.php @@ -0,0 +1,49 @@ +locale = $locale; + } + + /** @inheritDoc */ + public function __toString() { + return $this->locale; + } + + /** + * Create a language, asserting that the language code is valid. + * + * @param string $locale Language code + * + * @return Locale + * + * @throws InvalidLocaleException + */ + public static function parse($locale) { + // TODO(evan): Better sanitizing of locales using \Locale perhaps + if (strlen($locale) < 2 || strlen($locale) > 5) { + throw new InvalidLocaleException("Unrecognized locale: $locale"); + } + + return new Locale($locale); + } +} \ No newline at end of file diff --git a/engine/classes/Elgg/I18n/MessageBundle.php b/engine/classes/Elgg/I18n/MessageBundle.php new file mode 100644 index 00000000000..1d8cd07273f --- /dev/null +++ b/engine/classes/Elgg/I18n/MessageBundle.php @@ -0,0 +1,25 @@ +template = $template; + } + + /** + * Applies the inputs to the message template and returns the result. + * + * @param array $args The inputs to this message + * + * @return string The rendered including all the interpolated inputs + */ + public abstract function format(array $args); + + /** + * Get the string template this message uses for translation. + * + * @return string + */ + public function __toString() { + return $this->template; + } +} \ No newline at end of file diff --git a/engine/classes/Elgg/I18n/MessageTranslator.php b/engine/classes/Elgg/I18n/MessageTranslator.php new file mode 100644 index 00000000000..9357798dccf --- /dev/null +++ b/engine/classes/Elgg/I18n/MessageTranslator.php @@ -0,0 +1,53 @@ +defaultLocale = $defaultLocale; + $this->messages = $messages; + } + + /** @inheritDoc */ + public function translate($key, array $args = [], Locale $locale = null) { + $locales = [ + $locale, + $this->defaultLocale, + Locale::parse('en'), + ]; + + foreach ($locales as $locale) { + if (!$locale) { + continue; + } + + $message = $this->messages->get($key, $locale); + + if ($message) { + return $message->format($args); + } + } + + return $key; + } +} \ No newline at end of file diff --git a/engine/classes/Elgg/I18n/NullMessageTemplate.php b/engine/classes/Elgg/I18n/NullMessageTemplate.php new file mode 100644 index 00000000000..e2d4c1cc34e --- /dev/null +++ b/engine/classes/Elgg/I18n/NullMessageTemplate.php @@ -0,0 +1,18 @@ +template, $args); + } +} \ No newline at end of file diff --git a/engine/classes/Elgg/I18n/Translator.php b/engine/classes/Elgg/I18n/Translator.php index be190804d55..4c1aa88b9b6 100644 --- a/engine/classes/Elgg/I18n/Translator.php +++ b/engine/classes/Elgg/I18n/Translator.php @@ -6,9 +6,7 @@ * * @access private * - * @package Elgg.Core - * @subpackage I18n - * @since 1.10.0 + * @since 1.10.0 */ class Translator { diff --git a/engine/classes/Elgg/I18n/TranslatorInterface.php b/engine/classes/Elgg/I18n/TranslatorInterface.php new file mode 100644 index 00000000000..d468f5a5d31 --- /dev/null +++ b/engine/classes/Elgg/I18n/TranslatorInterface.php @@ -0,0 +1,37 @@ + en). + * + * If no locale is specified, or if no translation can be found for the specified + * locale, the translator may choose to fall back to some other language(s). + * + * It should never throw exceptions, since lack of translation should never be + * cause to bring down an app or cancel a request. However, implementations may + * log warnings to alert admins that requested language strings are missing. + * + * @param string $key A key identifying the message to translate. + * @param array $args An array of arguments with which to format the message. + * @param Locale $locale Optionally, the standard language code + * (defaults to site/user default, then English) + * + * @return string The final, best-effort translation. + */ + function translate($key, array $args = [], Locale $locale = null); +} \ No newline at end of file diff --git a/engine/tests/phpunit/Elgg/I18n/MessageTranslatorTest.php b/engine/tests/phpunit/Elgg/I18n/MessageTranslatorTest.php new file mode 100644 index 00000000000..d74340b4d18 --- /dev/null +++ b/engine/tests/phpunit/Elgg/I18n/MessageTranslatorTest.php @@ -0,0 +1,47 @@ +english = Locale::parse('en'); + $this->spanish = Locale::parse('es'); + } + + public function testKeyIsReturnedIfNoTranslationCanBeFound() { + $messages = new ArrayMessageBundle([]); + $translator = new MessageTranslator(Locale::parse('en'), $messages); + + $this->assertEquals('foobar', $translator->translate('foobar')); + } + + public function testTranslateReturnsTranslationForSpecifiedLocaleIfAvailable() { + $messages = new ArrayMessageBundle([ + 'en' => ['one' => 'one'], + 'es' => ['one' => 'uno'], + ]); + $translator = new MessageTranslator(Locale::parse('en'), $messages); + + $this->assertEquals('uno', $translator->translate('one', [], Locale::parse('es'))); + } + + public function testTranslateReturnsTranslationForDefaultLocaleIfNoLocaleWasSpecified() { + $messages = new ArrayMessageBundle([ + 'en' => ['one' => 'one'], + 'es' => ['one' => 'uno'], + ]); + $translator = new MessageTranslator(Locale::parse('en'), $messages); + + $this->assertEquals('one', $translator->translate('one', [])); + } + + public function testFallsBackToLanguageIfTranslationForSpecifiedLanguageIsNotAvailable() { + $messages = new ArrayMessageBundle([ + 'en' => ['one' => 'one'], + ]); + $translator = new MessageTranslator(Locale::parse('en'), $messages); + + $this->assertEquals('one', $translator->translate('one', [], Locale::parse('es'))); + } +} \ No newline at end of file diff --git a/engine/tests/phpunit/Elgg/I18n/TranslatorTest.php b/engine/tests/phpunit/Elgg/I18n/TranslatorTest.php index 92aaacf26ea..bb9347b50c5 100644 --- a/engine/tests/phpunit/Elgg/I18n/TranslatorTest.php +++ b/engine/tests/phpunit/Elgg/I18n/TranslatorTest.php @@ -12,7 +12,7 @@ public function testSetLanguageFromGetParameter() { _elgg_services()->input->set('hl', $input_lang); $lang = $translator->getLanguage(); - $this->assertEquals($lang, $input_lang); + $this->assertEquals($lang, $input_lang); } public function testCheckLanguageKeyExists() { @@ -24,4 +24,7 @@ public function testCheckLanguageKeyExists() { $this->assertFalse($translator->languageKeyExists('__elgg_php_unit:test_key:missing')); } + public function testDoesNotPerformSprintfFormattingIfArgsNotProvided() { + $this->markTestIncomplete(); + } } \ No newline at end of file