From 75a2e7cf0aca8ad646d752223fa8dc02a8ab95c0 Mon Sep 17 00:00:00 2001 From: Ere Maijala <ere.maijala@helsinki.fi> Date: Thu, 2 Jul 2020 11:17:26 -0400 Subject: [PATCH] Expand capabilities of TranslatableStringInterface - Support non-translatable content - Support nested objects --- .../src/VuFind/I18n/TranslatableString.php | 19 +++- .../I18n/TranslatableStringInterface.php | 7 ++ .../I18n/Translator/TranslatorAwareTrait.php | 19 +++- .../I18n/TranslatableStringTest.php | 70 +++++++++++++ .../View/Helper/Root/TranslateTest.php | 97 ++++++++++++++++++- 5 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 module/VuFind/tests/unit-tests/src/VuFindTest/I18n/TranslatableStringTest.php diff --git a/module/VuFind/src/VuFind/I18n/TranslatableString.php b/module/VuFind/src/VuFind/I18n/TranslatableString.php index 1c6ceca53ec..0bc1943a0d4 100644 --- a/module/VuFind/src/VuFind/I18n/TranslatableString.php +++ b/module/VuFind/src/VuFind/I18n/TranslatableString.php @@ -52,16 +52,23 @@ class TranslatableString implements TranslatableStringInterface */ protected $displayString; + /** + * Whether translation is allowed + */ + protected $translatable; + /** * Constructor * * @param string $string Original string * @param string $displayString Translatable display string + * @param bool $translatable Whether translation is allowed */ - public function __construct($string, $displayString) + public function __construct($string, $displayString, $translatable = true) { $this->string = (string)$string; $this->displayString = $displayString; + $this->translatable = $translatable; } /** @@ -84,4 +91,14 @@ class TranslatableString implements TranslatableStringInterface { return $this->displayString; } + + /** + * Checks if the string can be translated + * + * @return bool + */ + public function isTranslatable() + { + return $this->translatable; + } } diff --git a/module/VuFind/src/VuFind/I18n/TranslatableStringInterface.php b/module/VuFind/src/VuFind/I18n/TranslatableStringInterface.php index 53d60ab905b..d258cc904df 100644 --- a/module/VuFind/src/VuFind/I18n/TranslatableStringInterface.php +++ b/module/VuFind/src/VuFind/I18n/TranslatableStringInterface.php @@ -45,4 +45,11 @@ interface TranslatableStringInterface * @return string */ public function getDisplayString(); + + /** + * Checks if the string can be translated + * + * @return bool + */ + public function isTranslatable(); } diff --git a/module/VuFind/src/VuFind/I18n/Translator/TranslatorAwareTrait.php b/module/VuFind/src/VuFind/I18n/Translator/TranslatorAwareTrait.php index 87bb415a778..57b9145c813 100644 --- a/module/VuFind/src/VuFind/I18n/Translator/TranslatorAwareTrait.php +++ b/module/VuFind/src/VuFind/I18n/Translator/TranslatorAwareTrait.php @@ -106,6 +106,9 @@ trait TranslatorAwareTrait // Special case: deal with objects with a designated display value: if ($str instanceof \VuFind\I18n\TranslatableStringInterface) { + if (!$str->isTranslatable()) { + return $str->getDisplayString(); + } // On this pass, don't use the $default, since we want to fail over // to getDisplayString before giving up: $translated = $this @@ -114,7 +117,17 @@ trait TranslatorAwareTrait return $translated; } // Override $domain/$str using getDisplayString() before proceeding: - list($domain, $str) = $this->extractTextDomain($str->getDisplayString()); + $str = $str->getDisplayString(); + // Also the display string can be a TranslatableString. This makes it + // possible have multiple levels of translatable values while still + // providing a sane default string if translation is not found. Used at + // least with hierarchical facets where translation key can be the exact + // facet value (e.g. "0/Book/") or a displayable value (e.g. "Book"). + if ($str instanceof \VuFind\I18n\TranslatableStringInterface) { + return $this->translate($str, $tokens, $default); + } else { + list($domain, $str) = $this->extractTextDomain($str); + } } // Default case: deal with ordinary strings (or string-castable objects): @@ -204,7 +217,9 @@ trait TranslatorAwareTrait } if ($target instanceof \VuFind\I18n\TranslatableStringInterface) { $class = get_class($target); - $parts[1] = new $class($parts[1], $target->getDisplayString()); + $parts[1] = new $class( + $parts[1], $target->getDisplayString(), $target->isTranslatable() + ); } return $parts; } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/I18n/TranslatableStringTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/I18n/TranslatableStringTest.php new file mode 100644 index 00000000000..1c0e1d01c1b --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/I18n/TranslatableStringTest.php @@ -0,0 +1,70 @@ +<?php +/** + * TranslatableString Test Class + * + * Note that most tests using TranslatableString are in + * VuFindTest\View\Helper\Root\TranslateTest + * + * PHP version 7 + * + * Copyright (C) The National Library of Finland 2020. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Tests + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +namespace VuFindTest\I18n\Translator\Loader; + +use VuFind\I18n\TranslatableString; + +/** + * TranslatableString Test Class + * + * Note that most tests using TranslatableString are in + * VuFindTest\View\Helper\Root\TranslateTest + * + * @category VuFind + * @package Tests + * @author Ere Maijala <ere.maijala@helsinki.fi> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class TranslatableStringTest extends \VuFindTest\Unit\TestCase +{ + /** + * Test standalone behavior. + * + * @return void + */ + public function testWithoutTranslate() + { + $s = new TranslatableString('foo', 'bar'); + $this->assertEquals('foo', (string)$s); + $this->assertEquals('bar', $s->getDisplayString()); + $this->assertTrue($s->isTranslatable()); + + $s = new TranslatableString('foo', new TranslatableString('bar', 'baz')); + $this->assertEquals('foo', (string)$s); + $this->assertEquals('bar', (string)$s->getDisplayString()); + $this->assertEquals('baz', $s->getDisplayString()->getDisplayString()); + $this->assertTrue($s->isTranslatable()); + + $s = new TranslatableString('foo', 'bar', false); + $this->assertFalse($s->isTranslatable()); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/TranslateTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/TranslateTest.php index 3a42c737dca..aa689c9cc6d 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/TranslateTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/View/Helper/Root/TranslateTest.php @@ -127,6 +127,25 @@ class TranslateTest extends \PHPUnit\Framework\TestCase ); } + /** + * Test TranslatableString default values. + * + * @return void + */ + public function testTranslateTranslatableStringDefaultValues() + { + $translate = new Translate(); + $translate->setTranslator( + $this->getMockTranslator(['default' => []]) + ); + + $s = new TranslatableString('foo', 'bar'); + $this->assertEquals('bar', $translate->__invoke($s)); + + $s = new TranslatableString('foo', new TranslatableString('bar', 'baz')); + $this->assertEquals('baz', $translate->__invoke($s)); + } + /** * Test translation of a TranslatableString object with a loaded translator * @@ -136,7 +155,12 @@ class TranslateTest extends \PHPUnit\Framework\TestCase { $translate = new Translate(); $translate->setTranslator( - $this->getMockTranslator(['default' => ['foo' => '%%token%%']]) + $this->getMockTranslator( + [ + 'default' => ['foo' => '%%token%%'], + 'other' => ['foo' => 'Foo', 'bar' => 'Bar'] + ] + ) ); // Test a TranslatableString with a translation. @@ -165,6 +189,22 @@ class TranslateTest extends \PHPUnit\Framework\TestCase $str3, ['%%token%%' => 'baz'], 'failure' ) ); + + // Test a TranslatableString with another TranslatableString as a fallback. + $str4 = new TranslatableString( + 'xyzzy', new TranslatableString('bar', 'baz') + ); + $this->assertEquals('baz', $translate->__invoke($str4)); + $str5 = new TranslatableString( + 'xyzzy', new TranslatableString('foo', 'baz') + ); + $this->assertEquals('%%token%%', $translate->__invoke($str5)); + + // Test a TranslatableString with translation forbidden + $str6 = new TranslatableString('foo', 'bar', false); + $this->assertEquals('bar', $translate->__invoke($str6)); + $str7 = new TranslatableString('foo', '', false); + $this->assertEquals('', $translate->__invoke($str7)); } /** @@ -194,6 +234,40 @@ class TranslateTest extends \PHPUnit\Framework\TestCase // No string translatable $str3 = new TranslatableString('d1::f2', 'd2::f1'); $this->assertEquals('failure', $translate->__invoke($str3, [], 'failure')); + + // Secondary string a translatable TranslatableString + $str4 = new TranslatableString( + 'd1::f2', new TranslatableString('d2::f2', 'd3::f3') + ); + $this->assertEquals('str2', $translate->__invoke($str4)); + // Secondary string a TranslatableString with no translation + $str5 = new TranslatableString( + 'd1::f2', new TranslatableString('d2::f1', 'failure') + ); + $this->assertEquals('failure', $translate->__invoke($str5)); + // Secondary string a non-translatable TranslatableString + $str6 = new TranslatableString( + 'd1::f2', new TranslatableString('d2::f2', 'failure', false) + ); + $this->assertEquals('failure', $translate->__invoke($str6)); + + // Three levels of TranslatableString with the last one translatable + $str7 = new TranslatableString( + 'd1::f2', + new TranslatableString( + 'd3::f3', new TranslatableString('d2::f2', 'failure') + ) + ); + $this->assertEquals('str2', $translate->__invoke($str7)); + + // Three levels of TranslatableString with no translation + $str8 = new TranslatableString( + 'd1::f2', + new TranslatableString( + 'd3::f3', new TranslatableString('d3::f2', 'failure') + ) + ); + $this->assertEquals('failure', $translate->__invoke($str8)); } /** @@ -223,6 +297,27 @@ class TranslateTest extends \PHPUnit\Framework\TestCase ); } + /** + * Test nested translation with potential text domain conflict + * + * @return void + */ + public function testTranslateNestedTextDomainWithConflict() + { + $translations = [ + 'd1' => ['foo' => 'bar', 'failure' => 'success'], + 'd2' => ['baz' => 'xyzzy', 'failure' => 'mediocrity'], + ]; + $translate = new Translate(); + $translate->setTranslator( + $this->getMockTranslator($translations) + ); + $str = new TranslatableString( + 'd1::baz', new TranslatableString('d2::foo', 'failure') + ); + $this->assertEquals('failure', $translate->__invoke($str)); + } + /** * Test locale retrieval without a loaded translator * -- GitLab