preloadClasses as $class) { if (!class_exists($class)) { throw new \LogicException("Could not load class $class"); } } $s = DIRECTORY_SEPARATOR; $this->alternateBasePath = __DIR__ . $s . 'i18nTest' . $s . "_fakewebroot"; $this->alternateBaseSavePath = TEMP_FOLDER . $s . 'i18nTextCollectorTest_webroot'; Filesystem::makeFolder($this->alternateBaseSavePath); Director::config()->update('alternate_base_folder', $this->alternateBasePath); // Replace old template loader with new one with alternate base path $this->_oldLoader = ThemeResourceLoader::instance(); ThemeResourceLoader::set_instance($loader = new ThemeResourceLoader($this->alternateBasePath)); $loader->addSet('$default', new ThemeManifest( $this->alternateBasePath, project(), false, true )); SSViewer::config()->update('theme', 'testtheme1'); $this->originalLocale = i18n::get_locale(); // Override default adapter to avoid cached translations between tests. // Emulates behaviour in i18n::get_translators() $this->origAdapter = i18n::get_translator('core'); $adapter = new Zend_Translate(array( 'adapter' => i18nRailsYamlAdapter::class, 'locale' => i18n::config()->get('default_locale'), 'disableNotices' => true, )); i18n::register_translator($adapter, 'core'); $adapter->removeCache(); i18n::include_by_locale('en'); } /** * Number of test manifests * * @var int */ protected $manifests = 0; /** * Safely push a new class manifest. * These will be cleaned up on tearDown() * * @param ClassManifest $manifest */ protected function pushManifest(ClassManifest $manifest) { $this->manifests++; ClassLoader::instance()->pushManifest($manifest); } public function tearDown() { ThemeResourceLoader::set_instance($this->_oldLoader); i18n::set_locale($this->originalLocale); i18n::register_translator($this->origAdapter, 'core'); while($this->manifests > 0) { ClassLoader::instance()->popManifest(); $this->manifests--; } parent::tearDown(); } public function testGetExistingTranslations() { $translations = i18n::get_existing_translations(); $this->assertTrue(isset($translations['en_US']), 'Checking for en translation'); $this->assertEquals($translations['en_US'], 'English (United States)'); $this->assertTrue(isset($translations['de_DE']), 'Checking for de_DE translation'); } public function testGetClosestTranslation() { // Validate necessary assumptions for this test $translations = i18n::get_existing_translations(); $this->assertTrue(isset($translations['en_US'])); $this->assertTrue(isset($translations['en_GB'])); $this->assertTrue(isset($translations['es_ES'])); $this->assertTrue(isset($translations['es_AR'])); $this->assertFalse(isset($translations['en_ZZ'])); $this->assertFalse(isset($translations['es_ZZ'])); $this->assertFalse(isset($translations['zz_ZZ'])); // Test indeterminate locales $this->assertEmpty(i18n::get_closest_translation('zz_ZZ')); // Test english fallback $this->assertEquals('en_US', i18n::get_closest_translation('en_US')); $this->assertEquals('en_GB', i18n::get_closest_translation('en_GB')); $this->assertEquals('en_US', i18n::get_closest_translation('en_ZZ')); // Test spanish fallbacks $this->assertEquals('es_AR', i18n::get_closest_translation('es_AR')); $this->assertEquals('es_ES', i18n::get_closest_translation('es_ES')); $this->assertEquals('es_ES', i18n::get_closest_translation('es_XX')); } public function testDataObjectFieldLabels() { $oldLocale = i18n::get_locale(); i18n::set_locale('de_DE'); $obj = new i18nTest\TestDataObject(); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTest_DataObject.MyProperty' => 'MyProperty' ), 'en_US'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTest_DataObject.MyProperty' => 'Mein Attribut' ), 'de_DE'); $this->assertEquals( $obj->fieldLabel('MyProperty'), 'Mein Attribut' ); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTest_DataObject.MyUntranslatedProperty' => 'Mein Attribut' ), 'en_US'); $this->assertEquals( $obj->fieldLabel('MyUntranslatedProperty'), 'My Untranslated Property' ); i18n::set_locale($oldLocale); } public function testProvideI18nEntities() { $oldLocale = i18n::get_locale(); i18n::set_locale('en_US'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTest_Object.MyProperty' => 'Untranslated' ), 'en_US'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTest_Object.my_translatable_property' => 'Übersetzt' ), 'de_DE'); $this->assertEquals( i18nTest\TestObject::$my_translatable_property, 'Untranslated' ); $this->assertEquals( i18nTest\TestObject::my_translatable_property(), 'Untranslated' ); i18n::set_locale('en_US'); $this->assertEquals( i18nTest\TestObject::my_translatable_property(), 'Untranslated', 'Getter returns original static value when called in default locale' ); i18n::set_locale('de_DE'); $this->assertEquals( i18nTest\TestObject::my_translatable_property(), 'Übersetzt', 'Getter returns translated value when called in another locale' ); } public function testTemplateTranslation() { $oldLocale = i18n::get_locale(); i18n::set_locale('en_US'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTestModule.MAINTEMPLATE' => 'Main Template', 'i18nTestModule.ss.SPRINTFNONAMESPACE' => 'My replacement no namespace: %s', 'i18nTestModule.LAYOUTTEMPLATE' => 'Layout Template', 'i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE' => 'Layout Template no namespace', 'i18nTestModule.SPRINTFNAMESPACE' => 'My replacement: %s', 'i18nTestModule.WITHNAMESPACE' => 'Include Entity with Namespace', 'i18nTestModuleInclude.ss.NONAMESPACE' => 'Include Entity without Namespace', 'i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE' => 'My include replacement: %s', 'i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE' => 'My include replacement no namespace: %s' ), 'en_US'); $viewer = new SSViewer('i18nTestModule'); $parsedHtml = Convert::nl2os($viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue')))); $this->assertContains( Convert::nl2os("Layout Template\n"), $parsedHtml ); $this->assertContains( Convert::nl2os("Layout Template no namespace\n"), $parsedHtml ); i18n::set_locale('de_DE'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTestModule.MAINTEMPLATE' => 'TRANS Main Template', 'i18nTestModule.ss.SPRINTFNONAMESPACE' => 'TRANS My replacement no namespace: %s', 'i18nTestModule.LAYOUTTEMPLATE' => 'TRANS Layout Template', 'i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE' => 'TRANS Layout Template no namespace', 'i18nTestModule.SPRINTFNAMESPACE' => 'TRANS My replacement: %s', 'i18nTestModule.WITHNAMESPACE' => 'TRANS Include Entity with Namespace', 'i18nTestModuleInclude.ss.NONAMESPACE' => 'TRANS Include Entity without Namespace', 'i18nTestModuleInclude.ss.SPRINTFINCLUDENAMESPACE' => 'TRANS My include replacement: %s', 'i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE' => 'TRANS My include replacement no namespace: %s' ), 'de_DE'); $viewer = new SSViewer('i18nTestModule'); $parsedHtml = Convert::nl2os($viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue')))); $this->assertContains( Convert::nl2os("TRANS Main Template\n"), $parsedHtml ); $this->assertContains( Convert::nl2os("TRANS Layout Template\n"), $parsedHtml ); $this->assertContains( Convert::nl2os("TRANS Layout Template no namespace\n"), $parsedHtml ); $this->assertContains( Convert::nl2os("TRANS My replacement: TestPropertyValue\n"), $parsedHtml ); $this->assertContains( Convert::nl2os("TRANS Include Entity with Namespace\n"), $parsedHtml ); $this->assertContains( Convert::nl2os("TRANS Include Entity without Namespace\n"), $parsedHtml ); $this->assertContains( Convert::nl2os("TRANS My include replacement: TestPropertyValue\n"), $parsedHtml ); $this->assertContains( Convert::nl2os("TRANS My include replacement no namespace: TestPropertyValue\n"), $parsedHtml ); i18n::set_locale($oldLocale); } public function testNewTMethodSignature() { global $lang; $oldLocale = i18n::get_locale(); i18n::set_locale('en_US'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTestModule.NEWMETHODSIG' => 'TRANS New _t method signature test', 'i18nTestModule.INJECTIONS' => 'TRANS Hello {name} {greeting}. But it is late, {goodbye}', 'i18nTestModule.INJECTIONSLEGACY' => 'TRANS Hello %s %s. But it is late, %s', ), 'en_US'); $entity = "i18nTestModule.INJECTIONS"; $default = "Hello {name} {greeting}. But it is late, {goodbye}"; $translated = i18n::_t('i18nTestModule.NEWMETHODSIG',"New _t method signature test"); $this->assertContains( "TRANS New _t method signature test", $translated ); $translated = i18n::_t($entity.'_DOES_NOT_EXIST', $default, array("name"=>"Mark", "greeting"=>"welcome", "goodbye"=>"bye")); $this->assertContains( "Hello Mark welcome. But it is late, bye", $translated, "Testing fallback to the translation default (but using the injection array)" ); $translated = i18n::_t($entity, $default, array("name"=>"Paul", "greeting"=>"good you are here", "goodbye"=>"see you")); $this->assertContains( "TRANS Hello Paul good you are here. But it is late, see you", $translated, "Testing entity, default string and injection array" ); $translated = i18n::_t($entity, $default, "New context (this should be ignored)", array("name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen")); $this->assertContains( "TRANS Hello Steffen willkommen. But it is late, wiedersehen", $translated, "Full test of translation, using default, context and injection array" ); $translated = i18n::_t($entity, array("name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow")); $this->assertContains( "TRANS Hello Cat meow. But it is late, meow", $translated, "Testing a translation with just entity and injection array" ); $translated = i18n::_t( 'i18nTestModule.INJECTIONSLEGACY', // has %s placeholders array("name"=>"Cat", "greeting2"=>"meow", "goodbye"=>"meow") ); $this->assertContains( "TRANS Hello Cat meow. But it is late, meow", $translated, "Testing sprintf placeholders with named injections" ); $translated = i18n::_t( 'i18nTestModule.INJECTIONSLEGACY', // has %s placeholders array("Cat", "meow"/*, "meow" */) // remove third arg ); $this->assertContains( "TRANS Hello Cat meow. But it is late, ", $translated, "Testing sprintf placeholders with unnamed injections and too few args" ); $translated = i18n::_t( 'i18nTestModule.INJECTIONS', // has {name} placeholders array("Cat", "meow", "meow") ); $this->assertContains( "TRANS Hello Cat meow. But it is late, meow", $translated, "Testing named injection placeholders with unnamed injections" ); i18n::set_locale($oldLocale); } /** * See @i18nTestModule.ss for the template that is being used for this test * */ public function testNewTemplateTranslation() { global $lang; $oldLocale = i18n::get_locale(); i18n::set_locale('en_US'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTestModule.NEWMETHODSIG' => 'TRANS New _t method signature test', 'i18nTestModule.INJECTIONS' => 'TRANS Hello {name} {greeting}. But it is late, {goodbye}' ),'en_US'); $viewer = new SSViewer('i18nTestModule'); $parsedHtml = Convert::nl2os($viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue')))); $this->assertContains( Convert::nl2os("Hello Mark welcome. But it is late, bye\n"), $parsedHtml, "Testing fallback to the translation default (but using the injection array)" ); $this->assertContains( Convert::nl2os("TRANS Hello Paul good you are here. But it is late, see you\n"), $parsedHtml, "Testing entity, default string and injection array" ); $this->assertContains( Convert::nl2os("TRANS Hello Cat meow. But it is late, meow\n"), $parsedHtml, "Testing a translation with just entity and injection array" ); //test injected calls $this->assertContains( Convert::nl2os( "TRANS Hello ".Director::absoluteBaseURL()." ".i18n::get_locale().". But it is late, global calls\n" ), $parsedHtml, "Testing a translation with just entity and injection array, but with global variables injected in" ); i18n::set_locale($oldLocale); } public function testGetLocaleFromLang() { $this->assertEquals('en_US', i18n::get_locale_from_lang('en')); $this->assertEquals('de_DE', i18n::get_locale_from_lang('de_DE')); $this->assertEquals('xy_XY', i18n::get_locale_from_lang('xy')); } public function testValidateLocale() { $this->assertTrue(i18n::validate_locale('en_US'), 'Known locale in underscore format is valid'); $this->assertTrue(i18n::validate_locale('en-US'), 'Known locale in dash format is valid'); $this->assertFalse(i18n::validate_locale('en'), 'Short lang format is not valid'); $this->assertFalse(i18n::validate_locale('xx_XX'), 'Unknown locale in correct format is not valid'); $this->assertFalse(i18n::validate_locale(''), 'Empty string is not valid'); } public function testTranslate() { $oldLocale = i18n::get_locale(); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTestModule.ENTITY' => 'Entity with "Double Quotes"', ), 'en_US'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTestModule.ENTITY' => 'Entity with "Double Quotes" (de)', 'i18nTestModule.ADDITION' => 'Addition (de)', ), 'de'); i18n::get_translator('core')->getAdapter()->addTranslation(array( 'i18nTestModule.ENTITY' => 'Entity with "Double Quotes" (de_AT)', ), 'de_AT'); $this->assertEquals(i18n::_t('i18nTestModule.ENTITY'), 'Entity with "Double Quotes"', 'Returns translation in default language' ); i18n::set_locale('de'); $this->assertEquals(i18n::_t('i18nTestModule.ENTITY'), 'Entity with "Double Quotes" (de)', 'Returns translation according to current locale' ); i18n::set_locale('de_AT'); $this->assertEquals(i18n::_t('i18nTestModule.ENTITY'), 'Entity with "Double Quotes" (de_AT)', 'Returns specific regional translation if available' ); $this->assertEquals(i18n::_t('i18nTestModule.ADDITION'), 'Addition (de)', 'Returns fallback non-regional translation if regional is not available' ); i18n::set_locale('fr'); $this->assertEquals(i18n::_t('i18nTestModule.ENTITY'), '', 'Returns empty translation without default string if locale is not found' ); $this->assertEquals(i18n::_t('i18nTestModule.ENTITY', 'default'), 'default', 'Returns default string if locale is not found' ); i18n::set_locale($oldLocale); } public function testIncludeByLocale() { // Looping through modules, so we can test the translation autoloading // Load non-exclusive to retain core class autoloading $classManifest = new ClassManifest($this->alternateBasePath, true, true, false); $this->pushManifest($classManifest); $adapter = i18n::get_translator('core')->getAdapter(); $this->assertTrue($adapter->isAvailable('en')); $this->assertFalse($adapter->isAvailable('de')); $this->assertFalse($adapter->isTranslated('i18nTestModule.ENTITY', 'de'), 'Existing unloaded entity not available before call' ); $this->assertFalse($adapter->isTranslated('i18nTestModule.ENTITY', 'af'), 'Non-existing unloaded entity not available before call' ); // set _fakewebroot module priority i18n::config()->update('module_priority', array('subfolder','i18ntestmodule')); i18n::include_by_locale('de'); $this->assertTrue($adapter->isAvailable('en')); $this->assertTrue($adapter->isAvailable('de')); $this->assertTrue($adapter->isTranslated('i18nTestModule.ENTITY', null, 'de'), 'Includes module files'); $this->assertTrue($adapter->isTranslated('i18nTestTheme1.LAYOUTTEMPLATE', null, 'de'), 'Includes theme files'); $this->assertTrue($adapter->isTranslated('i18nTestModule.OTHERENTITY', null, 'de'), 'Includes submodule files'); // check module priority $this->assertEquals($adapter->translate('i18nTestModule.PRIORITYNOTICE', 'de'), 'High Module Priority (de)' ); } public function testIncludeByLocaleWithoutFallbackLanguage() { $classManifest = new ClassManifest($this->alternateBasePath, true, true, false); $this->pushManifest($classManifest); $adapter = i18n::get_translator('core')->getAdapter(); $this->assertTrue($adapter->isAvailable('en')); $this->assertFalse($adapter->isAvailable('mi')); // not defined at all $this->assertFalse($adapter->isAvailable('mi_NZ')); // defined, but not loaded yet $this->assertFalse($adapter->isTranslated('i18nTestModule.ENTITY', 'mi'), 'Existing unloaded entity not available before call' ); $this->assertFalse($adapter->isTranslated('i18nTestModule.ENTITY', 'mi_NZ'), 'Non-existing unloaded entity not available before call' ); i18n::include_by_locale('mi_NZ'); $this->assertFalse($adapter->isAvailable('mi')); $this->assertTrue($adapter->isAvailable('mi_NZ')); $this->assertTrue($adapter->isTranslated('i18nTestModule.ENTITY', null, 'mi_NZ'), 'Includes module files'); } public function testRegisterTranslator() { $translator = new Zend_Translate(array( 'adapter' => CustomTranslatorAdapter::class, 'disableNotices' => true, )); i18n::register_translator($translator, 'custom', 10); $translators = i18n::get_translators(); $this->assertArrayHasKey('custom', $translators[10]); $this->assertInstanceOf('Zend_Translate', $translators[10]['custom']); $this->assertInstanceOf(CustomTranslatorAdapter::class, $translators[10]['custom']->getAdapter()); i18n::unregister_translator('custom'); $translators = i18n::get_translators(); $this->assertArrayNotHasKey('custom', $translators[10]); } public function testMultipleTranslators() { // Looping through modules, so we can test the translation autoloading // Load non-exclusive to retain core class autoloading $classManifest = new ClassManifest($this->alternateBasePath, true, true, false); $this->pushManifest($classManifest); // Changed manifest, so we also need to unset all previously collected messages. // The easiest way to do this it to register a new adapter. $adapter = new Zend_Translate(array( 'adapter' => i18nRailsYamlAdapter::class, 'locale' => i18n::config()->get('default_locale'), 'disableNotices' => true, )); i18n::register_translator($adapter, 'core'); i18n::set_locale('en_US'); $this->assertEquals( i18n::_t('i18nTestModule.ENTITY'), 'Entity with "Double Quotes"' ); $this->assertEquals( i18n::_t('AdapterEntity1', 'AdapterEntity1'), 'AdapterEntity1', 'Falls back to default string if not found' ); // Add a new translator $translator = new Zend_Translate(array( 'adapter' => CustomTranslatorAdapter::class, 'disableNotices' => true, )); i18n::register_translator($translator, 'custom', 11); $this->assertEquals( i18n::_t('i18nTestModule.ENTITY'), 'i18nTestModule.ENTITY CustomAdapter (en_US)', 'Existing entities overruled by adapter with higher priority' ); $this->assertEquals( i18n::_t('AdapterEntity1', 'AdapterEntity1'), 'AdapterEntity1 CustomAdapter (en_US)', 'New entities only defined in new adapter are detected' ); // Add a second new translator to test priorities $translator = new Zend_Translate(array( 'adapter' => OtherCustomTranslatorAdapter::class, 'disableNotices' => true, )); i18n::register_translator($translator, 'othercustom_lower_prio', 5); $this->assertEquals( i18n::_t('i18nTestModule.ENTITY'), 'i18nTestModule.ENTITY CustomAdapter (en_US)', 'Adapter with lower priority loses' ); // Add a third new translator to test priorities $translator = new Zend_Translate(array( 'adapter' => OtherCustomTranslatorAdapter::class, 'disableNotices' => true, )); i18n::register_translator($translator, 'othercustom_higher_prio', 15); $this->assertEquals( i18n::_t('i18nTestModule.ENTITY'), 'i18nTestModule.ENTITY OtherCustomAdapter (en_US)', 'Adapter with higher priority wins' ); i18n::unregister_translator('custom'); i18n::unregister_translator('othercustom_lower_prio'); i18n::unregister_translator('othercustom_higher_prio'); } public function testGetLanguageName() { i18n::config()->update( 'common_languages', array('de_CGN' => array('name' => 'German (Cologne)', 'native' => 'Kölsch')) ); $this->assertEquals('German (Cologne)', i18n::get_language_name('de_CGN')); $this->assertEquals('Kölsch', i18n::get_language_name('de_CGN', true)); } }