silverstripe-framework/tests/i18n/i18nTest.php
Ingo Schommer 00ffe72944 Translations: Switch to Transifex format
- Based on new (last) translation download from getlocalization.com
- Removed untranslated strings. Getlocalization started including those at some point
which is highly annoying, unnecessary and breaks the new transfix system,
since it'll mark all of the english strings as actual translations
- Avoid dots in entities. It confuses the Transifex YML parser
- Removed some locales unknown to Transifex which didn't have any translations anyway
- Removed "lolcat" locale, uses custom notation (en@lolcal)
  which SilverStripe's i18n system can't handle
  (needs mapping from SS naming to Zend naming)
- Renamed "Te Reo/Maori" locale from "mi_NZ" to "mi" (Transifex/CLDR notation)
- Namespaced all entities used in templates (deprecated usage)
- Converted dots to underscores where template filenames are used for namespaces,
since Transifex YML parsing handles them as separate YML keys otherwise
- Removed whitespace in entity names, SilverStripe i18n can't handle it
- Only allow selection of locales registered through i18n::$all_locales to avoid
  issues with unknown locales in Zend's CLDR database
2013-08-07 00:25:16 +02:00

655 lines
22 KiB
PHP

<?php
require_once 'Zend/Translate.php';
/**
* @package framework
* @subpackage tests
*/
class i18nTest extends SapphireTest {
/**
* @var string $tmpBasePath Used to write language files.
* We don't want to store them inside framework (or in any web-accessible place)
* in case something goes wrong with the file parsing.
*/
protected $alternateBaseSavePath;
/**
* @var string $alternateBasePath Fake webroot with a single module
* /i18ntestmodule which contains some files with _t() calls.
*/
protected $alternateBasePath;
protected $extraDataObjects = array(
'i18nTest_DataObject'
);
public function setUp() {
parent::setUp();
$this->alternateBasePath = $this->getCurrentAbsolutePath() . "/_fakewebroot";
$this->alternateBaseSavePath = TEMP_FOLDER . '/i18nTextCollectorTest_webroot';
FileSystem::makeFolder($this->alternateBaseSavePath);
Director::setBaseFolder($this->alternateBasePath);
// Push a template loader running from the fake webroot onto the stack.
$templateManifest = new SS_TemplateManifest($this->alternateBasePath, false, true);
$templateManifest->regenerate(false);
SS_TemplateLoader::instance()->pushManifest($templateManifest);
$this->_oldTheme = SSViewer::current_theme();
SSViewer::set_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',
'locale' => i18n::default_locale(),
'disableNotices' => true,
));
i18n::register_translator($adapter, 'core');
$adapter->removeCache();
i18n::include_by_locale('en');
}
public function tearDown() {
SS_TemplateLoader::instance()->popManifest();
i18n::set_locale($this->originalLocale);
Director::setBaseFolder(null);
SSViewer::set_theme($this->_oldTheme);
i18n::register_translator($this->origAdapter, 'core');
parent::tearDown();
}
public function testDateFormatFromLocale() {
i18n::set_locale('en_US');
$this->assertEquals('MMM d, y', i18n::get_date_format());
i18n::set_locale('en_NZ');
$this->assertEquals('d/MM/yyyy', i18n::get_date_format());
i18n::set_locale('en_US');
}
public function testTimeFormatFromLocale() {
i18n::set_locale('en_US');
$this->assertEquals('h:mm:ss a', i18n::get_time_format());
i18n::set_locale('de_DE');
$this->assertEquals('HH:mm:ss', i18n::get_time_format());
i18n::set_locale('en_US');
}
public function testDateFormatCustom() {
i18n::set_locale('en_US');
$this->assertEquals('MMM d, y', i18n::get_date_format());
i18n::set_date_format('d/MM/yyyy');
$this->assertEquals('d/MM/yyyy', i18n::get_date_format());
}
public function testTimeFormatCustom() {
i18n::set_locale('en_US');
$this->assertEquals('h:mm:ss a', i18n::get_time_format());
i18n::set_time_format('HH:mm:ss');
$this->assertEquals('HH:mm:ss', i18n::get_time_format());
}
public function testGetExistingTranslations() {
$translations = i18n::get_existing_translations();
$this->assertTrue(isset($translations['en']), 'Checking for en translation');
$this->assertEquals($translations['en'], 'English (United States)');
$this->assertTrue(isset($translations['de']), 'Checking for de_DE translation');
}
public function testDataObjectFieldLabels() {
$oldLocale = i18n::get_locale();
i18n::set_locale('de_DE');
$obj = new i18nTest_DataObject();
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_Object::$my_translatable_property,
'Untranslated'
);
$this->assertEquals(
i18nTest_Object::my_translatable_property(),
'Untranslated'
);
i18n::set_locale('en_US');
$this->assertEquals(
i18nTest_Object::my_translatable_property(),
'Untranslated',
'Getter returns original static value when called in default locale'
);
i18n::set_locale('de_DE');
$this->assertEquals(
i18nTest_Object::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 = $viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue')));
$this->assertContains(
"Layout Template\n",
$parsedHtml
);
$this->assertContains(
"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 = $viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue')));
$this->assertContains(
"TRANS Main Template\n",
$parsedHtml
);
$this->assertContains(
"TRANS Layout Template\n",
$parsedHtml
);
$this->assertContains(
"TRANS Layout Template no namespace",
$parsedHtml
);
$this->assertContains(
"TRANS My replacement: TestPropertyValue",
$parsedHtml
);
$this->assertContains(
"TRANS Include Entity with Namespace",
$parsedHtml
);
$this->assertContains(
"TRANS Include Entity without Namespace",
$parsedHtml
);
$this->assertContains(
"TRANS My include replacement: TestPropertyValue",
$parsedHtml
);
$this->assertContains(
"TRANS My include replacement no namespace: TestPropertyValue",
$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 = $viewer->process(new ArrayData(array('TestProperty' => 'TestPropertyValue')));
$this->assertContains(
"Hello Mark welcome. But it is late, bye\n",
$parsedHtml, "Testing fallback to the translation default (but using the injection array)"
);
$this->assertContains(
"TRANS Hello Paul good you are here. But it is late, see you\n",
$parsedHtml, "Testing entity, default string and injection array"
);
$this->assertContains(
"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(
"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 SS_ClassManifest($this->alternateBasePath, true, true, false);
SS_ClassLoader::instance()->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'
);
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');
SS_ClassLoader::instance()->popManifest();
}
public function testIncludeByLocaleWithoutFallbackLanguage() {
$classManifest = new SS_ClassManifest($this->alternateBasePath, true, true, false);
SS_ClassLoader::instance()->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');
SS_ClassLoader::instance()->popManifest();
}
public function testRegisterTranslator() {
$translator = new Zend_Translate(array(
'adapter' => 'i18nTest_CustomTranslatorAdapter',
'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('i18nTest_CustomTranslatorAdapter', $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 SS_ClassManifest($this->alternateBasePath, true, true, false);
SS_ClassLoader::instance()->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',
'locale' => i18n::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' => 'i18nTest_CustomTranslatorAdapter',
'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' => 'i18nTest_OtherCustomTranslatorAdapter',
'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' => 'i18nTest_OtherCustomTranslatorAdapter',
'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');
SS_ClassLoader::instance()->popManifest();
}
}
class i18nTest_DataObject extends DataObject implements TestOnly {
static $db = array(
'MyProperty' => 'Varchar',
'MyUntranslatedProperty' => 'Text'
);
static $has_one = array(
'HasOneRelation' => 'Member'
);
static $has_many = array(
'HasManyRelation' => 'Member'
);
static $many_many = array(
'ManyManyRelation' => 'Member'
);
/**
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*/
public function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['MyProperty'] = _t('i18nTest_DataObject.MyProperty', 'My Property');
return $labels;
}
}
class i18nTest_Object extends Object implements TestOnly, i18nEntityProvider {
static $my_translatable_property = "Untranslated";
public static function my_translatable_property() {
return _t("i18nTest_Object.my_translatable_property", self::$my_translatable_property);
}
public function provideI18nEntities() {
return array(
"i18nTest_Object.my_translatable_property" => array(
self::$my_translatable_property
)
);
}
}
class i18nTest_CustomTranslatorAdapter extends Zend_Translate_Adapter
implements TestOnly,i18nTranslateAdapterInterface {
protected function _loadTranslationData($filename, $locale, array $options = array()) {
return array(
$locale => array(
'AdapterEntity1' => 'AdapterEntity1 CustomAdapter (' . $locale . ')',
'i18nTestModule.ENTITY' => 'i18nTestModule.ENTITY CustomAdapter (' . $locale . ')',
)
);
}
public function toString() {
return 'i18nTest_CustomTranslatorAdapter';
}
public function getFilenameForLocale($locale) {
return false; // not file based
}
}
class i18nTest_OtherCustomTranslatorAdapter extends Zend_Translate_Adapter
implements TestOnly,i18nTranslateAdapterInterface {
protected function _loadTranslationData($filename, $locale, array $options = array()) {
return array(
$locale => array(
'i18nTestModule.ENTITY' => 'i18nTestModule.ENTITY OtherCustomAdapter (' . $locale . ')',
)
);
}
public function toString() {
return 'i18nTest_OtherCustomTranslatorAdapter';
}
public function getFilenameForLocale($locale) {
return false; // not file based
}
}