mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-08-29 14:06:08 +02:00
481 lines
18 KiB
PHP
481 lines
18 KiB
PHP
<?php
|
|
|
|
namespace SilverStripe\i18n\Tests;
|
|
|
|
use InvalidArgumentException;
|
|
use SilverStripe\Control\Director;
|
|
use SilverStripe\Core\Convert;
|
|
use SilverStripe\Core\Injector\Injector;
|
|
use SilverStripe\Dev\SapphireTest;
|
|
use SilverStripe\i18n\i18n;
|
|
use SilverStripe\i18n\Messages\MessageProvider;
|
|
use SilverStripe\i18n\Messages\Symfony\SymfonyMessageProvider;
|
|
use SilverStripe\View\ArrayData;
|
|
use SilverStripe\View\SSViewer;
|
|
|
|
class i18nTest extends SapphireTest
|
|
{
|
|
use i18nTestManifest;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->setupManifest();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$this->tearDownManifest();
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function testGetExistingTranslations()
|
|
{
|
|
$translations = i18n::getSources()->getKnownLocales();
|
|
$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
|
|
// As per set of locales loaded from _fakewebroot
|
|
$translations = i18n::getSources()->getKnownLocales();
|
|
$this->assertEquals(
|
|
[
|
|
'en_GB',
|
|
'en_US',
|
|
'fr_FR',
|
|
'de_AT',
|
|
'de_DE',
|
|
'ja_JP',
|
|
'mi_NZ',
|
|
'pl_PL',
|
|
'es_AR',
|
|
'es_ES',
|
|
],
|
|
array_keys($translations ?? [])
|
|
);
|
|
|
|
// 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()
|
|
{
|
|
i18n::set_locale('de_DE');
|
|
|
|
// Load into the translator as a literal array data source
|
|
/** @var SymfonyMessageProvider $provider */
|
|
$provider = Injector::inst()->get(MessageProvider::class);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[ i18nTest\TestDataObject::class . '.MyProperty' => 'MyProperty' ],
|
|
'en_US'
|
|
);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[ i18nTest\TestDataObject::class . '.MyProperty' => 'Mein Attribut' ],
|
|
'de_DE'
|
|
);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[ i18nTest\TestDataObject::class . '.MyUntranslatedProperty' => 'Mein Attribut' ],
|
|
'en_US'
|
|
);
|
|
|
|
// Test field labels
|
|
$obj = new i18nTest\TestDataObject();
|
|
$this->assertEquals(
|
|
'Mein Attribut',
|
|
$obj->fieldLabel('MyProperty')
|
|
);
|
|
$this->assertEquals(
|
|
'My untranslated property',
|
|
$obj->fieldLabel('MyUntranslatedProperty')
|
|
);
|
|
}
|
|
|
|
public function testProvideI18nEntities()
|
|
{
|
|
/** @var SymfonyMessageProvider $provider */
|
|
$provider = Injector::inst()->get(MessageProvider::class);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[ i18nTest\TestObject::class . '.MyProperty' => 'Untranslated' ],
|
|
'en_US'
|
|
);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[ i18nTest\TestObject::class . '.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::config()->set('missing_default_warning', false);
|
|
|
|
/** @var SymfonyMessageProvider $provider */
|
|
$provider = Injector::inst()->get(MessageProvider::class);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[
|
|
'i18nTestModule.MAINTEMPLATE' => 'Main Template',
|
|
'REPLACEMENTNONAMESPACE' => 'My replacement no namespace: {replacement}',
|
|
'i18nTestModule.LAYOUTTEMPLATE' => 'Layout Template',
|
|
'LAYOUTTEMPLATENONAMESPACE' => 'Layout Template no namespace',
|
|
'i18nTestModule.REPLACEMENTNAMESPACE' => 'My replacement: {replacement}',
|
|
'i18nTestModule.WITHNAMESPACE' => 'Include Entity with Namespace',
|
|
'NONAMESPACE' => 'Include Entity without Namespace',
|
|
'i18nTestModuleInclude_ss.REPLACEMENTINCLUDENAMESPACE' => 'My include replacement: {replacement}',
|
|
'REPLACEMENTINCLUDENONAMESPACE' => 'My include replacement no namespace: {replacement}'
|
|
],
|
|
'en_US'
|
|
);
|
|
|
|
$viewer = new SSViewer('i18nTestModule');
|
|
$parsedHtml = Convert::nl2os($viewer->process(new ArrayData([
|
|
'TestProperty' => 'TestPropertyValue'
|
|
])));
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("Layout Template\n"),
|
|
$parsedHtml
|
|
);
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("Layout Template no namespace\n"),
|
|
$parsedHtml
|
|
);
|
|
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[
|
|
'i18nTestModule.MAINTEMPLATE' => 'TRANS Main Template',
|
|
'REPLACEMENTNONAMESPACE' => 'TRANS My replacement no namespace: {replacement}',
|
|
'i18nTestModule.LAYOUTTEMPLATE' => 'TRANS Layout Template',
|
|
'LAYOUTTEMPLATENONAMESPACE' => 'TRANS Layout Template no namespace',
|
|
'i18nTestModule.REPLACEMENTNAMESPACE' => 'TRANS My replacement: {replacement}',
|
|
'i18nTestModule.WITHNAMESPACE' => 'TRANS Include Entity with Namespace',
|
|
'NONAMESPACE' => 'TRANS Include Entity without Namespace',
|
|
'i18nTestModuleInclude_ss.REPLACEMENTINCLUDENAMESPACE' => 'TRANS My include replacement: {replacement}',
|
|
'REPLACEMENTINCLUDENONAMESPACE' => 'TRANS My include replacement no namespace: {replacement}',
|
|
'i18nTestModule.PLURALS' => 'An item|{count} items',
|
|
],
|
|
'de_DE'
|
|
);
|
|
|
|
i18n::set_locale('de_DE');
|
|
$viewer = new SSViewer('i18nTestModule');
|
|
$parsedHtml = Convert::nl2os($viewer->process(new ArrayData(['TestProperty' => 'TestPropertyValue'])));
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS Main Template\n"),
|
|
$parsedHtml
|
|
);
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS Layout Template\n"),
|
|
$parsedHtml
|
|
);
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS Layout Template no namespace\n"),
|
|
$parsedHtml
|
|
);
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS My replacement: TestPropertyValue\n"),
|
|
$parsedHtml
|
|
);
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS Include Entity with Namespace\n"),
|
|
$parsedHtml
|
|
);
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS Include Entity without Namespace\n"),
|
|
$parsedHtml
|
|
);
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS My include replacement: TestPropertyValue\n"),
|
|
$parsedHtml
|
|
);
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS My include replacement no namespace: TestPropertyValue\n"),
|
|
$parsedHtml
|
|
);
|
|
// Check plurals
|
|
$this->assertStringContainsString('Single: An item', $parsedHtml);
|
|
$this->assertStringContainsString('Multiple: 4 items', $parsedHtml);
|
|
$this->assertStringContainsString('None: 0 items', $parsedHtml);
|
|
|
|
i18n::set_locale($oldLocale);
|
|
}
|
|
|
|
public function testNewTMethodSignature()
|
|
{
|
|
/** @var SymfonyMessageProvider $provider */
|
|
$provider = Injector::inst()->get(MessageProvider::class);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[
|
|
'i18nTestModule.NEWMETHODSIG' => 'TRANS New _t method signature test',
|
|
'i18nTestModule.INJECTIONS' => 'TRANS Hello {name} {greeting}. But it is late, {goodbye}'
|
|
],
|
|
'en_US'
|
|
);
|
|
|
|
$entity = "i18nTestModule.INJECTIONS";
|
|
$default = "Hello {name} {greeting}. But it is late, {goodbye}";
|
|
|
|
// Test missing entity key
|
|
$translated = i18n::_t(
|
|
$entity . '_DOES_NOT_EXIST',
|
|
$default,
|
|
["name"=>"Mark", "greeting"=>"welcome", "goodbye"=>"bye"]
|
|
);
|
|
$this->assertStringContainsString(
|
|
"Hello Mark welcome. But it is late, bye",
|
|
$translated,
|
|
"Testing fallback to the translation default (but using the injection array)"
|
|
);
|
|
|
|
// Test standard injection
|
|
$translated = i18n::_t(
|
|
$entity,
|
|
$default,
|
|
["name"=>"Paul", "greeting"=>"good you are here", "goodbye"=>"see you"]
|
|
);
|
|
$this->assertStringContainsString(
|
|
"TRANS Hello Paul good you are here. But it is late, see you",
|
|
$translated,
|
|
"Testing entity, default string and injection array"
|
|
);
|
|
|
|
// @deprecated 5.0 Passing in context
|
|
$translated = i18n::_t(
|
|
$entity,
|
|
$default,
|
|
"New context (this should be ignored)",
|
|
["name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen"]
|
|
);
|
|
$this->assertStringContainsString(
|
|
"TRANS Hello Steffen willkommen. But it is late, wiedersehen",
|
|
$translated,
|
|
"Full test of translation, using default, context and injection array"
|
|
);
|
|
|
|
// Passing in non-associative arrays for placeholders is now an error
|
|
$this->expectExceptionMessage(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('Injection must be an associative array');
|
|
|
|
i18n::_t(
|
|
$entity, // has {name} placeholders
|
|
$default,
|
|
["Cat", "meow", "meow"]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* See @i18nTestModule.ss for the template that is being used for this test
|
|
* */
|
|
public function testNewTemplateTranslation()
|
|
{
|
|
i18n::config()->set('missing_default_warning', false);
|
|
|
|
/** @var SymfonyMessageProvider $provider */
|
|
$provider = Injector::inst()->get(MessageProvider::class);
|
|
$provider->getTranslator()->addResource(
|
|
'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(['TestProperty' => 'TestPropertyValue'])));
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("Hello Mark welcome. But it is late, bye\n"),
|
|
$parsedHtml,
|
|
"Testing fallback to the translation default (but using the injection array)"
|
|
);
|
|
|
|
$this->assertStringContainsString(
|
|
Convert::nl2os("TRANS Hello Paul good you are here. But it is late, see you\n"),
|
|
$parsedHtml,
|
|
"Testing entity, default string and injection array"
|
|
);
|
|
|
|
//test injected calls
|
|
$this->assertStringContainsString(
|
|
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"
|
|
);
|
|
}
|
|
|
|
public function testGetLocaleFromLang()
|
|
{
|
|
$this->assertEquals('en_US', i18n::getData()->localeFromLang('en'));
|
|
$this->assertEquals('de_DE', i18n::getData()->localeFromLang('de_DE'));
|
|
$this->assertEquals('xy_XY', i18n::getData()->localeFromLang('xy'));
|
|
}
|
|
|
|
public function testValidateLocale()
|
|
{
|
|
$this->assertTrue(i18n::getData()->validate('en_US'), 'Known locale in underscore format is valid');
|
|
$this->assertTrue(i18n::getData()->validate('en-US'), 'Known locale in dash format is valid');
|
|
$this->assertFalse(i18n::getData()->validate('en'), 'Short lang format is not valid');
|
|
$this->assertFalse(i18n::getData()->validate('xx_XX'), 'Unknown locale in correct format is not valid');
|
|
$this->assertFalse(i18n::getData()->validate(''), 'Empty string is not valid');
|
|
$this->assertTrue(i18n::getData()->validate('de_DE'), 'Known locale where language is same as region');
|
|
$this->assertTrue(i18n::getData()->validate('fr-FR'), 'Known locale where language is same as region');
|
|
$this->assertTrue(i18n::getData()->validate('zh_cmn'), 'Known locale with all lowercase letters');
|
|
}
|
|
|
|
public function testTranslate()
|
|
{
|
|
/** @var SymfonyMessageProvider $provider */
|
|
$provider = Injector::inst()->get(MessageProvider::class);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[ 'i18nTestModule.ENTITY' => 'Entity with "Double Quotes"' ],
|
|
'en_US'
|
|
);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[
|
|
'i18nTestModule.ENTITY' => 'Entity with "Double Quotes" (de)',
|
|
'i18nTestModule.ADDITION' => 'Addition (de)',
|
|
],
|
|
'de'
|
|
);
|
|
$provider->getTranslator()->addResource(
|
|
'array',
|
|
[
|
|
'i18nTestModule.ENTITY' => 'Entity with "Double Quotes" (de_AT)',
|
|
],
|
|
'de_AT'
|
|
);
|
|
|
|
|
|
$this->assertEquals(
|
|
'Entity with "Double Quotes"',
|
|
i18n::_t('i18nTestModule.ENTITY', 'Ignored default'),
|
|
'Returns translation in default language'
|
|
);
|
|
|
|
i18n::set_locale('de');
|
|
$this->assertEquals(
|
|
'Entity with "Double Quotes" (de)',
|
|
i18n::_t('i18nTestModule.ENTITY', 'Entity with "Double Quotes"'),
|
|
'Returns translation according to current locale'
|
|
);
|
|
|
|
i18n::set_locale('de_AT');
|
|
$this->assertEquals(
|
|
'Entity with "Double Quotes" (de_AT)',
|
|
i18n::_t('i18nTestModule.ENTITY', 'Entity with "Double Quotes"'),
|
|
'Returns specific regional translation if available'
|
|
);
|
|
$this->assertEquals(
|
|
'Addition (de)',
|
|
i18n::_t('i18nTestModule.ADDITION', 'Addition'),
|
|
'Returns fallback non-regional translation if regional is not available'
|
|
);
|
|
|
|
i18n::set_locale('fr');
|
|
$this->assertEquals(
|
|
'Entity with "Double Quotes" (fr)',
|
|
i18n::_t('i18nTestModule.ENTITY', 'Entity with "Double Quotes"'),
|
|
'Non-specific locales fall back to language-only localisations'
|
|
);
|
|
}
|
|
|
|
public function pluralisationDataProvider()
|
|
{
|
|
return [
|
|
// English - 2 plural forms
|
|
['en_NZ', 0, '0 months'],
|
|
['en_NZ', 1, 'A month'],
|
|
['en_NZ', 2, '2 months'],
|
|
['en_NZ', 5, '5 months'],
|
|
['en_NZ', 10, '10 months'],
|
|
// Polish - 4 plural forms
|
|
['pl_PL', 0, '0 miesięcy'],
|
|
['pl_PL', 1, '1 miesiąc'],
|
|
['pl_PL', 2, '2 miesiące'],
|
|
['pl_PL', 5, '5 miesięcy'],
|
|
['pl_PL', 10, '10 miesięcy'],
|
|
// Japanese - 1 plural form
|
|
['ja_JP', 0, '0日'],
|
|
['ja_JP', 1, '1日'],
|
|
['ja_JP', 2, '2日'],
|
|
['ja_JP', 5, '5日'],
|
|
['ja_JP', 10, '10日'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider pluralisationDataProvider()
|
|
* @param string $locale
|
|
* @param int $count
|
|
* @param string $expected
|
|
*/
|
|
public function testPluralisation($locale, $count, $expected)
|
|
{
|
|
i18n::set_locale($locale);
|
|
$this->assertEquals(
|
|
$expected,
|
|
_t('Month.PLURALS', 'A month|{count} months', ['count' => $count]),
|
|
"Plural form in locale $locale with count $count should be $expected"
|
|
);
|
|
}
|
|
|
|
public function testGetLanguageName()
|
|
{
|
|
i18n::config()->merge(
|
|
'common_languages',
|
|
['de_CGN' => ['name' => 'German (Cologne)', 'native' => 'Kölsch']]
|
|
);
|
|
$this->assertEquals('German', i18n::getData()->languageName('de_CGN'));
|
|
$this->assertEquals('Deutsch', i18n::with_locale('de_CGN', function () {
|
|
return i18n::getData()->languageName('de_CGN');
|
|
}));
|
|
}
|
|
}
|