Initial Drupal 11 with DDEV setup
This commit is contained in:
14
web/core/modules/locale/tests/src/Functional/GenericTest.php
Normal file
14
web/core/modules/locale/tests/src/Functional/GenericTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for locale.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests translation update's effects on configuration translations.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigTranslationImportTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'locale_test_translate'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests update changes configuration translations if enabled after language.
|
||||
*/
|
||||
public function testConfigTranslationImport(): void {
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'administer permissions',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Add a language. The Afrikaans translation file of locale_test_translate
|
||||
// (test.af.po) has been prepared with a configuration translation.
|
||||
ConfigurableLanguage::createFromLangcode('af')->save();
|
||||
|
||||
// Enable locale module.
|
||||
$this->container->get('module_installer')->install(['locale']);
|
||||
$this->resetAll();
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
||||
->save();
|
||||
|
||||
// Add translation permissions now that the locale module has been enabled.
|
||||
$edit = [
|
||||
'authenticated[translate interface]' => 'translate interface',
|
||||
];
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$this->submitForm($edit, 'Save permissions');
|
||||
|
||||
// Check and update the translation status. This will import the Afrikaans
|
||||
// translations of locale_test_translate module.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
|
||||
// Override the Drupal core translation status to be up to date.
|
||||
// Drupal core should not be a subject in this test.
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['af']->type = 'current';
|
||||
\Drupal::state()->set('locale.translation_status', $status);
|
||||
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->submitForm([], 'Update translations');
|
||||
|
||||
// Check if configuration translations have been imported.
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'system.maintenance');
|
||||
// cSpell:disable-next-line
|
||||
$this->assertEquals('Ons is tans besig met onderhoud op @site. Wees asseblief geduldig, ons sal binnekort weer terug wees.', $override->get('message'));
|
||||
|
||||
// Ensure that \Drupal\locale\LocaleConfigSubscriber::onConfigSave() works
|
||||
// as expected during a configuration install that installs locale.
|
||||
/** @var \Drupal\Core\Config\FileStorage $sync */
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($this->container->get('config.storage'), $sync);
|
||||
|
||||
// Add our own translation to the config that will be imported.
|
||||
$af_sync = $sync->createCollection('language.af');
|
||||
$data = $af_sync->read('system.maintenance');
|
||||
$data['message'] = 'Test af message';
|
||||
$af_sync->write('system.maintenance', $data);
|
||||
|
||||
// Uninstall locale module.
|
||||
$this->container->get('module_installer')->uninstall(['locale_test_translate']);
|
||||
$this->container->get('module_installer')->uninstall(['locale']);
|
||||
$this->resetAll();
|
||||
|
||||
$this->configImporter()->import();
|
||||
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['af']->type = 'current';
|
||||
\Drupal::state()->set('locale.translation_status', $status);
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->submitForm([], 'Update translations');
|
||||
|
||||
// Check if configuration translations have been imported.
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'system.maintenance');
|
||||
$this->assertEquals('Test af message', $override->get('message'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests update changes configuration translations if enabled after language.
|
||||
*/
|
||||
public function testConfigTranslationModuleInstall(): void {
|
||||
|
||||
// Enable locale, block and config_translation modules.
|
||||
$this->container->get('module_installer')->install(['block', 'config_translation']);
|
||||
$this->resetAll();
|
||||
|
||||
// The testing profile overrides locale.settings to disable translation
|
||||
// import. Test that this override is in place.
|
||||
$this->assertFalse($this->config('locale.settings')->get('translation.import_enabled'), 'Translations imports are disabled by default in the Testing profile.');
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'administer permissions',
|
||||
'translate configuration',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
||||
->save();
|
||||
|
||||
// Add predefined language.
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm(['predefined_langcode' => 'af'], 'Add language');
|
||||
|
||||
// Add the system branding block to the page.
|
||||
$this->drupalPlaceBlock('system_branding_block', ['region' => 'header', 'id' => 'site_branding']);
|
||||
$this->drupalGet('admin/config/system/site-information');
|
||||
$this->submitForm(['site_slogan' => 'Test site slogan'], 'Save configuration');
|
||||
$this->drupalGet('admin/config/system/site-information/translate/af/edit');
|
||||
$this->submitForm([
|
||||
'translation[config_names][system.site][slogan]' => 'Test site slogan in Afrikaans',
|
||||
], 'Save translation');
|
||||
|
||||
// Get the front page and ensure that the translated configuration appears.
|
||||
$this->drupalGet('af');
|
||||
$this->assertSession()->pageTextContains('Test site slogan in Afrikaans');
|
||||
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'locale_test_translate.settings');
|
||||
$this->assertEquals('Locale can translate Afrikaans', $override->get('translatable_default_with_translation'));
|
||||
|
||||
// Update test configuration.
|
||||
$override
|
||||
->set('translatable_no_default', 'This translation is preserved')
|
||||
->set('translatable_default_with_translation', 'This translation is preserved')
|
||||
->set('translatable_default_with_no_translation', 'This translation is preserved')
|
||||
->save();
|
||||
|
||||
// Install any module.
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm(['modules[dblog][enable]' => 'dblog'], 'Install');
|
||||
$this->assertSession()->pageTextContains('Module Database Logging has been installed.');
|
||||
|
||||
// Get the front page and ensure that the translated configuration still
|
||||
// appears.
|
||||
$this->drupalGet('af');
|
||||
$this->assertSession()->pageTextContains('Test site slogan in Afrikaans');
|
||||
|
||||
$this->rebuildContainer();
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'locale_test_translate.settings');
|
||||
$expected = [
|
||||
'translatable_no_default' => 'This translation is preserved',
|
||||
'translatable_default_with_translation' => 'This translation is preserved',
|
||||
'translatable_default_with_no_translation' => 'This translation is preserved',
|
||||
];
|
||||
$this->assertEquals($expected, $override->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests removing a string from Locale deletes configuration translations.
|
||||
*/
|
||||
public function testLocaleRemovalAndConfigOverrideDelete(): void {
|
||||
// Enable the locale module.
|
||||
$this->container->get('module_installer')->install(['locale']);
|
||||
$this->resetAll();
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'administer permissions',
|
||||
'translate interface',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
||||
->save();
|
||||
|
||||
// Add predefined language.
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm(['predefined_langcode' => 'af'], 'Add language');
|
||||
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'locale_test_translate.settings');
|
||||
$this->assertEquals(['translatable_default_with_translation' => 'Locale can translate Afrikaans'], $override->get());
|
||||
|
||||
// Remove the string from translation to simulate a Locale removal. Note
|
||||
// that is no current way of doing this in the UI.
|
||||
$locale_storage = \Drupal::service('locale.storage');
|
||||
$string = $locale_storage->findString(['source' => 'Locale can translate']);
|
||||
\Drupal::service('locale.storage')->delete($string);
|
||||
// Force a rebuild of config translations.
|
||||
$count = \Drupal::service('locale.config_manager')->updateConfigTranslations(['locale_test_translate.settings'], ['af']);
|
||||
$this->assertEquals(1, $count, 'Correct count of updated translations');
|
||||
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'locale_test_translate.settings');
|
||||
$this->assertEquals([], $override->get());
|
||||
$this->assertTrue($override->isNew(), 'The configuration override was deleted when the Locale string was deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests removing a string from Locale changes configuration translations.
|
||||
*/
|
||||
public function testLocaleRemovalAndConfigOverridePreserve(): void {
|
||||
// Enable the locale module.
|
||||
$this->container->get('module_installer')->install(['locale']);
|
||||
$this->resetAll();
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'administer permissions',
|
||||
'translate interface',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
||||
->save();
|
||||
|
||||
// Add predefined language.
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm(['predefined_langcode' => 'af'], 'Add language');
|
||||
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'locale_test_translate.settings');
|
||||
// Update test configuration.
|
||||
$override
|
||||
->set('translatable_no_default', 'This translation is preserved')
|
||||
->set('translatable_default_with_no_translation', 'This translation is preserved')
|
||||
->save();
|
||||
$expected = [
|
||||
'translatable_default_with_translation' => 'Locale can translate Afrikaans',
|
||||
'translatable_no_default' => 'This translation is preserved',
|
||||
'translatable_default_with_no_translation' => 'This translation is preserved',
|
||||
];
|
||||
$this->assertEquals($expected, $override->get());
|
||||
|
||||
// Set the translated string to empty.
|
||||
$search = [
|
||||
'string' => 'Locale can translate',
|
||||
'langcode' => 'af',
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => '',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('af', 'locale_test_translate.settings');
|
||||
$expected = [
|
||||
'translatable_no_default' => 'This translation is preserved',
|
||||
'translatable_default_with_no_translation' => 'This translation is preserved',
|
||||
];
|
||||
$this->assertEquals($expected, $override->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting a non-English language as default and importing configuration.
|
||||
*/
|
||||
public function testConfigTranslationWithNonEnglishLanguageDefault(): void {
|
||||
/** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */
|
||||
$module_installer = $this->container->get('module_installer');
|
||||
ConfigurableLanguage::createFromLangcode('af')->save();
|
||||
|
||||
$module_installer->install(['locale']);
|
||||
$this->resetAll();
|
||||
/** @var \Drupal\locale\StringStorageInterface $local_storage */
|
||||
$local_storage = $this->container->get('locale.storage');
|
||||
|
||||
$source_string = 'Locale can translate';
|
||||
$translation_string = 'Locale can translate Afrikaans';
|
||||
|
||||
// Create a translation for the "Locale can translate" string, this string
|
||||
// can be found in the "locale_test_translate" module's install config.
|
||||
$source = $local_storage->createString([
|
||||
'source' => $source_string,
|
||||
])->save();
|
||||
$local_storage->createTranslation([
|
||||
'lid' => $source->getId(),
|
||||
'language' => 'af',
|
||||
'translation' => $translation_string,
|
||||
])->save();
|
||||
|
||||
// Verify that we can find the newly added string translation, it is not a
|
||||
// customized translation.
|
||||
$translation = $local_storage->findTranslation([
|
||||
'source' => $source_string,
|
||||
'language' => 'af',
|
||||
]);
|
||||
$this->assertEquals($translation_string, $translation->getString());
|
||||
$this->assertFalse((bool) $translation->customized);
|
||||
|
||||
// Uninstall the "locale_test_translate" module, verify that we can still
|
||||
// find the string translation.
|
||||
$module_installer->uninstall(['locale_test_translate']);
|
||||
$this->resetAll();
|
||||
$translation = $local_storage->findTranslation([
|
||||
'source' => $source_string,
|
||||
'language' => 'af',
|
||||
]);
|
||||
$this->assertEquals($translation_string, $translation->getString());
|
||||
|
||||
// Set the default language to "Afrikaans" and re-enable the
|
||||
// "locale_test_translate" module.
|
||||
$this->config('system.site')->set('default_langcode', 'af')->save();
|
||||
$module_installer->install(['locale_test_translate']);
|
||||
$this->resetAll();
|
||||
|
||||
// Verify that enabling the "locale_test_translate" module didn't cause
|
||||
// the string translation to be overwritten.
|
||||
$translation = $local_storage->findTranslation([
|
||||
'source' => $source_string,
|
||||
'language' => 'af',
|
||||
]);
|
||||
$this->assertEquals($translation_string, $translation->getString());
|
||||
$this->assertFalse((bool) $translation->customized);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\locale\StringStorageInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* Tests translation of configuration strings.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigTranslationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* The language code used.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $langcode;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'contact', 'contact_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* @var \Drupal\locale\StringStorageInterface
|
||||
*/
|
||||
protected StringStorageInterface $storage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Add a default locale storage for all these tests.
|
||||
$this->storage = $this->container->get('locale.storage');
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
||||
->save();
|
||||
|
||||
// Add custom language.
|
||||
$this->langcode = 'xx';
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'translate interface',
|
||||
'administer modules',
|
||||
'access site-wide contact form',
|
||||
'administer contact forms',
|
||||
'administer site configuration',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$name = $this->randomMachineName(16);
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $this->langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
// Set path prefix.
|
||||
$edit = ["prefix[$this->langcode]" => $this->langcode];
|
||||
$this->drupalGet('admin/config/regional/language/detection/url');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic configuration translation.
|
||||
*/
|
||||
public function testConfigTranslation(): void {
|
||||
// Check that the maintenance message exists and create translation for it.
|
||||
$source = '@site is currently under maintenance. We should be back shortly. Thank you for your patience.';
|
||||
$string = $this->storage->findString(['source' => $source, 'context' => '', 'type' => 'configuration']);
|
||||
$this->assertNotEmpty($string, 'Configuration strings have been created upon installation.');
|
||||
|
||||
// Translate using the UI so configuration is refreshed.
|
||||
$message = $this->randomMachineName(20);
|
||||
$search = [
|
||||
'string' => $string->source,
|
||||
'langcode' => $this->langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $message,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Get translation and check we've only got the message.
|
||||
$translation = \Drupal::languageManager()->getLanguageConfigOverride($this->langcode, 'system.maintenance')->get();
|
||||
$this->assertCount(1, $translation, 'Got the right number of properties after translation.');
|
||||
$this->assertEquals($message, $translation['message']);
|
||||
|
||||
// Check default medium date format exists and create a translation for it.
|
||||
$string = $this->storage->findString(['source' => 'D, j M Y - H:i', 'context' => 'PHP date format', 'type' => 'configuration']);
|
||||
$this->assertNotEmpty($string, 'Configuration date formats have been created upon installation.');
|
||||
|
||||
// Translate using the UI so configuration is refreshed.
|
||||
$search = [
|
||||
'string' => $string->source,
|
||||
'langcode' => $this->langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => 'D',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
$translation = \Drupal::languageManager()->getLanguageConfigOverride($this->langcode, 'core.date_format.medium')->get();
|
||||
$this->assertEquals('D', $translation['pattern'], 'Got the right date format pattern after translation.');
|
||||
|
||||
// Formatting the date 8 / 27 / 1985 @ 13:37 EST with pattern D should
|
||||
// display "Tue".
|
||||
$formatted_date = $this->container->get('date.formatter')->format(494015820, $type = 'medium', NULL, 'America/New_York', $this->langcode);
|
||||
$this->assertEquals('Tue', $formatted_date, 'Got the right formatted date using the date format translation pattern.');
|
||||
|
||||
// Assert strings from image module config are not available.
|
||||
$string = $this->storage->findString(['source' => 'Medium (220×220)', 'context' => '', 'type' => 'configuration']);
|
||||
$this->assertNull($string, 'Configuration strings have been created upon installation.');
|
||||
|
||||
// Enable the image module.
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm(['modules[image][enable]' => "1"], 'Install');
|
||||
$this->rebuildContainer();
|
||||
|
||||
$string = $this->storage->findString(['source' => 'Medium (220×220)', 'context' => '', 'type' => 'configuration']);
|
||||
$this->assertNotEmpty($string, 'Configuration strings have been created upon installation.');
|
||||
$locations = $string->getLocations();
|
||||
// Check the configuration string has been created with the right location.
|
||||
$this->assertArrayHasKey('configuration', $locations);
|
||||
$this->assertArrayHasKey('image.style.medium', $locations['configuration']);
|
||||
|
||||
// Check the string is unique and has no translation yet.
|
||||
$translations = $this->storage->getTranslations(['language' => $this->langcode, 'type' => 'configuration', 'name' => 'image.style.medium']);
|
||||
$this->assertCount(1, $translations);
|
||||
$translation = reset($translations);
|
||||
$this->assertEquals($string->source, $translation->source);
|
||||
$this->assertEmpty($translation->translation);
|
||||
|
||||
// Translate using the UI so configuration is refreshed.
|
||||
$image_style_label = $this->randomMachineName(20);
|
||||
$search = [
|
||||
'string' => $string->source,
|
||||
'langcode' => $this->langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $image_style_label,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Check the right single translation has been created.
|
||||
$translations = $this->storage->getTranslations(['language' => $this->langcode, 'type' => 'configuration', 'name' => 'image.style.medium']);
|
||||
$translation = reset($translations);
|
||||
$this->assertCount(1, $translations, 'Got only one translation for image configuration.');
|
||||
$this->assertEquals($string->source, $translation->source);
|
||||
$this->assertEquals($image_style_label, $translation->translation);
|
||||
|
||||
// Try more complex configuration data.
|
||||
$translation = \Drupal::languageManager()->getLanguageConfigOverride($this->langcode, 'image.style.medium')->get();
|
||||
$this->assertEquals($image_style_label, $translation['label'], 'Got the right translation for image style name after translation');
|
||||
|
||||
// Uninstall the module.
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->submitForm(['uninstall[image]' => "image"], 'Uninstall');
|
||||
$this->submitForm([], 'Uninstall');
|
||||
|
||||
// Ensure that the translated configuration has been removed.
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('xx', 'image.style.medium');
|
||||
$this->assertTrue($override->isNew(), 'Translated configuration for image module removed.');
|
||||
|
||||
// Translate default category using the UI so configuration is refreshed.
|
||||
$category_label = $this->randomMachineName(20);
|
||||
$search = [
|
||||
'string' => 'Website feedback',
|
||||
'langcode' => $this->langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $category_label,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Check if this category displayed in this language will use the
|
||||
// translation. This test ensures the entity loaded from the request
|
||||
// upcasting will already work.
|
||||
$this->drupalGet($this->langcode . '/contact/feedback');
|
||||
$this->assertSession()->pageTextContains($category_label);
|
||||
|
||||
// Check if the UI does not show the translated String.
|
||||
$this->drupalGet('admin/structure/contact/manage/feedback');
|
||||
$this->assertSession()->fieldValueEquals('edit-label', 'Website feedback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translatability of optional configuration in locale.
|
||||
*/
|
||||
public function testOptionalConfiguration(): void {
|
||||
$this->assertNodeConfig(FALSE, FALSE);
|
||||
// Enable the node module.
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm(['modules[node][enable]' => "1"], 'Install');
|
||||
$this->submitForm([], 'Continue');
|
||||
$this->rebuildContainer();
|
||||
$this->assertNodeConfig(TRUE, FALSE);
|
||||
// Enable the views module (which node provides some optional config for).
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm(['modules[views][enable]' => "1"], 'Install');
|
||||
$this->rebuildContainer();
|
||||
$this->assertNodeConfig(TRUE, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that node configuration source strings are made available in locale.
|
||||
*
|
||||
* @param bool $required
|
||||
* Whether to assume a sample of the required default configuration is
|
||||
* present.
|
||||
* @param bool $optional
|
||||
* Whether to assume a sample of the optional default configuration is
|
||||
* present.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertNodeConfig(bool $required, bool $optional): void {
|
||||
// Check the required default configuration in node module.
|
||||
$string = $this->storage->findString(['source' => 'Make content sticky', 'context' => '', 'type' => 'configuration']);
|
||||
if ($required) {
|
||||
$this->assertFalse($this->config('system.action.node_make_sticky_action')->isNew());
|
||||
$this->assertNotEmpty($string, 'Node action text can be found with node module.');
|
||||
}
|
||||
else {
|
||||
$this->assertTrue($this->config('system.action.node_make_sticky_action')->isNew());
|
||||
$this->assertNull($string, 'Node action text can not be found without node module.');
|
||||
}
|
||||
|
||||
// Check the optional default configuration in node module.
|
||||
$string = $this->storage->findString(['source' => 'No front page content has been created yet.<br/>Follow the <a target="_blank" href="https://www.drupal.org/docs/user_guide/en/index.html">User Guide</a> to start building your site.', 'context' => '', 'type' => 'configuration']);
|
||||
if ($optional) {
|
||||
$this->assertFalse($this->config('views.view.frontpage')->isNew());
|
||||
$this->assertNotEmpty($string, 'Node view text can be found with node and views modules.');
|
||||
}
|
||||
else {
|
||||
$this->assertTrue($this->config('views.view.frontpage')->isNew());
|
||||
$this->assertNull($string, 'Node view text can not be found without node and/or views modules.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
|
||||
/**
|
||||
* Tests multilingual support for content types and individual nodes.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleContentTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'locale'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Verifies that machine name fields are always LTR.
|
||||
*/
|
||||
public function testMachineNameLTR(): void {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'administer content types',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
]);
|
||||
|
||||
// Log in as admin.
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Verify that the machine name field is LTR for a new content type.
|
||||
$this->drupalGet('admin/structure/types/add');
|
||||
$type = $this->assertSession()->fieldExists('type');
|
||||
$this->assertSame('ltr', $type->getAttribute('dir'));
|
||||
|
||||
// Install the Arabic language (which is RTL) and configure as the default.
|
||||
$edit = [];
|
||||
$edit['predefined_langcode'] = 'ar';
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add language');
|
||||
|
||||
$edit = [
|
||||
'site_default_language' => 'ar',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Verify that the machine name field is still LTR for a new content type.
|
||||
$this->drupalGet('admin/structure/types/add');
|
||||
$type = $this->assertSession()->fieldExists('type');
|
||||
$this->assertSame('ltr', $type->getAttribute('dir'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a content type can be set to multilingual and language is present.
|
||||
*/
|
||||
public function testContentTypeLanguageConfiguration(): void {
|
||||
$type1 = $this->drupalCreateContentType();
|
||||
$type2 = $this->drupalCreateContentType();
|
||||
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'administer content types',
|
||||
'access administration pages',
|
||||
]);
|
||||
// User to create a node.
|
||||
$web_user = $this->drupalCreateUser([
|
||||
"create {$type1->id()} content",
|
||||
"create {$type2->id()} content",
|
||||
"edit any {$type2->id()} content",
|
||||
]);
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language.
|
||||
$name = $this->randomMachineName(16);
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
|
||||
// Set the content type to use multilingual support.
|
||||
$this->drupalGet("admin/structure/types/manage/{$type2->id()}");
|
||||
$this->assertSession()->pageTextContains('Language settings');
|
||||
$edit = [
|
||||
'language_configuration[language_alterable]' => TRUE,
|
||||
];
|
||||
$this->drupalGet("admin/structure/types/manage/{$type2->id()}");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The content type {$type2->label()} has been updated.");
|
||||
$this->drupalLogout();
|
||||
\Drupal::languageManager()->reset();
|
||||
|
||||
// Verify language selection is not present on the node add form.
|
||||
$this->drupalLogin($web_user);
|
||||
$this->drupalGet("node/add/{$type1->id()}");
|
||||
// Verify language select list is not present.
|
||||
$this->assertSession()->fieldNotExists('langcode[0][value]');
|
||||
|
||||
// Verify language selection appears on the node add form.
|
||||
$this->drupalGet("node/add/{$type2->id()}");
|
||||
// Verify language select list is present.
|
||||
$this->assertSession()->fieldExists('langcode[0][value]');
|
||||
// Ensure language appears.
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
|
||||
// Create a node.
|
||||
$node_title = $this->randomMachineName();
|
||||
$node_body = $this->randomMachineName();
|
||||
$edit = [
|
||||
'type' => $type2->id(),
|
||||
'title' => $node_title,
|
||||
'body' => [['value' => $node_body]],
|
||||
'langcode' => $langcode,
|
||||
];
|
||||
$node = $this->drupalCreateNode($edit);
|
||||
// Edit the content and ensure correct language is selected.
|
||||
$path = 'node/' . $node->id() . '/edit';
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->responseContains('<option value="' . $langcode . '" selected="selected">' . $name . '</option>');
|
||||
// Ensure we can change the node language.
|
||||
$edit = [
|
||||
'langcode[0][value]' => 'en',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains($node_title . ' has been updated.');
|
||||
|
||||
// Verify that the creation message contains a link to a node.
|
||||
$xpath = $this->assertSession()->buildXPathQuery('//div[@data-drupal-messages]//a[contains(@href, :href)]', [
|
||||
':href' => 'node/' . $node->id(),
|
||||
]);
|
||||
$this->assertSession()->elementExists('xpath', $xpath);
|
||||
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a dir and lang tags exist in node's attributes.
|
||||
*/
|
||||
public function testContentTypeDirLang(): void {
|
||||
$type = $this->drupalCreateContentType();
|
||||
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'administer content types',
|
||||
'access administration pages',
|
||||
]);
|
||||
// User to create a node.
|
||||
$web_user = $this->drupalCreateUser([
|
||||
"create {$type->id()} content",
|
||||
"edit own {$type->id()} content",
|
||||
]);
|
||||
|
||||
// Log in as admin.
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Install Arabic language.
|
||||
$edit = [];
|
||||
$edit['predefined_langcode'] = 'ar';
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add language');
|
||||
|
||||
// Install Spanish language.
|
||||
$edit = [];
|
||||
$edit['predefined_langcode'] = 'es';
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add language');
|
||||
\Drupal::languageManager()->reset();
|
||||
|
||||
// Set the content type to use multilingual support.
|
||||
$this->drupalGet("admin/structure/types/manage/{$type->id()}");
|
||||
$edit = [
|
||||
'language_configuration[language_alterable]' => TRUE,
|
||||
];
|
||||
$this->drupalGet("admin/structure/types/manage/{$type->id()}");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("The content type {$type->label()} has been updated.");
|
||||
$this->drupalLogout();
|
||||
|
||||
// Log in as web user to add new node.
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Create three nodes: English, Arabic and Spanish.
|
||||
$nodes = [];
|
||||
foreach (['en', 'es', 'ar'] as $langcode) {
|
||||
$nodes[$langcode] = $this->drupalCreateNode([
|
||||
'langcode' => $langcode,
|
||||
'type' => $type->id(),
|
||||
'promote' => NodeInterface::PROMOTED,
|
||||
]);
|
||||
}
|
||||
|
||||
// Check if English node does not have lang tag.
|
||||
$this->drupalGet('node/' . $nodes['en']->id());
|
||||
$element = $this->cssSelect('article[lang="en"]');
|
||||
$this->assertEmpty($element, 'The lang tag has not been assigned to the English node.');
|
||||
|
||||
// Check if English node does not have dir tag.
|
||||
$element = $this->cssSelect('article[dir="ltr"]');
|
||||
$this->assertEmpty($element, 'The dir tag has not been assigned to the English node.');
|
||||
|
||||
// Check if Arabic node has lang="ar" & dir="rtl" tags.
|
||||
$this->drupalGet('node/' . $nodes['ar']->id());
|
||||
$element = $this->cssSelect('article[lang="ar"][dir="rtl"]');
|
||||
$this->assertNotEmpty($element, 'The lang and dir tags have been assigned correctly to the Arabic node.');
|
||||
|
||||
// Check if Spanish node has lang="es" tag.
|
||||
$this->drupalGet('node/' . $nodes['es']->id());
|
||||
$element = $this->cssSelect('article[lang="es"]');
|
||||
$this->assertNotEmpty($element, 'The lang tag has been assigned correctly to the Spanish node.');
|
||||
|
||||
// Check if Spanish node does not have dir="ltr" tag.
|
||||
$element = $this->cssSelect('article[lang="es"][dir="ltr"]');
|
||||
$this->assertEmpty($element, 'The dir tag has not been assigned to the Spanish node.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
// cspell:ignore janvier lundi
|
||||
|
||||
/**
|
||||
* Tests the exportation of locale files.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleExportTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user able to create languages and export translations.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User|false
|
||||
*/
|
||||
protected $adminUser = NULL;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'translate interface',
|
||||
'access administration pages',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Copy test po files to the translations directory.
|
||||
\Drupal::service('file_system')->copy(__DIR__ . '/../../../tests/test.de.po', 'translations://', FileExists::Replace);
|
||||
\Drupal::service('file_system')->copy(__DIR__ . '/../../../tests/test.xx.po', 'translations://', FileExists::Replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests exportation of translations.
|
||||
*/
|
||||
public function testExportTranslation(): void {
|
||||
$file_system = \Drupal::service('file_system');
|
||||
// First import some known translations.
|
||||
// This will also automatically add the 'fr' language.
|
||||
$name = $file_system->tempnam('temporary://', "po_") . '.po';
|
||||
file_put_contents($name, $this->getPoFile());
|
||||
$this->drupalGet('admin/config/regional/translate/import');
|
||||
$this->submitForm([
|
||||
'langcode' => 'fr',
|
||||
'files[file]' => $name,
|
||||
], 'Import');
|
||||
$file_system->unlink($name);
|
||||
|
||||
// Get the French translations.
|
||||
$this->drupalGet('admin/config/regional/translate/export');
|
||||
$this->submitForm(['langcode' => 'fr'], 'Export');
|
||||
|
||||
// Ensure we have a translation file.
|
||||
$this->assertSession()->pageTextContains('# French translation of Drupal');
|
||||
// Ensure our imported translations exist in the file.
|
||||
$this->assertSession()->pageTextContains('msgstr "lundi"');
|
||||
|
||||
// Import some more French translations which will be marked as customized.
|
||||
$name = $file_system->tempnam('temporary://', "po2_") . '.po';
|
||||
file_put_contents($name, $this->getCustomPoFile());
|
||||
$this->drupalGet('admin/config/regional/translate/import');
|
||||
$this->submitForm([
|
||||
'langcode' => 'fr',
|
||||
'files[file]' => $name,
|
||||
'customized' => 1,
|
||||
], 'Import');
|
||||
$file_system->unlink($name);
|
||||
|
||||
// Create string without translation in the locales_source table.
|
||||
$this->container
|
||||
->get('locale.storage')
|
||||
->createString()
|
||||
->setString('February')
|
||||
->save();
|
||||
|
||||
// Export only customized French translations.
|
||||
$this->drupalGet('admin/config/regional/translate/export');
|
||||
$this->submitForm([
|
||||
'langcode' => 'fr',
|
||||
'content_options[not_customized]' => FALSE,
|
||||
'content_options[customized]' => TRUE,
|
||||
'content_options[not_translated]' => FALSE,
|
||||
], 'Export');
|
||||
|
||||
// Ensure we have a translation file.
|
||||
$this->assertSession()->pageTextContains('# French translation of Drupal');
|
||||
// Ensure the customized translations exist in the file.
|
||||
$this->assertSession()->pageTextContains('msgstr "janvier"');
|
||||
// Ensure no untranslated strings exist in the file.
|
||||
$this->assertSession()->responseNotContains('msgid "February"');
|
||||
|
||||
// Export only untranslated French translations.
|
||||
$this->drupalGet('admin/config/regional/translate/export');
|
||||
$this->submitForm([
|
||||
'langcode' => 'fr',
|
||||
'content_options[not_customized]' => FALSE,
|
||||
'content_options[customized]' => FALSE,
|
||||
'content_options[not_translated]' => TRUE,
|
||||
], 'Export');
|
||||
|
||||
// Ensure we have a translation file.
|
||||
$this->assertSession()->pageTextContains('# French translation of Drupal');
|
||||
// Ensure no customized translations exist in the file.
|
||||
$this->assertSession()->responseNotContains('msgstr "janvier"');
|
||||
// Ensure the untranslated strings exist in the file, and with right quotes.
|
||||
$this->assertSession()->responseContains($this->getUntranslatedString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests exportation of translation template file.
|
||||
*/
|
||||
public function testExportTranslationTemplateFile(): void {
|
||||
// Load an admin page with JavaScript so _drupal_add_library() fires at
|
||||
// least once and _locale_parse_js_file() gets to run at least once so that
|
||||
// the locales_source table gets populated with something.
|
||||
$this->drupalGet('admin/config/regional/language');
|
||||
// Get the translation template file.
|
||||
$this->drupalGet('admin/config/regional/translate/export');
|
||||
$this->submitForm([], 'Export');
|
||||
// Ensure we have a translation file.
|
||||
$this->assertSession()->pageTextContains('# LANGUAGE translation of PROJECT');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a proper .po file.
|
||||
*/
|
||||
public function getPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file that will be marked as customized.
|
||||
*/
|
||||
public function getCustomPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "January"
|
||||
msgstr "janvier"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file fragment with an untranslated string.
|
||||
*
|
||||
* @return string
|
||||
* A .po file fragment with an untranslated string.
|
||||
*/
|
||||
public function getUntranslatedString() {
|
||||
return <<< EOF
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
EOF;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the locale functionality in the altered file settings form.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleFileSystemFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$account = $this->drupalCreateUser(['administer site configuration']);
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation directory settings on the file settings form.
|
||||
*/
|
||||
public function testFileConfigurationPage(): void {
|
||||
// By default there should be no setting for the translation directory.
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->assertSession()->fieldNotExists('translation_path');
|
||||
|
||||
// With locale module installed, the setting should appear.
|
||||
$module_installer = $this->container->get('module_installer');
|
||||
$module_installer->install(['locale']);
|
||||
$this->rebuildContainer();
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->assertSession()->fieldExists('translation_path');
|
||||
|
||||
// The setting should persist.
|
||||
$translation_path = $this->publicFilesDirectory . '/translations_changed';
|
||||
$fields = [
|
||||
'translation_path' => $translation_path,
|
||||
];
|
||||
$this->submitForm($fields, 'Save configuration');
|
||||
$this->drupalGet('admin/config/media/file-system');
|
||||
$this->assertSession()->fieldValueEquals('translation_path', $translation_path);
|
||||
$this->assertEquals($this->config('locale.settings')->get('translation.path'), $translation_path);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,725 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\File\FileExists;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
// cspell:ignore chien chiens deutsch januari lundi montag moutons műveletek
|
||||
// cspell:ignore svibanj svib räme
|
||||
|
||||
/**
|
||||
* Tests the import of locale files.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleImportFunctionalTest extends BrowserTestBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'dblog'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user able to create languages and import translations.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A user able to create languages, import translations, access site reports.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $adminUserAccessSiteReports;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Copy test po files to the translations directory.
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$file_system->copy(__DIR__ . '/../../../tests/test.de.po', 'translations://', FileExists::Replace);
|
||||
$file_system->copy(__DIR__ . '/../../../tests/test.xx.po', 'translations://', FileExists::Replace);
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'translate interface',
|
||||
'access administration pages',
|
||||
]);
|
||||
$this->adminUserAccessSiteReports = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'translate interface',
|
||||
'access administration pages',
|
||||
'access site reports',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests import of standalone .po files.
|
||||
*/
|
||||
public function testStandalonePoFile(): void {
|
||||
// Try importing a .po file.
|
||||
$this->importPoFile($this->getPoFile(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
$this->config('locale.settings');
|
||||
// The import should automatically create the corresponding language.
|
||||
$this->assertSession()->pageTextContains("The language French has been created.");
|
||||
|
||||
// The import should have created 8 strings.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 8 translations were added, 0 translations were updated and 0 translations were removed.");
|
||||
|
||||
// This import should have saved plural forms to have 2 variants.
|
||||
$locale_plurals = \Drupal::service('locale.plural.formula')->getNumberOfPlurals('fr');
|
||||
$this->assertEquals(2, $locale_plurals, 'Plural number initialized.');
|
||||
|
||||
// Ensure we were redirected correctly.
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('locale.translate_page'));
|
||||
|
||||
// Try importing a .po file with invalid tags.
|
||||
$this->importPoFile($this->getBadPoFile(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
|
||||
// The import should have created 1 string and rejected 2.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 1 translations were added, 0 translations were updated and 0 translations were removed.");
|
||||
$this->assertSession()->pageTextContains("2 translation strings were skipped because of disallowed or malformed HTML. See the log for details.");
|
||||
|
||||
// Repeat the process with a user that can access site reports, and this
|
||||
// time the different warnings must contain links to the log.
|
||||
$this->drupalLogin($this->adminUserAccessSiteReports);
|
||||
|
||||
// Try importing a .po file with invalid tags.
|
||||
$this->importPoFile($this->getBadPoFile(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
|
||||
$this->assertSession()->pageTextContains("2 translation strings were skipped because of disallowed or malformed HTML. See the log for details.");
|
||||
|
||||
// Check empty files import with a user that cannot access site reports..
|
||||
$this->drupalLogin($this->adminUser);
|
||||
// Try importing a zero byte sized .po file.
|
||||
$this->importPoFile($this->getEmptyPoFile(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
// The import should have created 0 string and rejected 0.
|
||||
$this->assertSession()->pageTextContains("One translation file could not be imported. See the log for details.");
|
||||
|
||||
// Repeat the process with a user that can access site reports, and this
|
||||
// time the different warnings must contain links to the log.
|
||||
$this->drupalLogin($this->adminUserAccessSiteReports);
|
||||
// Try importing a zero byte sized .po file.
|
||||
$this->importPoFile($this->getEmptyPoFile(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
// The import should have created 0 string and rejected 0.
|
||||
$this->assertSession()->pageTextContains("One translation file could not be imported. See the log for details.");
|
||||
|
||||
// Try importing a .po file which doesn't exist.
|
||||
$name = $this->randomMachineName(16);
|
||||
$this->drupalGet('admin/config/regional/translate/import');
|
||||
$this->submitForm([
|
||||
'langcode' => 'fr',
|
||||
'files[file]' => $name,
|
||||
], 'Import');
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('locale.translate_import'));
|
||||
$this->assertSession()->pageTextContains('File to import not found.');
|
||||
|
||||
// Try importing a .po file with overriding strings, and ensure existing
|
||||
// strings are kept.
|
||||
$this->importPoFile($this->getOverwritePoFile(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
|
||||
// The import should have created 1 string.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 1 translations were added, 0 translations were updated and 0 translations were removed.");
|
||||
// Ensure string wasn't overwritten.
|
||||
$search = [
|
||||
'string' => 'Montag',
|
||||
'langcode' => 'fr',
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('No strings available.');
|
||||
|
||||
// This import should not have changed number of plural forms.
|
||||
$locale_plurals = \Drupal::service('locale.plural.formula')->getNumberOfPlurals('fr');
|
||||
$this->assertEquals(2, $locale_plurals, 'Plural numbers untouched.');
|
||||
|
||||
// Try importing a .po file with overriding strings, and ensure existing
|
||||
// strings are overwritten.
|
||||
$this->importPoFile($this->getOverwritePoFile(), [
|
||||
'langcode' => 'fr',
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
]);
|
||||
|
||||
// The import should have updated 2 strings.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 0 translations were added, 2 translations were updated and 0 translations were removed.");
|
||||
// Ensure string was overwritten.
|
||||
$search = [
|
||||
'string' => 'Montag',
|
||||
'langcode' => 'fr',
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextNotContains('No strings available.');
|
||||
// This import should have changed number of plural forms.
|
||||
$locale_plurals = \Drupal::service('locale.plural.formula')->reset()->getNumberOfPlurals('fr');
|
||||
$this->assertEquals(3, $locale_plurals, 'Plural numbers changed.');
|
||||
|
||||
// Importing a .po file and mark its strings as customized strings.
|
||||
$this->importPoFile($this->getCustomPoFile(), [
|
||||
'langcode' => 'fr',
|
||||
'customized' => TRUE,
|
||||
]);
|
||||
|
||||
// The import should have created 6 strings.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 6 translations were added, 0 translations were updated and 0 translations were removed.");
|
||||
|
||||
// The database should now contain 6 customized strings (two imported
|
||||
// strings are not translated).
|
||||
$count = Database::getConnection()->select('locales_target')
|
||||
->condition('customized', 1)
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEquals(6, $count, 'Customized translations successfully imported.');
|
||||
|
||||
// Try importing a .po file with overriding strings, and ensure existing
|
||||
// customized strings are kept.
|
||||
$this->importPoFile($this->getCustomOverwritePoFile(), [
|
||||
'langcode' => 'fr',
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
'overwrite_options[customized]' => FALSE,
|
||||
]);
|
||||
|
||||
// The import should have created 1 string.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 1 translations were added, 0 translations were updated and 0 translations were removed.");
|
||||
// Ensure string wasn't overwritten.
|
||||
$search = [
|
||||
'string' => 'januari',
|
||||
'langcode' => 'fr',
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('No strings available.');
|
||||
|
||||
// Try importing a .po file with overriding strings, and ensure existing
|
||||
// customized strings are overwritten.
|
||||
$this->importPoFile($this->getCustomOverwritePoFile(), [
|
||||
'langcode' => 'fr',
|
||||
'overwrite_options[not_customized]' => FALSE,
|
||||
'overwrite_options[customized]' => TRUE,
|
||||
]);
|
||||
|
||||
// The import should have updated 2 strings.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 0 translations were added, 2 translations were updated and 0 translations were removed.");
|
||||
// Ensure string was overwritten.
|
||||
$search = [
|
||||
'string' => 'januari',
|
||||
'langcode' => 'fr',
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextNotContains('No strings available.');
|
||||
|
||||
// Try importing a .po file with invalid encoding.
|
||||
$this->importPoFile($this->getInvalidEncodedPoFile(), [], ['Windows-1252']);
|
||||
$this->assertSession()->pageTextContains('The file is encoded with ASCII. It must be encoded with UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests msgctxt context support.
|
||||
*/
|
||||
public function testLanguageContext(): void {
|
||||
// Try importing a .po file.
|
||||
$this->importPoFile($this->getPoFileWithContext(), [
|
||||
'langcode' => 'hr',
|
||||
]);
|
||||
|
||||
// We cast the return value of t() to string so as to retrieve the
|
||||
// translated value, rendered as a string.
|
||||
$this->assertSame('Svibanj', (string) $this->t('May', [], ['langcode' => 'hr', 'context' => 'Long month name']), 'Long month name context is working.');
|
||||
$this->assertSame('Svib.', (string) $this->t('May', [], ['langcode' => 'hr', 'context' => 'Abbreviated month name']), 'Abbreviated month name context is working.');
|
||||
$this->assertSame('Svi.', (string) $this->t('May', [], ['langcode' => 'hr']), 'Default context is working.');
|
||||
$this->assertSame('sv', (string) $this->t('st', [], ['langcode' => 'hr']), 'Default context for "saint" is working.');
|
||||
$this->assertSame('.', (string) $this->t('st', [], ['langcode' => 'hr', 'context' => 'Day ordinal suffix']), 'Day ordinal suffix context is working.');
|
||||
|
||||
// Ensure that the date formatter applies the right translation context.
|
||||
$formatted_date = $this->container->get('date.formatter')->format(483820620, 'custom', 'jS F Y', 'America/New_York', 'hr');
|
||||
$this->assertEquals('1. Svibanj 1985', $formatted_date, 'Got the right formatted date using the date format translation pattern.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests empty msgstr at end of .po file see #611786.
|
||||
*/
|
||||
public function testEmptyMsgstr(): void {
|
||||
$langcode = 'hu';
|
||||
|
||||
// Try importing a .po file.
|
||||
$this->importPoFile($this->getPoFileWithMsgstr(), [
|
||||
'langcode' => $langcode,
|
||||
]);
|
||||
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 1 translations were added, 0 translations were updated and 0 translations were removed.");
|
||||
$this->assertSame('Műveletek', (string) $this->t('Operations', [], ['langcode' => $langcode]), 'String imported and translated.');
|
||||
|
||||
// Try importing a .po file.
|
||||
$this->importPoFile($this->getPoFileWithEmptyMsgstr(), [
|
||||
'langcode' => $langcode,
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
]);
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 0 translations were added, 0 translations were updated and 1 translations were removed.");
|
||||
|
||||
$str = "Operations";
|
||||
$search = [
|
||||
'string' => $str,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
// Check that search finds the string as untranslated.
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests .po file import with configuration translation.
|
||||
*/
|
||||
public function testConfigPoFile(): void {
|
||||
// Values for translations to assert. Config key, original string,
|
||||
// translation and config property name.
|
||||
$config_strings = [
|
||||
'system.maintenance' => [
|
||||
'@site is currently under maintenance. We should be back shortly. Thank you for your patience.',
|
||||
// cSpell:disable-next-line
|
||||
'@site karbantartás alatt áll. Rövidesen visszatérünk. Köszönjük a türelmet.',
|
||||
'message',
|
||||
],
|
||||
'user.role.anonymous' => [
|
||||
'Anonymous user',
|
||||
// cSpell:disable-next-line
|
||||
'Névtelen felhasználó',
|
||||
'label',
|
||||
],
|
||||
];
|
||||
|
||||
// Add custom language for testing.
|
||||
$langcode = 'xx';
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $this->randomMachineName(16),
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
|
||||
// Check for the source strings we are going to translate. Adding the
|
||||
// custom language should have made the process to export configuration
|
||||
// strings to interface translation executed.
|
||||
$locale_storage = $this->container->get('locale.storage');
|
||||
foreach ($config_strings as $config_string) {
|
||||
$string = $locale_storage->findString(['source' => $config_string[0], 'context' => '', 'type' => 'configuration']);
|
||||
$this->assertNotEmpty($string, 'Configuration strings have been created upon installation.');
|
||||
}
|
||||
|
||||
// Import a .po file to translate.
|
||||
$this->importPoFile($this->getPoFileWithConfig(), [
|
||||
'langcode' => $langcode,
|
||||
]);
|
||||
|
||||
// Translations got recorded in the interface translation system.
|
||||
foreach ($config_strings as $config_string) {
|
||||
$search = [
|
||||
'string' => $config_string[0],
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains($config_string[1]);
|
||||
}
|
||||
|
||||
// Test that translations got recorded in the config system.
|
||||
$overrides = \Drupal::service('language.config_factory_override');
|
||||
foreach ($config_strings as $config_key => $config_string) {
|
||||
$override = $overrides->getOverride($langcode, $config_key);
|
||||
$this->assertEquals($override->get($config_string[2]), $config_string[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests .po file import with user.settings configuration.
|
||||
*/
|
||||
public function testConfigTranslationImportingPoFile(): void {
|
||||
// Set the language code.
|
||||
$langcode = 'de';
|
||||
|
||||
// Import a .po file to translate.
|
||||
$this->importPoFile($this->getPoFileWithConfigDe(), [
|
||||
'langcode' => $langcode,
|
||||
]);
|
||||
|
||||
// Check that the 'Anonymous' string is translated.
|
||||
$config = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'user.settings');
|
||||
$this->assertEquals('Anonymous German', $config->get('anonymous'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the translation are imported when a new language is created.
|
||||
*/
|
||||
public function testCreatedLanguageTranslation(): void {
|
||||
// Import a .po file to add de language.
|
||||
$this->importPoFile($this->getPoFileWithConfigDe(), ['langcode' => 'de']);
|
||||
|
||||
// Get the language.entity.de label and check it's been translated.
|
||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('de', 'language.entity.de');
|
||||
$this->assertEquals('Deutsch', $override->get('label'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function: import a standalone .po file in a given language.
|
||||
*
|
||||
* @param string $contents
|
||||
* Contents of the .po file to import.
|
||||
* @param array $options
|
||||
* (optional) Additional options to pass to the translation import form.
|
||||
* @param array $encodings
|
||||
* (optional) The encoding of the file.
|
||||
*/
|
||||
public function importPoFile($contents, array $options = [], array $encodings = []): void {
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$name = $file_system->tempnam('temporary://', "po_") . '.po';
|
||||
foreach ($encodings as $encoding) {
|
||||
$contents = mb_convert_encoding($contents, $encoding);
|
||||
}
|
||||
file_put_contents($name, $contents);
|
||||
$options['files[file]'] = $name;
|
||||
$this->drupalGet('admin/config/regional/translate/import');
|
||||
$this->submitForm($options, 'Import');
|
||||
$file_system->unlink($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a proper .po file.
|
||||
*/
|
||||
public function getPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "One sheep"
|
||||
msgid_plural "@count sheep"
|
||||
msgstr[0] "un mouton"
|
||||
msgstr[1] "@count moutons"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
|
||||
msgid "Tuesday"
|
||||
msgstr "mardi"
|
||||
|
||||
msgid "Wednesday"
|
||||
msgstr "mercredi"
|
||||
|
||||
msgid "Thursday"
|
||||
msgstr "jeudi"
|
||||
|
||||
msgid "Friday"
|
||||
msgstr "vendredi"
|
||||
|
||||
msgid "Saturday"
|
||||
msgstr "samedi"
|
||||
|
||||
msgid "Sunday"
|
||||
msgstr "dimanche"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns an empty .po file.
|
||||
*/
|
||||
public function getEmptyPoFile() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a bad .po file.
|
||||
*/
|
||||
public function getBadPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Save configuration"
|
||||
msgstr "Enregistrer la configuration"
|
||||
|
||||
msgid "edit"
|
||||
msgstr "modifier<img SRC="javascript:alert(\'xss\');">"
|
||||
|
||||
msgid "delete"
|
||||
msgstr "supprimer<script>alert('xss');</script>"
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a proper .po file for testing.
|
||||
*/
|
||||
public function getOverwritePoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "Montag"
|
||||
|
||||
msgid "Day"
|
||||
msgstr "Jour"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file that will be marked as customized.
|
||||
*/
|
||||
public function getCustomPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "One dog"
|
||||
msgid_plural "@count dogs"
|
||||
msgstr[0] "un chien"
|
||||
msgstr[1] "@count chiens"
|
||||
|
||||
msgid "January"
|
||||
msgstr "janvier"
|
||||
|
||||
msgid "February"
|
||||
msgstr "février"
|
||||
|
||||
msgid "March"
|
||||
msgstr "mars"
|
||||
|
||||
msgid "April"
|
||||
msgstr "avril"
|
||||
|
||||
msgid "June"
|
||||
msgstr "juin"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file for testing customized strings.
|
||||
*/
|
||||
public function getCustomOverwritePoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "January"
|
||||
msgstr "januari"
|
||||
|
||||
msgid "February"
|
||||
msgstr "februari"
|
||||
|
||||
msgid "July"
|
||||
msgstr "juillet"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with context.
|
||||
*/
|
||||
public function getPoFileWithContext() {
|
||||
// Croatian (code hr) is one of the languages that have a different
|
||||
// form for the full name and the abbreviated name for the month of May.
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
|
||||
|
||||
msgctxt "Long month name"
|
||||
msgid "May"
|
||||
msgstr "Svibanj"
|
||||
|
||||
msgctxt "Abbreviated month name"
|
||||
msgid "May"
|
||||
msgstr "Svib."
|
||||
|
||||
msgid "May"
|
||||
msgstr "Svi."
|
||||
|
||||
msgctxt "Day ordinal suffix"
|
||||
msgid "st"
|
||||
msgstr "."
|
||||
|
||||
msgid "st"
|
||||
msgstr "sv"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with an empty last item.
|
||||
*/
|
||||
public function getPoFileWithEmptyMsgstr() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Operations"
|
||||
msgstr ""
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with an empty last item.
|
||||
*/
|
||||
public function getPoFileWithMsgstr() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Operations"
|
||||
msgstr "Műveletek"
|
||||
|
||||
msgid "Will not appear in Drupal core, so we can ensure the test passes"
|
||||
msgstr ""
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with configuration translations.
|
||||
*/
|
||||
public function getPoFileWithConfig() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "@site is currently under maintenance. We should be back shortly. Thank you for your patience."
|
||||
msgstr "@site karbantartás alatt áll. Rövidesen visszatérünk. Köszönjük a türelmet."
|
||||
|
||||
msgid "Anonymous user"
|
||||
msgstr "Névtelen felhasználó"
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with configuration translations.
|
||||
*/
|
||||
public function getPoFileWithConfigDe() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Anonymous"
|
||||
msgstr "Anonymous German"
|
||||
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns a .po file with invalid encoding.
|
||||
*/
|
||||
public function getInvalidEncodedPoFile() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=Windows-1252\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "Swamp"
|
||||
msgstr "Räme"
|
||||
EOF;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test installation of Locale module.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleInstallTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'file',
|
||||
'language',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests Locale install message.
|
||||
*/
|
||||
public function testLocaleInstallMessage(): void {
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer modules',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$edit = [];
|
||||
$edit['modules[locale][enable]'] = 'locale';
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm($edit, 'Install');
|
||||
|
||||
$this->assertSession()->statusMessageContains('available translations', 'status');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Component\Gettext\PoItem;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
// cspell:ignore descripcion mostrar
|
||||
|
||||
/**
|
||||
* Tests parsing js files for translatable strings.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleJavascriptTranslationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'locale_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* @covers ::\_locale_parse_js_file
|
||||
*/
|
||||
public function testFileParsing(): void {
|
||||
|
||||
// This test is for ensuring that the regular expression in
|
||||
// _locale_parse_js_file() finds translatable source strings in all valid
|
||||
// JavaScript syntax regardless of the coding style used, especially with
|
||||
// respect to optional whitespace, line breaks, etc.
|
||||
// - We test locale_test.js, because that is the one that contains a
|
||||
// variety of whitespace styles.
|
||||
$files[] = __DIR__ . '/../../js/locale_test.js';
|
||||
|
||||
foreach ($files as $filename) {
|
||||
// Parse the file to look for source strings.
|
||||
_locale_parse_js_file($filename);
|
||||
|
||||
// Get all of the source strings that were found.
|
||||
$strings = $this->container
|
||||
->get('locale.storage')
|
||||
->getStrings([
|
||||
'type' => 'javascript',
|
||||
'name' => $filename,
|
||||
]);
|
||||
|
||||
$source_strings = [];
|
||||
foreach ($strings as $string) {
|
||||
$source_strings[$string->source] = $string->context;
|
||||
}
|
||||
|
||||
$etx = PoItem::DELIMITER;
|
||||
// List of all strings that should be in the file.
|
||||
$test_strings = [
|
||||
'Standard Call t' => '',
|
||||
'Whitespace Call t' => '',
|
||||
|
||||
'Single Quote t' => '',
|
||||
"Single Quote \\'Escaped\\' t" => '',
|
||||
'Single Quote Concat strings t' => '',
|
||||
|
||||
'Double Quote t' => '',
|
||||
"Double Quote \\\"Escaped\\\" t" => '',
|
||||
'Double Quote Concat strings t' => '',
|
||||
|
||||
'Context !key Args t' => 'Context string',
|
||||
|
||||
'Context Unquoted t' => 'Context string unquoted',
|
||||
'Context Single Quoted t' => 'Context string single quoted',
|
||||
'Context Double Quoted t' => 'Context string double quoted',
|
||||
|
||||
"Standard Call plural{$etx}Standard Call @count plural" => '',
|
||||
"Whitespace Call plural{$etx}Whitespace Call @count plural" => '',
|
||||
|
||||
"Single Quote plural{$etx}Single Quote @count plural" => '',
|
||||
"Single Quote \\'Escaped\\' plural{$etx}Single Quote \\'Escaped\\' @count plural" => '',
|
||||
|
||||
"Double Quote plural{$etx}Double Quote @count plural" => '',
|
||||
"Double Quote \\\"Escaped\\\" plural{$etx}Double Quote \\\"Escaped\\\" @count plural" => '',
|
||||
|
||||
"Context !key Args plural{$etx}Context !key Args @count plural" => 'Context string',
|
||||
|
||||
"Context Unquoted plural{$etx}Context Unquoted @count plural" => 'Context string unquoted',
|
||||
"Context Single Quoted plural{$etx}Context Single Quoted @count plural" => 'Context string single quoted',
|
||||
"Context Double Quoted plural{$etx}Context Double Quoted @count plural" => 'Context string double quoted',
|
||||
|
||||
"No count argument plural - singular{$etx}No count argument plural - plural" => '',
|
||||
];
|
||||
|
||||
// Assert that all strings were found properly.
|
||||
foreach ($test_strings as $str => $context) {
|
||||
|
||||
// Make sure that the string was found in the file.
|
||||
$this->assertTrue(isset($source_strings[$str]), "Found source string: $str");
|
||||
|
||||
// Make sure that the proper context was matched.
|
||||
$this->assertArrayHasKey($str, $source_strings);
|
||||
$this->assertSame($context, $source_strings[$str]);
|
||||
}
|
||||
|
||||
$this->assertSameSize($test_strings, $source_strings, 'Found correct number of source strings.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert translations JS is added before drupal.js, because it depends on it.
|
||||
*/
|
||||
public function testLocaleTranslationJsDependencies(): void {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'translate interface',
|
||||
]);
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
// Code for the language.
|
||||
$langcode = 'es';
|
||||
// The English name for the language.
|
||||
$name = $this->randomMachineName(16);
|
||||
// The domain prefix.
|
||||
$prefix = $langcode;
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
|
||||
// Set path prefix.
|
||||
$edit = ["prefix[$langcode]" => $prefix];
|
||||
$this->drupalGet('admin/config/regional/language/detection/url');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// This forces locale.admin.js string sources to be imported, which contains
|
||||
// the next translation.
|
||||
$this->drupalGet($prefix . '/admin/config/regional/translate');
|
||||
|
||||
// Translate a string in locale.admin.js to our new language.
|
||||
$strings = \Drupal::service('locale.storage')
|
||||
->getStrings([
|
||||
'source' => 'Show description',
|
||||
'type' => 'javascript',
|
||||
'name' => 'core/modules/locale/js/locale.admin.js',
|
||||
]);
|
||||
$string = $strings[0];
|
||||
|
||||
$this->submitForm(['string' => 'Show description'], 'Filter');
|
||||
$edit = ['strings[' . $string->lid . '][translations][0]' => 'Mostrar descripcion'];
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Calculate the filename of the JS including the translations.
|
||||
$js_translation_files = \Drupal::state()->get('locale.translation.javascript');
|
||||
$js_filename = $prefix . '_' . $js_translation_files[$prefix] . '.js';
|
||||
|
||||
$content = $this->getSession()->getPage()->getContent();
|
||||
$this->assertSession()->responseContains('core/misc/drupal.js');
|
||||
$this->assertSession()->responseContains($js_filename);
|
||||
// Assert translations JS is included before drupal.js.
|
||||
$this->assertLessThan(strpos($content, 'core/misc/drupal.js'), strpos($content, $js_filename));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Component\Gettext\PoItem;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\WaitTerminateTestTrait;
|
||||
|
||||
/**
|
||||
* Tests LocaleLookup.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleLocaleLookupTest extends BrowserTestBase {
|
||||
|
||||
use WaitTerminateTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'locale_test'];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// The \Drupal\locale\LocaleTranslation service stores localization cache
|
||||
// data after the response is flushed to the client. We do not want to race
|
||||
// with any string translations that may be saving from the login below.
|
||||
$this->setWaitForTerminate();
|
||||
|
||||
// Change the language default object to different values.
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
$this->config('system.site')->set('default_langcode', 'fr')->save();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer modules',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that there are no circular dependencies.
|
||||
*/
|
||||
public function testCircularDependency(): void {
|
||||
// Ensure that we can enable early_translation_test on a non-english site.
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm(['modules[early_translation_test][enable]' => TRUE], 'Install');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests language fallback defaults.
|
||||
*/
|
||||
public function testLanguageFallbackDefaults(): void {
|
||||
$this->drupalGet('');
|
||||
// Ensure state of fallback languages persisted by
|
||||
// locale_test_language_fallback_candidates_locale_lookup_alter() is empty.
|
||||
$this->assertEquals([], \Drupal::state()->get('locale.test_language_fallback_candidates_locale_lookup_alter_candidates'));
|
||||
// Make sure there is enough information provided for alter hooks.
|
||||
$context = \Drupal::state()->get('locale.test_language_fallback_candidates_locale_lookup_alter_context');
|
||||
$this->assertEquals('fr', $context['langcode']);
|
||||
$this->assertEquals('locale_lookup', $context['operation']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests old plural style @count[number] fix.
|
||||
*
|
||||
* @dataProvider providerTestFixOldPluralStyle
|
||||
*/
|
||||
public function testFixOldPluralStyle($translation_value, $expected): void {
|
||||
$string_storage = \Drupal::service('locale.storage');
|
||||
$string = $string_storage->findString(['source' => 'Member for', 'context' => '']);
|
||||
$lid = $string->getId();
|
||||
$string_storage->createTranslation([
|
||||
'lid' => $lid,
|
||||
'language' => 'fr',
|
||||
'translation' => $translation_value,
|
||||
])->save();
|
||||
_locale_refresh_translations(['fr'], [$lid]);
|
||||
|
||||
// Check that 'count[2]' was fixed for render value.
|
||||
$this->drupalGet('');
|
||||
$this->assertSession()->pageTextContains($expected);
|
||||
|
||||
// Check that 'count[2]' was saved for source value.
|
||||
$translation = $string_storage->findTranslation(['language' => 'fr', 'lid' => $lid])->translation;
|
||||
$this->assertSame($translation_value, $translation, 'Source value not changed');
|
||||
$this->assertStringContainsString('@count[2]', $translation, 'Source value contains @count[2]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for testFixOldPluralStyle().
|
||||
*
|
||||
* @return array
|
||||
* An array of test data:
|
||||
* - translation value
|
||||
* - expected result
|
||||
*/
|
||||
public static function providerTestFixOldPluralStyle() {
|
||||
return [
|
||||
'non-plural translation' => ['@count[2] non-plural test', '@count[2] non-plural test'],
|
||||
'plural translation' => ['@count[2] plural test' . PoItem::DELIMITER, '@count plural test'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
/**
|
||||
* Tests installing in a different language with a dev version string.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleNonInteractiveDevInstallTest extends LocaleNonInteractiveInstallTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getVersionStringToTest(): string {
|
||||
include_once $this->root . '/core/includes/install.core.inc';
|
||||
$version = _install_get_version_info(\Drupal::VERSION);
|
||||
return $version['major'] . '.' . $version['minor'] . '.x';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests installing in a different language with a non-dev version string.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleNonInteractiveInstallTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
$this->markTestSkipped('Skipped due to major version-specific logic. See https://www.drupal.org/project/drupal/issues/3359322');
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version string to use in the translation file.
|
||||
*
|
||||
* @return string
|
||||
* The version string to test, for example, '8.0.0' or '8.6.x'.
|
||||
*/
|
||||
protected function getVersionStringToTest(): string {
|
||||
include_once $this->root . '/core/includes/install.core.inc';
|
||||
$version = _install_get_version_info(\Drupal::VERSION);
|
||||
return $version['major'] . '.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function installParameters() {
|
||||
$parameters = parent::installParameters();
|
||||
// Install Drupal in German.
|
||||
$parameters['parameters']['langcode'] = 'de';
|
||||
// Create a po file so we don't attempt to download one from
|
||||
// localize.drupal.org and to have a test translation that will not change.
|
||||
\Drupal::service('file_system')->mkdir($this->publicFilesDirectory . '/translations', NULL, TRUE);
|
||||
$contents = <<<PO
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter the password that accompanies your username."
|
||||
msgstr "Geben sie das Passwort für ihren Benutzernamen ein."
|
||||
|
||||
PO;
|
||||
$version = $this->getVersionStringToTest();
|
||||
file_put_contents($this->publicFilesDirectory . "/translations/drupal-{$version}.de.po", $contents);
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the expected translated text appears on the login screen.
|
||||
*/
|
||||
public function testInstallerTranslations(): void {
|
||||
$this->drupalGet('user/login');
|
||||
// cSpell:disable-next-line
|
||||
$this->assertSession()->responseContains('Geben sie das Passwort für ihren Benutzernamen ein.');
|
||||
}
|
||||
|
||||
}
|
||||
160
web/core/modules/locale/tests/src/Functional/LocalePathTest.php
Normal file
160
web/core/modules/locale/tests/src/Functional/LocalePathTest.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\Traits\Core\PathAliasTestTrait;
|
||||
|
||||
/**
|
||||
* Tests you can configure a language for individual URL aliases.
|
||||
*
|
||||
* @group locale
|
||||
* @group path
|
||||
*/
|
||||
class LocalePathTest extends BrowserTestBase {
|
||||
|
||||
use PathAliasTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'locale', 'path', 'views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
$this->config('system.site')->set('page.front', '/node')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a language can be associated with a path alias.
|
||||
*/
|
||||
public function testPathLanguageConfiguration(): void {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'create page content',
|
||||
'administer url aliases',
|
||||
'create url aliases',
|
||||
'access administration pages',
|
||||
'access content overview',
|
||||
]);
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language.
|
||||
$name = $this->randomMachineName(16);
|
||||
// The domain prefix.
|
||||
$prefix = $langcode;
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
|
||||
// Set path prefix.
|
||||
$edit = ["prefix[$langcode]" => $prefix];
|
||||
$this->drupalGet('admin/config/regional/language/detection/url');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Check that the "xx" front page is readily available because path prefix
|
||||
// negotiation is pre-configured.
|
||||
$this->drupalGet($prefix);
|
||||
$this->assertSession()->pageTextContains('Welcome!');
|
||||
|
||||
// Create a node.
|
||||
$node = $this->drupalCreateNode(['type' => 'page']);
|
||||
|
||||
// Create a path alias in default language (English).
|
||||
$path = 'admin/config/search/path/add';
|
||||
$english_path = $this->randomMachineName(8);
|
||||
$edit = [
|
||||
'path[0][value]' => '/node/' . $node->id(),
|
||||
'alias[0][value]' => '/' . $english_path,
|
||||
'langcode[0][value]' => 'en',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Create a path alias in new custom language.
|
||||
$custom_language_path = $this->randomMachineName(8);
|
||||
$edit = [
|
||||
'path[0][value]' => '/node/' . $node->id(),
|
||||
'alias[0][value]' => '/' . $custom_language_path,
|
||||
'langcode[0][value]' => $langcode,
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Confirm English language path alias works.
|
||||
$this->drupalGet($english_path);
|
||||
$this->assertSession()->pageTextContains($node->label());
|
||||
|
||||
// Confirm custom language path alias works.
|
||||
$this->drupalGet($prefix . '/' . $custom_language_path);
|
||||
$this->assertSession()->pageTextContains($node->label());
|
||||
|
||||
// Create a custom path.
|
||||
$custom_path = $this->randomMachineName(8);
|
||||
|
||||
// Check priority of language for alias by source path.
|
||||
$path_alias = $this->createPathAlias('/node/' . $node->id(), '/' . $custom_path, LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
$lookup_path = $this->container->get('path_alias.manager')->getAliasByPath('/node/' . $node->id(), 'en');
|
||||
$this->assertEquals('/' . $english_path, $lookup_path, 'English language alias has priority.');
|
||||
// Same check for language 'xx'.
|
||||
$lookup_path = $this->container->get('path_alias.manager')->getAliasByPath('/node/' . $node->id(), $prefix);
|
||||
$this->assertEquals('/' . $custom_language_path, $lookup_path, 'Custom language alias has priority.');
|
||||
$path_alias->delete();
|
||||
|
||||
// Create language nodes to check priority of aliases.
|
||||
$first_node = $this->drupalCreateNode(['type' => 'page', 'promote' => 1, 'langcode' => 'en']);
|
||||
$second_node = $this->drupalCreateNode(['type' => 'page', 'promote' => 1, 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED]);
|
||||
|
||||
// Assign a custom path alias to the first node with the English language.
|
||||
$this->createPathAlias('/node/' . $first_node->id(), '/' . $custom_path, $first_node->language()->getId());
|
||||
|
||||
// Assign a custom path alias to second node with
|
||||
// LanguageInterface::LANGCODE_NOT_SPECIFIED.
|
||||
$this->createPathAlias('/node/' . $second_node->id(), '/' . $custom_path, $second_node->language()->getId());
|
||||
|
||||
// Test that both node titles link to our path alias.
|
||||
$this->drupalGet('admin/content');
|
||||
$custom_path_url = Url::fromUserInput('/' . $custom_path)->toString();
|
||||
$this->assertSession()->elementExists('xpath', $this->assertSession()->buildXPathQuery('//a[@href=:href and normalize-space(text())=:title]', [
|
||||
':href' => $custom_path_url,
|
||||
':title' => $first_node->label(),
|
||||
]));
|
||||
$this->assertSession()->elementExists('xpath', $this->assertSession()->buildXPathQuery('//a[@href=:href and normalize-space(text())=:title]', [
|
||||
':href' => $custom_path_url,
|
||||
':title' => $second_node->label(),
|
||||
]));
|
||||
|
||||
// Confirm that the custom path leads to the first node.
|
||||
$this->drupalGet($custom_path);
|
||||
$this->assertSession()->pageTextContains($first_node->label());
|
||||
|
||||
// Confirm that the custom path with prefix leads to the second node.
|
||||
$this->drupalGet($prefix . '/' . $custom_path);
|
||||
$this->assertSession()->pageTextContains($second_node->label());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,485 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Component\Gettext\PoItem;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
// cspell:ignore nmsgid nmsgstr heure heures jours lundi ponedjeljak
|
||||
|
||||
/**
|
||||
* Tests plural handling for various languages.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocalePluralFormatTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* An admin user.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'translate interface',
|
||||
'access administration pages',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale_get_plural() and \Drupal::translation()->formatPlural().
|
||||
*/
|
||||
public function testGetPluralFormat(): void {
|
||||
// Import some .po files with formulas to set up the environment.
|
||||
// These will also add the languages to the system.
|
||||
$this->importPoFile($this->getPoFileWithSimplePlural(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
$this->importPoFile($this->getPoFileWithComplexPlural(), [
|
||||
'langcode' => 'hr',
|
||||
]);
|
||||
|
||||
// Attempt to import some broken .po files as well to prove that these
|
||||
// will not overwrite the proper plural formula imported above.
|
||||
$this->importPoFile($this->getPoFileWithMissingPlural(), [
|
||||
'langcode' => 'fr',
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
]);
|
||||
$this->importPoFile($this->getPoFileWithBrokenPlural(), [
|
||||
'langcode' => 'hr',
|
||||
'overwrite_options[not_customized]' => TRUE,
|
||||
]);
|
||||
|
||||
// Reset static caches from locale_get_plural() to ensure we get fresh data.
|
||||
drupal_static_reset('locale_get_plural');
|
||||
drupal_static_reset('locale_get_plural:plurals');
|
||||
drupal_static_reset('locale');
|
||||
|
||||
// Expected plural translation strings for each plural index.
|
||||
$plural_strings = [
|
||||
// English is not imported in this case, so we assume built-in text
|
||||
// and formulas.
|
||||
'en' => [
|
||||
0 => '1 hour',
|
||||
1 => '@count hours',
|
||||
],
|
||||
'fr' => [
|
||||
0 => '@count heure',
|
||||
1 => '@count heures',
|
||||
],
|
||||
'hr' => [
|
||||
0 => '@count sat',
|
||||
1 => '@count sata',
|
||||
2 => '@count sati',
|
||||
],
|
||||
// Hungarian is not imported, so it should assume the same text as
|
||||
// English, but it will always pick the plural form as per the built-in
|
||||
// logic, so only index -1 is relevant with the plural value.
|
||||
'hu' => [
|
||||
0 => '1 hour',
|
||||
-1 => '@count hours',
|
||||
],
|
||||
];
|
||||
|
||||
// Expected plural indexes precomputed base on the plural formulas with
|
||||
// given $count value.
|
||||
$plural_tests = [
|
||||
'en' => [
|
||||
1 => 0,
|
||||
0 => 1,
|
||||
5 => 1,
|
||||
123 => 1,
|
||||
235 => 1,
|
||||
],
|
||||
'fr' => [
|
||||
1 => 0,
|
||||
0 => 0,
|
||||
5 => 1,
|
||||
123 => 1,
|
||||
235 => 1,
|
||||
],
|
||||
'hr' => [
|
||||
1 => 0,
|
||||
21 => 0,
|
||||
0 => 2,
|
||||
2 => 1,
|
||||
8 => 2,
|
||||
123 => 1,
|
||||
235 => 2,
|
||||
],
|
||||
'hu' => [
|
||||
1 => -1,
|
||||
21 => -1,
|
||||
0 => -1,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($plural_tests as $langcode => $tests) {
|
||||
foreach ($tests as $count => $expected_plural_index) {
|
||||
// Assert that the we get the right plural index.
|
||||
$this->assertSame($expected_plural_index, locale_get_plural($count, $langcode), 'Computed plural index for ' . $langcode . ' for count ' . $count . ' is ' . $expected_plural_index);
|
||||
// Assert that the we get the right translation for that. Change the
|
||||
// expected index as per the logic for translation lookups.
|
||||
$expected_plural_index = ($count == 1) ? 0 : $expected_plural_index;
|
||||
$expected_plural_string = str_replace('@count', (string) $count, $plural_strings[$langcode][$expected_plural_index]);
|
||||
$this->assertSame($expected_plural_string, \Drupal::translation()->formatPlural($count, '@count hour', '@count hours', [], ['langcode' => $langcode])->render(), 'Plural translation of @count hour / @count hours for count ' . $count . ' in ' . $langcode . ' is ' . $expected_plural_string);
|
||||
// DO NOT use translation to pass translated strings into
|
||||
// PluralTranslatableMarkup::createFromTranslatedString() this way. It
|
||||
// is designed to be used with *already* translated text like settings
|
||||
// from configuration. We use PHP translation here just because we have
|
||||
// the expected result data in that format.
|
||||
$translated_string = \Drupal::translation()->translate('@count hour' . PoItem::DELIMITER . '@count hours', [], ['langcode' => $langcode]);
|
||||
$plural = PluralTranslatableMarkup::createFromTranslatedString($count, $translated_string, [], ['langcode' => $langcode]);
|
||||
$this->assertSame($expected_plural_string, $plural->render());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests plural editing of DateFormatter strings.
|
||||
*/
|
||||
public function testPluralEditDateFormatter(): void {
|
||||
|
||||
// Import some .po files with formulas to set up the environment.
|
||||
// These will also add the languages to the system.
|
||||
$this->importPoFile($this->getPoFileWithSimplePlural(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
|
||||
// Set French as the site default language.
|
||||
$this->config('system.site')->set('default_langcode', 'fr')->save();
|
||||
|
||||
// Visit User Info page before updating translation strings. Change the
|
||||
// created time to ensure that the we're dealing in seconds and it can't be
|
||||
// exactly 1 minute.
|
||||
$this->adminUser->set('created', time() - 1)->save();
|
||||
$this->drupalGet('user');
|
||||
|
||||
// Member for time should be translated.
|
||||
$this->assertSession()->pageTextContains("seconde");
|
||||
|
||||
$path = 'admin/config/regional/translate/';
|
||||
$search = [
|
||||
'langcode' => 'fr',
|
||||
// Limit to only translated strings to ensure that database ordering does
|
||||
// not break the test.
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($search, 'Filter');
|
||||
// Plural values for the langcode fr.
|
||||
$this->assertSession()->pageTextContains('@count seconde');
|
||||
$this->assertSession()->pageTextContains('@count secondes');
|
||||
|
||||
// Inject a plural source string to the database. We need to use a specific
|
||||
// langcode here because the language will be English by default and will
|
||||
// not save our source string for performance optimization if we do not ask
|
||||
// specifically for a language.
|
||||
\Drupal::translation()->formatPlural(1, '@count second', '@count seconds', [], ['langcode' => 'fr'])->render();
|
||||
$lid = Database::getConnection()->select('locales_source', 'ls')
|
||||
->fields('ls', ['lid'])
|
||||
->condition('source', "@count second" . PoItem::DELIMITER . "@count seconds")
|
||||
->condition('context', '')
|
||||
->execute()
|
||||
->fetchField();
|
||||
// Look up editing page for this plural string and check fields.
|
||||
$search = [
|
||||
'string' => '@count second',
|
||||
'langcode' => 'fr',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
|
||||
// Save complete translations for the string in langcode fr.
|
||||
$edit = [
|
||||
"strings[$lid][translations][0]" => '@count seconde updated',
|
||||
"strings[$lid][translations][1]" => '@count secondes updated',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// User interface input for translating seconds should not be duplicated
|
||||
$this->assertSession()->pageTextContainsOnce('@count seconds');
|
||||
|
||||
// Member for time should be translated. Change the created time to ensure
|
||||
// that the we're dealing in multiple seconds and it can't be exactly 1
|
||||
// second or minute.
|
||||
$this->adminUser->set('created', time() - 2)->save();
|
||||
$this->drupalGet('user');
|
||||
$this->assertSession()->pageTextContains("secondes updated");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests plural editing and export functionality.
|
||||
*/
|
||||
public function testPluralEditExport(): void {
|
||||
// Import some .po files with formulas to set up the environment.
|
||||
// These will also add the languages to the system.
|
||||
$this->importPoFile($this->getPoFileWithSimplePlural(), [
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
$this->importPoFile($this->getPoFileWithComplexPlural(), [
|
||||
'langcode' => 'hr',
|
||||
]);
|
||||
|
||||
// Get the French translations.
|
||||
$this->drupalGet('admin/config/regional/translate/export');
|
||||
$this->submitForm(['langcode' => 'fr'], 'Export');
|
||||
// Ensure we have a translation file.
|
||||
$this->assertSession()->pageTextContains('# French translation of Drupal');
|
||||
// Ensure our imported translations exist in the file.
|
||||
$this->assertSession()->responseContains("msgid \"Monday\"\nmsgstr \"lundi\"");
|
||||
// Check for plural export specifically.
|
||||
$this->assertSession()->responseContains("msgid \"@count hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure\"\nmsgstr[1] \"@count heures\"");
|
||||
|
||||
// Get the Croatian translations.
|
||||
$this->drupalGet('admin/config/regional/translate/export');
|
||||
$this->submitForm(['langcode' => 'hr'], 'Export');
|
||||
// Ensure we have a translation file.
|
||||
$this->assertSession()->pageTextContains('# Croatian translation of Drupal');
|
||||
// Ensure our imported translations exist in the file.
|
||||
$this->assertSession()->responseContains("msgid \"Monday\"\nmsgstr \"Ponedjeljak\"");
|
||||
// Check for plural export specifically.
|
||||
$this->assertSession()->responseContains("msgid \"@count hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata\"\nmsgstr[2] \"@count sati\"");
|
||||
|
||||
// Check if the source appears on the translation page.
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->assertSession()->pageTextContains("@count hour");
|
||||
$this->assertSession()->pageTextContains("@count hours");
|
||||
|
||||
// Look up editing page for this plural string and check fields.
|
||||
$path = 'admin/config/regional/translate/';
|
||||
$search = [
|
||||
'langcode' => 'hr',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($search, 'Filter');
|
||||
// Labels for plural editing elements.
|
||||
$this->assertSession()->pageTextContains('Singular form');
|
||||
$this->assertSession()->pageTextContains('First plural form');
|
||||
$this->assertSession()->pageTextContains('2. plural form');
|
||||
$this->assertSession()->pageTextNotContains('3. plural form');
|
||||
|
||||
// Plural values for langcode hr.
|
||||
$this->assertSession()->pageTextContains('@count sat');
|
||||
$this->assertSession()->pageTextContains('@count sata');
|
||||
$this->assertSession()->pageTextContains('@count sati');
|
||||
|
||||
$connection = Database::getConnection();
|
||||
// Edit langcode hr translations and see if that took effect.
|
||||
$lid = $connection->select('locales_source', 'ls')
|
||||
->fields('ls', ['lid'])
|
||||
->condition('source', "@count hour" . PoItem::DELIMITER . "@count hours")
|
||||
->condition('context', '')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$edit = [
|
||||
"strings[$lid][translations][1]" => '@count sata edited',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
$search = [
|
||||
'langcode' => 'fr',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
// Plural values for the langcode fr.
|
||||
$this->assertSession()->pageTextContains('@count heure');
|
||||
$this->assertSession()->pageTextContains('@count heures');
|
||||
$this->assertSession()->pageTextNotContains('2. plural form');
|
||||
|
||||
// Edit langcode fr translations and see if that took effect.
|
||||
$edit = [
|
||||
"strings[$lid][translations][0]" => '@count heure edited',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Inject a plural source string to the database. We need to use a specific
|
||||
// langcode here because the language will be English by default and will
|
||||
// not save our source string for performance optimization if we do not ask
|
||||
// specifically for a language.
|
||||
\Drupal::translation()->formatPlural(1, '@count day', '@count days', [], ['langcode' => 'fr'])->render();
|
||||
$lid = $connection->select('locales_source', 'ls')
|
||||
->fields('ls', ['lid'])
|
||||
->condition('source', "@count day" . PoItem::DELIMITER . "@count days")
|
||||
->condition('context', '')
|
||||
->execute()
|
||||
->fetchField();
|
||||
// Look up editing page for this plural string and check fields.
|
||||
$search = [
|
||||
'string' => '@count day',
|
||||
'langcode' => 'fr',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
|
||||
// Save complete translations for the string in langcode fr.
|
||||
$edit = [
|
||||
"strings[$lid][translations][0]" => '@count jour',
|
||||
"strings[$lid][translations][1]" => '@count jours',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Save complete translations for the string in langcode hr.
|
||||
$search = [
|
||||
'string' => '@count day',
|
||||
'langcode' => 'hr',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
|
||||
$edit = [
|
||||
"strings[$lid][translations][0]" => '@count dan',
|
||||
"strings[$lid][translations][1]" => '@count dana',
|
||||
"strings[$lid][translations][2]" => '@count dana',
|
||||
];
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Get the French translations.
|
||||
$this->drupalGet('admin/config/regional/translate/export');
|
||||
$this->submitForm(['langcode' => 'fr'], 'Export');
|
||||
// Check for plural export specifically.
|
||||
$this->assertSession()->responseContains("msgid \"@count hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure edited\"\nmsgstr[1] \"@count heures\"");
|
||||
$this->assertSession()->responseContains("msgid \"@count day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count jour\"\nmsgstr[1] \"@count jours\"");
|
||||
|
||||
// Get the Croatian translations.
|
||||
$this->drupalGet('admin/config/regional/translate/export');
|
||||
$this->submitForm(['langcode' => 'hr'], 'Export');
|
||||
// Check for plural export specifically.
|
||||
$this->assertSession()->responseContains("msgid \"@count hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata edited\"\nmsgstr[2] \"@count sati\"");
|
||||
$this->assertSession()->responseContains("msgid \"@count day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count dan\"\nmsgstr[1] \"@count dana\"\nmsgstr[2] \"@count dana\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a standalone .po file in a given language.
|
||||
*
|
||||
* @param string $contents
|
||||
* Contents of the .po file to import.
|
||||
* @param array $options
|
||||
* Additional options to pass to the translation import form.
|
||||
*/
|
||||
public function importPoFile($contents, array $options = []): void {
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$name = $file_system->tempnam('temporary://', "po_") . '.po';
|
||||
file_put_contents($name, $contents);
|
||||
$options['files[file]'] = $name;
|
||||
$this->drupalGet('admin/config/regional/translate/import');
|
||||
$this->submitForm($options, 'Import');
|
||||
$file_system->unlink($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file with a simple plural formula.
|
||||
*/
|
||||
public function getPoFileWithSimplePlural() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
msgid "@count hour"
|
||||
msgid_plural "@count hours"
|
||||
msgstr[0] "@count heure"
|
||||
msgstr[1] "@count heures"
|
||||
|
||||
msgid "@count second"
|
||||
msgid_plural "@count seconds"
|
||||
msgstr[0] "@count seconde"
|
||||
msgstr[1] "@count secondes"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file with a complex plural formula.
|
||||
*/
|
||||
public function getPoFileWithComplexPlural() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
|
||||
|
||||
msgid "@count hour"
|
||||
msgid_plural "@count hours"
|
||||
msgstr[0] "@count sat"
|
||||
msgstr[1] "@count sata"
|
||||
msgstr[2] "@count sati"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "Ponedjeljak"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file with a missing plural formula.
|
||||
*/
|
||||
public function getPoFileWithMissingPlural() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a .po file with a broken plural formula.
|
||||
*/
|
||||
public function getPoFileWithBrokenPlural() {
|
||||
return <<< EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: broken, will not parse\\n"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "Ponedjeljak"
|
||||
EOF;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\RequirementsPageTrait;
|
||||
|
||||
/**
|
||||
* Adds and configures languages to check field schema definition.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslatedSchemaDefinitionTest extends BrowserTestBase {
|
||||
|
||||
use RequirementsPageTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'locale', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected bool $useOneTimeLoginLinks = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
$this->config('system.site')->set('default_langcode', 'fr')->save();
|
||||
|
||||
// Clear all caches so that the base field definition, its cache in the
|
||||
// entity field manager, the t() cache, etc. are all cleared.
|
||||
$this->resetAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that translated field descriptions do not affect the update system.
|
||||
*/
|
||||
public function testTranslatedSchemaDefinition(): void {
|
||||
/** @var \Drupal\locale\StringDatabaseStorage $stringStorage */
|
||||
$stringStorage = \Drupal::service('locale.storage');
|
||||
|
||||
$source = $stringStorage->createString([
|
||||
'source' => 'Revision ID',
|
||||
])->save();
|
||||
|
||||
$stringStorage->createTranslation([
|
||||
'lid' => $source->lid,
|
||||
'language' => 'fr',
|
||||
'translation' => 'Translated Revision ID',
|
||||
])->save();
|
||||
|
||||
// Ensure that the field is translated when access through the API.
|
||||
$this->assertEquals('Translated Revision ID', \Drupal::service('entity_field.manager')->getBaseFieldDefinitions('node')['vid']->getLabel());
|
||||
|
||||
// Assert there are no updates.
|
||||
$this->assertFalse(\Drupal::service('entity.definition_update_manager')->needsUpdates());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that translations do not affect the update system.
|
||||
*/
|
||||
public function testTranslatedUpdate(): void {
|
||||
// Visit the update page to collect any strings that may be translatable.
|
||||
$user = $this->drupalCreateUser(['administer software updates']);
|
||||
$this->drupalLogin($user);
|
||||
$update_url = Url::fromRoute('system.db_update')->setAbsolute()->toString();
|
||||
|
||||
// Collect strings from the PHP warning page, if applicable, as well as the
|
||||
// main update page.
|
||||
$this->drupalGet($update_url, ['external' => TRUE]);
|
||||
$this->updateRequirementsProblem();
|
||||
|
||||
/** @var \Drupal\locale\StringDatabaseStorage $stringStorage */
|
||||
$stringStorage = \Drupal::service('locale.storage');
|
||||
$sources = $stringStorage->getStrings();
|
||||
|
||||
// Translate all source strings found.
|
||||
foreach ($sources as $source) {
|
||||
$stringStorage->createTranslation([
|
||||
'lid' => $source->lid,
|
||||
'language' => 'fr',
|
||||
'translation' => $this->randomMachineName(100),
|
||||
])->save();
|
||||
}
|
||||
|
||||
// Ensure that there are no updates just due to translations. Check for
|
||||
// markup and a link instead of specific text because text may be
|
||||
// translated.
|
||||
$this->drupalGet($update_url . '/selection', ['external' => TRUE]);
|
||||
$this->assertSession()->statusMessageExists('status');
|
||||
$this->assertSession()->linkByHrefNotExists('fr/update.php/run', 'No link to run updates.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\StreamWrapper\PublicStream;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests how translations are handled when a project gets updated.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslationChangeProjectVersionTest extends LocaleUpdateBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
\Drupal::moduleHandler()->loadInclude('locale', 'inc', 'locale.batch');
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
|
||||
\Drupal::state()->set('locale.test_projects_alter', TRUE);
|
||||
\Drupal::state()->set('locale.remove_core_project', TRUE);
|
||||
|
||||
// Setup the environment.
|
||||
$config = $this->config('locale.settings');
|
||||
$public_path = PublicStream::basePath();
|
||||
$this->setTranslationsDirectory($public_path . '/local');
|
||||
$config
|
||||
->set('translation.default_filename', '%project-%version.%language._po')
|
||||
->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
||||
->save();
|
||||
|
||||
// This test uses .po files for the old translation file instead of the ._po
|
||||
// files because locale_translate_get_interface_translation_files() (used to
|
||||
// delete old translation files) only works with .po files.
|
||||
// The new translation file uses _po.
|
||||
// Old version: 8.x-1.0; New version: 8.x-1.1.
|
||||
$this->makePoFile('remote/all/contrib_module_one', 'contrib_module_one-8.x-1.0.de.po', $this->timestampOld, []);
|
||||
$this->makePoFile('remote/all/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestampNew, []);
|
||||
$this->makePoFile('local', 'contrib_module_one-8.x-1.0.de.po', $this->timestampOld, []);
|
||||
|
||||
// Initialize the projects status and change the project version to the old
|
||||
// version. This makes the code update the module translation to the new
|
||||
// version when the (batch) update script is triggered.
|
||||
$status = locale_translation_get_status();
|
||||
$status['contrib_module_one']['de']->version = '8.x-1.0';
|
||||
\Drupal::keyValue('locale.translation_status')->setMultiple($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests update translations when project version changes.
|
||||
*/
|
||||
public function testUpdateImportSourceRemote(): void {
|
||||
|
||||
// Verify that the project status has the old version.
|
||||
$status = locale_translation_get_status(['contrib_module_one']);
|
||||
$this->assertEquals('8.x-1.0', $status['contrib_module_one']['de']->version);
|
||||
|
||||
// Verify that the old translation file exists and the new does not exist.
|
||||
$this->assertFileExists('translations://contrib_module_one-8.x-1.0.de.po');
|
||||
$this->assertFileDoesNotExist('translations://contrib_module_one-8.x-1.1.de._po');
|
||||
|
||||
// Run batch tasks.
|
||||
$context = [];
|
||||
locale_translation_batch_version_check('contrib_module_one', 'de', $context);
|
||||
locale_translation_batch_status_check('contrib_module_one', 'de', [], $context);
|
||||
locale_translation_batch_fetch_download('contrib_module_one', 'de', $context);
|
||||
|
||||
// Verify that the project status has the new version.
|
||||
$status = locale_translation_get_status(['contrib_module_one']);
|
||||
$this->assertEquals('8.x-1.1', $status['contrib_module_one']['de']->version);
|
||||
|
||||
// Verify that the old translation file was removed and the new was
|
||||
// downloaded.
|
||||
$this->assertFileDoesNotExist('translations://contrib_module_one-8.x-1.0.de.po');
|
||||
$this->assertFileExists('translations://contrib_module_one-8.x-1.1.de._po');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\StreamWrapper\PublicStream;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
|
||||
/**
|
||||
* Tests locale translation download.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslationDownloadTest extends LocaleUpdateBase {
|
||||
|
||||
/**
|
||||
* The virtual file stream for storing translations.
|
||||
*
|
||||
* @var \org\bovigo\vfs\vfsStreamDirectory
|
||||
*/
|
||||
protected $translationsStream;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$moduleHandler = $this->container->get('module_handler');
|
||||
$moduleHandler->loadInclude('locale', 'inc', 'locale.batch');
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
|
||||
// Let the translations:// stream wrapper point to a virtual file system to
|
||||
// make it independent from the test environment.
|
||||
$this->translationsStream = vfsStream::setup('translations');
|
||||
\Drupal::configFactory()->getEditable('locale.settings')
|
||||
->set('translation.path', $this->translationsStream->url())
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation download from remote sources.
|
||||
*/
|
||||
public function testUpdateImportSourceRemote(): void {
|
||||
|
||||
// Provide remote and 'previously' downloaded translation file.
|
||||
$this->setTranslationFiles();
|
||||
vfsStream::create([
|
||||
'contrib_module_one-8.x-1.1.de._po' => '__old_content__',
|
||||
], $this->translationsStream);
|
||||
|
||||
$url = \Drupal::service('url_generator')->generateFromRoute('<front>', [], ['absolute' => TRUE]);
|
||||
$uri = $url . PublicStream::basePath() . '/remote/all/contrib_module_one/contrib_module_one-8.x-1.1.de._po';
|
||||
$source_file = (object) [
|
||||
'uri' => $uri,
|
||||
];
|
||||
|
||||
$result = locale_translation_download_source($source_file, 'translations://');
|
||||
|
||||
$this->assertEquals('translations://contrib_module_one-8.x-1.1.de._po', $result->uri);
|
||||
$this->assertFileDoesNotExist('translations://contrib_module_one-8.x-1.1.de_0._po');
|
||||
$this->assertFileExists('translations://contrib_module_one-8.x-1.1.de._po');
|
||||
$this->assertStringNotContainsString('__old_content__', file_get_contents('translations://contrib_module_one-8.x-1.1.de._po'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,644 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
// phpcs:disable Drupal.Semantics.FunctionT.NotLiteralString
|
||||
|
||||
/**
|
||||
* Tests the validation of translation strings and search results.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslationUiTest extends BrowserTestBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected bool $useOneTimeLoginLinks = FALSE;
|
||||
|
||||
/**
|
||||
* Enable interface translation to English.
|
||||
*/
|
||||
public function testEnglishTranslation(): void {
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->drupalGet('admin/config/regional/language/edit/en');
|
||||
$this->submitForm(['locale_translate_english' => TRUE], 'Save language');
|
||||
$this->assertSession()->linkByHrefExists('/admin/config/regional/translate?langcode=en', 0, 'Enabled interface translation to English.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a language and tests string translation by users with the appropriate permissions.
|
||||
*/
|
||||
public function testStringTranslation(): void {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
]);
|
||||
// User to translate and delete string.
|
||||
$translate_user = $this->drupalCreateUser([
|
||||
'translate interface',
|
||||
'access administration pages',
|
||||
]);
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = 'Foo';
|
||||
// This will be the translation of $name.
|
||||
$translation = $this->randomMachineName(16);
|
||||
$translation_to_en = $this->randomMachineName(16);
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
// Add string.
|
||||
$this->t($name, [], ['langcode' => $langcode])->render();
|
||||
// Reset locale cache.
|
||||
$this->container->get('string_translation')->reset();
|
||||
$this->assertSession()->responseContains('"edit-languages-' . $langcode . '-weight"');
|
||||
// Ensure that test language was added.
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Add a whitespace at the end of string to ensure it is found.
|
||||
$name_ws = $name . " ";
|
||||
|
||||
// Search for the name and translate it.
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = [
|
||||
'string' => $name_ws,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
// Check that search finds the string as untranslated.
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
|
||||
// No t() here, it's surely not translated yet.
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
// Verify that there is no way to translate the string to English.
|
||||
$this->assertSession()->optionNotExists('edit-langcode', 'en');
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/config/regional/language/edit/en');
|
||||
$this->submitForm(['locale_translate_english' => TRUE], 'Save language');
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($translate_user);
|
||||
// Check that search finds the string as untranslated.
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
|
||||
// Assume this is the only result, given the random name.
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $translation,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
$this->assertSession()->pageTextContains('The strings have been saved.');
|
||||
$url_bits = explode('?', $this->getUrl());
|
||||
$this->assertEquals(Url::fromRoute('locale.translate_page', [], ['absolute' => TRUE])->toString(), $url_bits[0], 'Correct page redirection.');
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains($translation);
|
||||
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $translation_to_en,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains($translation_to_en);
|
||||
|
||||
$this->assertNotEquals($translation, $name);
|
||||
$this->assertEquals($translation, $this->t($name, [], ['langcode' => $langcode]), 't() works for non-English.');
|
||||
// Refresh the locale() cache to get fresh data from t() below. We are in
|
||||
// the same HTTP request and therefore t() is not refreshed by saving the
|
||||
// translation above.
|
||||
$this->container->get('string_translation')->reset();
|
||||
// Now we should get the proper fresh translation from t().
|
||||
$this->assertNotEquals($translation_to_en, $name);
|
||||
$this->assertEquals($translation_to_en, $this->t($name, [], ['langcode' => 'en']), 't() works for English.');
|
||||
$this->assertTrue($this->t($name, [], ['langcode' => LanguageInterface::LANGCODE_SYSTEM]) == $name, 't() works for LanguageInterface::LANGCODE_SYSTEM.');
|
||||
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('No strings available.');
|
||||
|
||||
// Test invalidation of 'rendered' cache tag after string translation.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('xx/user/login');
|
||||
$this->assertSession()->pageTextContains('Password');
|
||||
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = [
|
||||
'string' => 'Password',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => 'Llamas are larger than frogs.',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('xx/user/login');
|
||||
$this->assertSession()->pageTextContains('Llamas are larger than frogs.');
|
||||
|
||||
// Delete the language.
|
||||
$this->drupalLogin($admin_user);
|
||||
$path = 'admin/config/regional/language/delete/' . $langcode;
|
||||
// This a confirm form, we do not need any fields changed.
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertSession()->pageTextContains("The {$name} ({$langcode}) language has been removed.");
|
||||
// Reload to remove $name.
|
||||
$this->drupalGet($path);
|
||||
// Verify that language is no longer found.
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Delete the string.
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
// Assume this is the only result, given the random name.
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => '',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
$this->assertSession()->responseContains($name);
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => 'en',
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextNotContains('No strings available.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the rebuilding of JavaScript translation files on deletion.
|
||||
*/
|
||||
public function testJavaScriptTranslation(): void {
|
||||
$user = $this->drupalCreateUser([
|
||||
'translate interface',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = $this->randomMachineName(16);
|
||||
|
||||
// Add custom language.
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
$this->container->get('language_manager')->reset();
|
||||
|
||||
// Build the JavaScript translation file.
|
||||
|
||||
// Retrieve the source string of the first string available in the
|
||||
// {locales_source} table and translate it.
|
||||
$query = Database::getConnection()->select('locales_source', 's');
|
||||
$query->addJoin('INNER', 'locales_location', 'l', '[s].[lid] = [l].[lid]');
|
||||
$source = $query->fields('s', ['source'])
|
||||
->condition('l.type', 'javascript')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetchField();
|
||||
|
||||
$search = [
|
||||
'string' => $source,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Trigger JavaScript translation parsing and building.
|
||||
_locale_rebuild_js($langcode);
|
||||
|
||||
$locale_javascripts = \Drupal::state()->get('locale.translation.javascript', []);
|
||||
$js_file = 'public://' . $config->get('javascript.directory') . '/' . $langcode . '_' . $locale_javascripts[$langcode] . '.js';
|
||||
$this->assertFileExists($js_file);
|
||||
|
||||
// Test JavaScript translation rebuilding.
|
||||
\Drupal::service('file_system')->delete($js_file);
|
||||
$this->assertFileDoesNotExist($js_file);
|
||||
_locale_rebuild_js($langcode);
|
||||
$this->assertFileExists($js_file);
|
||||
|
||||
// Test if JavaScript translation contains a custom string override.
|
||||
$string_override = $this->randomMachineName();
|
||||
$settings = Settings::getAll();
|
||||
$settings['locale_custom_strings_' . $langcode] = ['' => [$string_override => $string_override]];
|
||||
// Recreate the settings static.
|
||||
new Settings($settings);
|
||||
_locale_rebuild_js($langcode);
|
||||
$locale_javascripts = \Drupal::state()->get('locale.translation.javascript', []);
|
||||
$js_file = 'public://' . $config->get('javascript.directory') . '/' . $langcode . '_' . $locale_javascripts[$langcode] . '.js';
|
||||
$content = file_get_contents($js_file);
|
||||
$this->assertStringContainsString('"' . $string_override . '":"' . $string_override . '"', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the validation of the translation input.
|
||||
*/
|
||||
public function testStringValidation(): void {
|
||||
// User to add language and strings.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'translate interface',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = $this->randomMachineName(16);
|
||||
|
||||
// These will be the invalid translations of $name.
|
||||
$key = $this->randomMachineName(16);
|
||||
$bad_translations[$key] = "<script>alert('xss');</script>" . $key;
|
||||
$key = $this->randomMachineName(16);
|
||||
$bad_translations[$key] = '<img SRC="javascript:alert(\'xss\');">' . $key;
|
||||
$key = $this->randomMachineName(16);
|
||||
$bad_translations[$key] = '<<SCRIPT>alert("xss");//<</SCRIPT>' . $key;
|
||||
$key = $this->randomMachineName(16);
|
||||
$bad_translations[$key] = "<BODY ONLOAD=alert('xss')>" . $key;
|
||||
|
||||
// Add custom language.
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
// Add string.
|
||||
$this->t($name, [], ['langcode' => $langcode])->render();
|
||||
// Reset locale cache.
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
// Find the edit path.
|
||||
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
foreach ($bad_translations as $translation) {
|
||||
$edit = [
|
||||
$lid => $translation,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
// Check for a form error on the textarea, which means the string was
|
||||
// rejected as unsafe.
|
||||
$this->assertSession()->elementAttributeContains('xpath', '//form[@id="locale-translate-edit-form"]//textarea', 'class', 'error');
|
||||
$this->assertSession()->pageTextNotContains('The string has been saved.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation search form.
|
||||
*/
|
||||
public function testStringSearch(): void {
|
||||
// User to add and remove language.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
]);
|
||||
// User to translate and delete string.
|
||||
$translate_user = $this->drupalCreateUser([
|
||||
'translate interface',
|
||||
'access administration pages',
|
||||
]);
|
||||
|
||||
// Code for the language.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = $this->randomMachineName(16);
|
||||
// This will be the translation of $name.
|
||||
$translation = $this->randomMachineName(16);
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => 'yy',
|
||||
'label' => $this->randomMachineName(16),
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
|
||||
// Add string.
|
||||
$this->t($name, [], ['langcode' => $langcode])->render();
|
||||
// Reset locale cache.
|
||||
$this->container->get('string_translation')->reset();
|
||||
$this->drupalLogout();
|
||||
|
||||
// Search for the name.
|
||||
$this->drupalLogin($translate_user);
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
// pageTextContains() seems to remove the input field where $name always
|
||||
// could be found, so this is not a false assert. See how
|
||||
// pageTextNotContains succeeds later.
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
|
||||
// Ensure untranslated string doesn't appear if searching on 'only
|
||||
// translated strings'.
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('No strings available.');
|
||||
|
||||
// Ensure untranslated string appears if searching on 'only untranslated
|
||||
// strings'.
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextNotContains('No strings available.');
|
||||
|
||||
// Add translation.
|
||||
// Assume this is the only result, given the random name.
|
||||
// We save the lid from the path.
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $translation,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Ensure translated string does appear if searching on 'only
|
||||
// translated strings'.
|
||||
$search = [
|
||||
'string' => $translation,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextNotContains('No strings available.');
|
||||
|
||||
// Ensure translated source string doesn't appear if searching on 'only
|
||||
// untranslated strings'.
|
||||
$search = [
|
||||
'string' => $name,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('No strings available.');
|
||||
|
||||
// Ensure translated string doesn't appear if searching on 'only
|
||||
// untranslated strings'.
|
||||
$search = [
|
||||
'string' => $translation,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('No strings available.');
|
||||
|
||||
// Ensure translated string does appear if searching on the custom language.
|
||||
$search = [
|
||||
'string' => $translation,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextNotContains('No strings available.');
|
||||
|
||||
// Ensure translated string doesn't appear if searching in System (English).
|
||||
$search = [
|
||||
'string' => $translation,
|
||||
'langcode' => 'yy',
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('No strings available.');
|
||||
|
||||
// Search for a string that isn't in the system.
|
||||
$unavailable_string = $this->randomMachineName(16);
|
||||
$search = [
|
||||
'string' => $unavailable_string,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('No strings available.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that only changed strings are saved customized when edited.
|
||||
*/
|
||||
public function testUICustomizedStrings(): void {
|
||||
$user = $this->drupalCreateUser([
|
||||
'translate interface',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
|
||||
// Create test source string.
|
||||
$string = $this->container->get('locale.storage')->createString([
|
||||
'source' => $this->randomMachineName(100),
|
||||
'context' => $this->randomMachineName(20),
|
||||
])->save();
|
||||
|
||||
// Create translation for new string and save it as non-customized.
|
||||
$translation = $this->container->get('locale.storage')->createTranslation([
|
||||
'lid' => $string->lid,
|
||||
'language' => 'de',
|
||||
'translation' => $this->randomMachineName(100),
|
||||
'customized' => 0,
|
||||
])->save();
|
||||
|
||||
// Reset locale cache.
|
||||
$this->container->get('string_translation')->reset();
|
||||
|
||||
// Ensure non-customized translation string does appear if searching
|
||||
// non-customized translation.
|
||||
$search = [
|
||||
'string' => $string->getString(),
|
||||
'langcode' => 'de',
|
||||
'translation' => 'translated',
|
||||
'customized' => '0',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
|
||||
$this->assertSession()->pageTextContains($translation->getString());
|
||||
|
||||
// Submit the translations without changing the translation.
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $translation->getString(),
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Ensure unchanged translation string does appear if searching
|
||||
// non-customized translation.
|
||||
$search = [
|
||||
'string' => $string->getString(),
|
||||
'langcode' => 'de',
|
||||
'translation' => 'translated',
|
||||
'customized' => '0',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains($string->getString());
|
||||
|
||||
// Submit the translations with a new translation.
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $this->randomMachineName(100),
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Ensure changed translation string does appear if searching customized
|
||||
// translation.
|
||||
$search = [
|
||||
'string' => $string->getString(),
|
||||
'langcode' => 'de',
|
||||
'translation' => 'translated',
|
||||
'customized' => '1',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains($string->getString());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\StreamWrapper\PublicStream;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
// cspell:ignore februar januar juni marz
|
||||
|
||||
/**
|
||||
* Base class for testing updates to string translations.
|
||||
*/
|
||||
abstract class LocaleUpdateBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Timestamp for an old translation.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timestampOld;
|
||||
|
||||
/**
|
||||
* Timestamp for a medium aged translation.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timestampMedium;
|
||||
|
||||
/**
|
||||
* Timestamp for a new translation.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timestampNew;
|
||||
|
||||
/**
|
||||
* Timestamp for current time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timestampNow;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'locale_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Setup timestamps to identify old and new translation sources.
|
||||
$this->timestampOld = \Drupal::time()->getRequestTime() - 300;
|
||||
$this->timestampMedium = \Drupal::time()->getRequestTime() - 200;
|
||||
$this->timestampNew = \Drupal::time()->getRequestTime() - 100;
|
||||
$this->timestampNow = \Drupal::time()->getRequestTime();
|
||||
|
||||
// Enable import of translations. By default this is disabled for automated
|
||||
// tests.
|
||||
$this->config('locale.settings')
|
||||
->set('translation.import_enabled', TRUE)
|
||||
->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the default translations directory.
|
||||
*
|
||||
* @param string $path
|
||||
* Path of the translations directory relative to the drupal installation
|
||||
* directory.
|
||||
*/
|
||||
protected function setTranslationsDirectory($path) {
|
||||
\Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
|
||||
$this->config('locale.settings')->set('translation.path', $path)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a language.
|
||||
*
|
||||
* @param string $langcode
|
||||
* The language code of the language to add.
|
||||
*/
|
||||
protected function addLanguage($langcode) {
|
||||
$edit = ['predefined_langcode' => $langcode];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add language');
|
||||
$this->container->get('language_manager')->reset();
|
||||
$this->assertNotEmpty(\Drupal::languageManager()->getLanguage($langcode), "Language $langcode added.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a translation file and tests its timestamp.
|
||||
*
|
||||
* @param string $path
|
||||
* Path of the file relative to the public file path.
|
||||
* @param string $filename
|
||||
* Name of the file to create.
|
||||
* @param int $timestamp
|
||||
* (optional) Timestamp to set the file to. Defaults to current time.
|
||||
* @param array $translations
|
||||
* (optional) Array of source/target value translation strings. Only
|
||||
* singular strings are supported, no plurals. No double quotes are allowed
|
||||
* in source and translations strings.
|
||||
*/
|
||||
protected function makePoFile($path, $filename, $timestamp = NULL, array $translations = []) {
|
||||
$timestamp = $timestamp ?: \Drupal::time()->getRequestTime();
|
||||
$path = 'public://' . $path;
|
||||
$text = '';
|
||||
$po_header = <<<EOF
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Drupal 8\\n"
|
||||
"MIME-Version: 1.0\\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n"
|
||||
"Content-Transfer-Encoding: 8bit\\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
||||
|
||||
EOF;
|
||||
|
||||
// Convert array of translations to Gettext source and translation strings.
|
||||
if ($translations) {
|
||||
foreach ($translations as $source => $target) {
|
||||
$text .= 'msgid "' . $source . '"' . "\n";
|
||||
$text .= 'msgstr "' . $target . '"' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
\Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
|
||||
$fileUri = $path . '/' . $filename;
|
||||
$file = File::create([
|
||||
'uid' => 1,
|
||||
'filename' => $filename,
|
||||
'uri' => $fileUri,
|
||||
'filemime' => 'text/x-gettext-translation',
|
||||
'timestamp' => $timestamp,
|
||||
]);
|
||||
$file->setPermanent();
|
||||
file_put_contents($file->getFileUri(), $po_header . $text);
|
||||
touch(\Drupal::service('file_system')->realpath($file->getFileUri()), $timestamp);
|
||||
$file->save();
|
||||
|
||||
$this->assertTrue(file_exists($fileUri));
|
||||
$this->assertEquals($timestamp, filemtime($fileUri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the environment containing local and remote translation files.
|
||||
*
|
||||
* Update tests require a simulated environment for local and remote files.
|
||||
* Normally remote files are located at a remote server (e.g. ftp.drupal.org).
|
||||
* For testing we can not rely on this. A directory in the file system of the
|
||||
* test site is designated for remote files and is addressed using an absolute
|
||||
* URL. Because Drupal does not allow files with a po extension to be accessed
|
||||
* (denied in .htaccess) the translation files get a _po extension. Another
|
||||
* directory is designated for local translation files.
|
||||
*
|
||||
* The environment is set up with the following files. File creation times are
|
||||
* set to create different variations in test conditions.
|
||||
* contrib_module_one
|
||||
* - remote file: timestamp new
|
||||
* - local file: timestamp old
|
||||
* contrib_module_two
|
||||
* - remote file: timestamp old
|
||||
* - local file: timestamp new
|
||||
* contrib_module_three
|
||||
* - remote file: timestamp old
|
||||
* - local file: timestamp old
|
||||
* custom_module_one
|
||||
* - local file: timestamp new
|
||||
* Time stamp of current translation set by setCurrentTranslations() is always
|
||||
* timestamp medium. This makes it easy to predict which translation will be
|
||||
* imported.
|
||||
*/
|
||||
protected function setTranslationFiles() {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// A flag is set to let the locale_test module replace the project data with
|
||||
// a set of test projects which match the below project files.
|
||||
\Drupal::state()->set('locale.test_projects_alter', TRUE);
|
||||
\Drupal::state()->set('locale.remove_core_project', FALSE);
|
||||
|
||||
// Setup the environment.
|
||||
$public_path = PublicStream::basePath();
|
||||
$this->setTranslationsDirectory($public_path . '/local');
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po')->save();
|
||||
|
||||
// Setting up sets of translations for the translation files.
|
||||
$translations_one = ['January' => 'Januar_1', 'February' => 'Februar_1', 'March' => 'Marz_1'];
|
||||
$translations_two = ['February' => 'Februar_2', 'March' => 'Marz_2', 'April' => 'April_2'];
|
||||
$translations_three = ['April' => 'April_3', 'May' => 'Mai_3', 'June' => 'Juni_3'];
|
||||
|
||||
// Add a number of files to the local file system to serve as remote
|
||||
// translation server and match the project definitions set in
|
||||
// locale_test_locale_translation_projects_alter().
|
||||
$this->makePoFile('remote/all/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestampNew, $translations_one);
|
||||
$this->makePoFile('remote/all/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampOld, $translations_two);
|
||||
$this->makePoFile('remote/all/contrib_module_three', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three);
|
||||
|
||||
// Add a number of files to the local file system to serve as local
|
||||
// translation files and match the project definitions set in
|
||||
// locale_test_locale_translation_projects_alter().
|
||||
$this->makePoFile('local', 'contrib_module_one-8.x-1.1.de._po', $this->timestampOld, $translations_one);
|
||||
$this->makePoFile('local', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampNew, $translations_two);
|
||||
$this->makePoFile('local', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three);
|
||||
$this->makePoFile('local', 'custom_module_one.de.po', $this->timestampNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up existing translations and their statuses in the database.
|
||||
*/
|
||||
protected function setCurrentTranslations() {
|
||||
// Add non customized translations to the database.
|
||||
$langcode = 'de';
|
||||
$context = '';
|
||||
$non_customized_translations = [
|
||||
'March' => 'Marz',
|
||||
'June' => 'Juni',
|
||||
];
|
||||
foreach ($non_customized_translations as $source => $translation) {
|
||||
$string = $this->container->get('locale.storage')->createString([
|
||||
'source' => $source,
|
||||
'context' => $context,
|
||||
])
|
||||
->save();
|
||||
$this->container->get('locale.storage')->createTranslation([
|
||||
'lid' => $string->getId(),
|
||||
'language' => $langcode,
|
||||
'translation' => $translation,
|
||||
'customized' => LOCALE_NOT_CUSTOMIZED,
|
||||
])->save();
|
||||
}
|
||||
|
||||
// Add customized translations to the database.
|
||||
$customized_translations = [
|
||||
'January' => 'Januar_customized',
|
||||
'February' => 'Februar_customized',
|
||||
'May' => 'Mai_customized',
|
||||
];
|
||||
foreach ($customized_translations as $source => $translation) {
|
||||
$string = $this->container->get('locale.storage')->createString([
|
||||
'source' => $source,
|
||||
'context' => $context,
|
||||
])
|
||||
->save();
|
||||
$this->container->get('locale.storage')->createTranslation([
|
||||
'lid' => $string->getId(),
|
||||
'language' => $langcode,
|
||||
'translation' => $translation,
|
||||
'customized' => LOCALE_CUSTOMIZED,
|
||||
])->save();
|
||||
}
|
||||
|
||||
// Add a state of current translations in locale_files.
|
||||
$default = [
|
||||
'langcode' => $langcode,
|
||||
'uri' => '',
|
||||
'timestamp' => $this->timestampMedium,
|
||||
'last_checked' => $this->timestampMedium,
|
||||
];
|
||||
$data[] = [
|
||||
'project' => 'contrib_module_one',
|
||||
'filename' => 'contrib_module_one-8.x-1.1.de._po',
|
||||
'version' => '8.x-1.1',
|
||||
];
|
||||
$data[] = [
|
||||
'project' => 'contrib_module_two',
|
||||
'filename' => 'contrib_module_two-8.x-2.0-beta4.de._po',
|
||||
'version' => '8.x-2.0-beta4',
|
||||
];
|
||||
$data[] = [
|
||||
'project' => 'contrib_module_three',
|
||||
'filename' => 'contrib_module_three-8.x-1.0.de._po',
|
||||
'version' => '8.x-1.0',
|
||||
];
|
||||
$data[] = [
|
||||
'project' => 'custom_module_one',
|
||||
'filename' => 'custom_module_one.de.po',
|
||||
'version' => '',
|
||||
];
|
||||
$connection = Database::getConnection();
|
||||
foreach ($data as $file) {
|
||||
$file = array_merge($default, $file);
|
||||
$connection->insert('locale_file')->fields($file)->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the translation of a string.
|
||||
*
|
||||
* @param string $source
|
||||
* Translation source string.
|
||||
* @param string $translation
|
||||
* Translation to check. Use empty string to check for a non-existent
|
||||
* translation.
|
||||
* @param string $langcode
|
||||
* Language code of the language to translate to.
|
||||
* @param string $message
|
||||
* (optional) A message to display with the assertion.
|
||||
*/
|
||||
protected function assertTranslation($source, $translation, $langcode, $message = '') {
|
||||
$query = Database::getConnection()->select('locales_target', 'lt');
|
||||
$query->innerJoin('locales_source', 'ls', '[ls].[lid] = [lt].[lid]');
|
||||
$db_translation = $query->fields('lt', ['translation'])
|
||||
->condition('ls.source', $source)
|
||||
->condition('lt.language', $langcode)
|
||||
->execute()
|
||||
->fetchField();
|
||||
$db_translation = $db_translation == FALSE ? '' : $db_translation;
|
||||
$this->assertEquals($translation, $db_translation, $message ?: "Correct translation of $source ($langcode)");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Tests\Traits\Core\CronRunTrait;
|
||||
use Drupal\locale\Hook\LocaleHooks;
|
||||
|
||||
/**
|
||||
* Tests for using cron to update project interface translations.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleUpdateCronTest extends LocaleUpdateBase {
|
||||
|
||||
use CronRunTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'translate interface',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->addLanguage('de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests interface translation update using cron.
|
||||
*/
|
||||
public function testUpdateCron(): void {
|
||||
// Set a flag to let the locale_test module replace the project data with a
|
||||
// set of test projects.
|
||||
\Drupal::state()->set('locale.test_projects_alter', TRUE);
|
||||
|
||||
// Setup local and remote translations files.
|
||||
$this->setTranslationFiles();
|
||||
$this->config('locale.settings')->set('translation.default_filename', '%project-%version.%language._po')->save();
|
||||
|
||||
// Update translations using batch to ensure a clean test starting point.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->submitForm([], 'Update translations');
|
||||
|
||||
// Store translation status for comparison.
|
||||
$initial_history = locale_translation_get_file_history();
|
||||
|
||||
// Prepare for test: Simulate new translations being available.
|
||||
// Change the last updated timestamp of a translation file.
|
||||
$contrib_module_two_uri = 'public://local/contrib_module_two-8.x-2.0-beta4.de._po';
|
||||
touch(\Drupal::service('file_system')->realpath($contrib_module_two_uri), \Drupal::time()->getRequestTime());
|
||||
|
||||
// Prepare for test: Simulate that the file has not been checked for a long
|
||||
// time. Set the last_check timestamp to zero.
|
||||
$query = Database::getConnection()->update('locale_file');
|
||||
$query->fields(['last_checked' => 0]);
|
||||
$query->condition('project', 'contrib_module_two');
|
||||
$query->condition('langcode', 'de');
|
||||
$query->execute();
|
||||
|
||||
// Test: Disable cron update and verify that no tasks are added to the
|
||||
// queue.
|
||||
$edit = [
|
||||
'update_interval_days' => 0,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate/settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Execute locale cron tasks to add tasks to the queue.
|
||||
$localeCron = new LocaleHooks();
|
||||
$localeCron->cron();
|
||||
|
||||
// Check whether no tasks are added to the queue.
|
||||
$queue = \Drupal::queue('locale_translation', TRUE);
|
||||
$this->assertEquals(0, $queue->numberOfItems(), 'Queue is empty');
|
||||
|
||||
// Test: Enable cron update and check if update tasks are added to the
|
||||
// queue.
|
||||
// Set cron update to Weekly.
|
||||
$edit = [
|
||||
'update_interval_days' => 7,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate/settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Execute locale cron tasks to add tasks to the queue.
|
||||
$localeCron->cron();
|
||||
|
||||
// Check whether tasks are added to the queue.
|
||||
// Expected tasks:
|
||||
// - locale_translation_batch_version_check
|
||||
// - locale_translation_batch_status_check
|
||||
// - locale_translation_batch_status_finished.
|
||||
$queue = \Drupal::queue('locale_translation', TRUE);
|
||||
$this->assertEquals(3, $queue->numberOfItems(), 'Queue holds tasks for one project.');
|
||||
$item = $queue->claimItem();
|
||||
$queue->releaseItem($item);
|
||||
$this->assertEquals('contrib_module_two', $item->data[1][0], 'Queue holds tasks for contrib module one.');
|
||||
|
||||
// Test: Run cron for a second time and check if tasks are not added to
|
||||
// the queue twice.
|
||||
$localeCron->cron();
|
||||
|
||||
// Check whether no more tasks are added to the queue.
|
||||
$queue = \Drupal::queue('locale_translation', TRUE);
|
||||
$this->assertEquals(3, $queue->numberOfItems(), 'Queue holds tasks for one project.');
|
||||
|
||||
// Ensure last checked is updated to a greater time than the initial value.
|
||||
sleep(1);
|
||||
// Test: Execute cron and check if tasks are executed correctly.
|
||||
// Run cron to process the tasks in the queue.
|
||||
$this->cronRun();
|
||||
|
||||
drupal_static_reset('locale_translation_get_file_history');
|
||||
$history = locale_translation_get_file_history();
|
||||
$initial = $initial_history['contrib_module_two']['de'];
|
||||
$current = $history['contrib_module_two']['de'];
|
||||
// Verify that the translation of contrib_module_one is imported and
|
||||
// updated.
|
||||
$this->assertGreaterThan($initial->timestamp, $current->timestamp);
|
||||
$this->assertGreaterThan($initial->last_checked, $current->last_checked);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test for proper version fallback in case of a development release.
|
||||
*
|
||||
* @group language
|
||||
*/
|
||||
class LocaleUpdateDevelopmentReleaseTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'locale_test_development_release'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
\Drupal::moduleHandler()->loadInclude('locale', 'inc', 'locale.compare');
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'translate interface',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm(['predefined_langcode' => 'hu'], 'Add language');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::\locale_translation_build_projects
|
||||
*/
|
||||
public function testLocaleUpdateDevelopmentRelease(): void {
|
||||
$projects = locale_translation_build_projects();
|
||||
$this->assertEquals('8.0.x', $projects['drupal']->info['version'], 'The branch of the core dev release.');
|
||||
$this->assertEquals('12.x-10.x', $projects['contrib']->info['version'], 'The branch of the contrib module dev release.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Tests for the user interface of project interface translations.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleUpdateInterfaceTest extends LocaleUpdateBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale_test_translate'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'translate interface',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the user interfaces of the interface translation update system.
|
||||
*
|
||||
* Testing the Available updates summary on the side wide status page and the
|
||||
* Available translation updates page.
|
||||
*/
|
||||
public function testInterface(): void {
|
||||
// No language added.
|
||||
// Check status page and Available translation updates page.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertSession()->pageTextNotContains('Translation update status');
|
||||
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertSession()->pageTextContains("No translatable languages available. Add a language first.");
|
||||
$this->assertSession()->linkByHrefExists(Url::fromRoute('entity.configurable_language.collection')->toString());
|
||||
|
||||
// Add German language.
|
||||
$this->addLanguage('de');
|
||||
|
||||
// Override Drupal core translation status as 'up-to-date'.
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['de']->type = 'current';
|
||||
\Drupal::keyValue('locale.translation_status')->set('drupal', $status['drupal']);
|
||||
|
||||
// One language added, all translations up to date.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertSession()->pageTextContains('Translation update status');
|
||||
$this->assertSession()->pageTextContains('Up to date');
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertSession()->pageTextContains('All translations up to date.');
|
||||
|
||||
// Set locale_test_translate module to have a local translation available.
|
||||
$status = locale_translation_get_status();
|
||||
$status['locale_test_translate']['de']->type = 'local';
|
||||
\Drupal::keyValue('locale.translation_status')->set('locale_test_translate', $status['locale_test_translate']);
|
||||
|
||||
// Check if updates are available for German.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertSession()->pageTextContains('Translation update status');
|
||||
$this->assertSession()->pageTextContains("Updates available for: German. See the Available translation updates page for more information.");
|
||||
$this->assertSession()->linkByHrefExists(Url::fromRoute('locale.translate_status')->toString());
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertSession()->pageTextContains('Updates for: Locale test translate');
|
||||
|
||||
// Set locale_test_translate module to have a dev release and no
|
||||
// translation found.
|
||||
$status = locale_translation_get_status();
|
||||
$status['locale_test_translate']['de']->version = '1.3-dev';
|
||||
$status['locale_test_translate']['de']->type = '';
|
||||
\Drupal::keyValue('locale.translation_status')->set('locale_test_translate', $status['locale_test_translate']);
|
||||
|
||||
// Check if no updates were found.
|
||||
$this->drupalGet('admin/reports/status');
|
||||
$this->assertSession()->pageTextContains('Translation update status');
|
||||
$this->assertSession()->pageTextContains("Missing translations for: German. See the Available translation updates page for more information.");
|
||||
$this->assertSession()->linkByHrefExists(Url::fromRoute('locale.translate_status')->toString());
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertSession()->pageTextContains('Missing translations for one project');
|
||||
$this->assertSession()->pageTextContains('Locale test translate (1.3-dev). File not found at core/modules/locale/tests/test.de.po');
|
||||
|
||||
// Override Drupal core translation status as 'no translations found'.
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['de']->type = '';
|
||||
$status['drupal']['de']->timestamp = 0;
|
||||
$status['drupal']['de']->version = '8.1.1';
|
||||
\Drupal::keyValue('locale.translation_status')->set('drupal', $status['drupal']);
|
||||
|
||||
// Check if Drupal core is not translated.
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertSession()->pageTextContains('Missing translations for 2 projects');
|
||||
$this->assertSession()->pageTextContains('Drupal core (8.1.1).');
|
||||
|
||||
// Override Drupal core translation status as 'translations available'.
|
||||
$status = locale_translation_get_status();
|
||||
$status['drupal']['de']->type = 'local';
|
||||
$status['drupal']['de']->files['local']->timestamp = \Drupal::time()->getRequestTime();
|
||||
$status['drupal']['de']->files['local']->info['version'] = '8.1.1';
|
||||
\Drupal::keyValue('locale.translation_status')->set('drupal', $status['drupal']);
|
||||
|
||||
// Check if translations are available for Drupal core.
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertSession()->pageTextContains('Updates for: Drupal core');
|
||||
$this->assertSession()->pageTextContains('Drupal core (' . $this->container->get('date.formatter')->format(\Drupal::time()->getRequestTime(), 'html_date') . ')');
|
||||
$this->assertSession()->buttonExists('Update translations');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,489 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
// cspell:ignore extraday lundi
|
||||
|
||||
/**
|
||||
* Tests for updating the interface translations of projects.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleUpdateTest extends LocaleUpdateBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
$module_handler->loadInclude('locale', 'inc', 'locale.compare');
|
||||
$module_handler->loadInclude('locale', 'inc', 'locale.fetch');
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'translate interface',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
// We use German as test language. This language must match the translation
|
||||
// file that come with the locale_test module (test.de.po) and can therefore
|
||||
// not be chosen randomly.
|
||||
$this->addLanguage('de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if local or remote translation sources are detected.
|
||||
*
|
||||
* The translation status process by default checks the status of the
|
||||
* installed projects. For testing purpose a predefined set of modules with
|
||||
* fixed file names and release versions is used. This custom project
|
||||
* definition is applied using a hook_locale_translation_projects_alter
|
||||
* implementation in the locale_test module.
|
||||
*
|
||||
* This test generates a set of local and remote translation files in their
|
||||
* respective local and remote translation directory. The test checks whether
|
||||
* the most recent files are selected in the different check scenarios: check
|
||||
* for local files only, check for both local and remote files.
|
||||
*/
|
||||
public function testUpdateCheckStatus(): void {
|
||||
// Case when contributed modules are absent.
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->assertSession()->pageTextContains('Missing translations for one project');
|
||||
|
||||
$config = $this->config('locale.settings');
|
||||
// Set a flag to let the locale_test module replace the project data with a
|
||||
// set of test projects.
|
||||
\Drupal::state()->set('locale.test_projects_alter', TRUE);
|
||||
|
||||
// Create local and remote translations files.
|
||||
$this->setTranslationFiles();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po')->save();
|
||||
|
||||
// Set the test conditions.
|
||||
$edit = [
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_LOCAL,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate/settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Get status of translation sources at local file system.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$result = locale_translation_get_status();
|
||||
$this->assertEquals(LOCALE_TRANSLATION_LOCAL, $result['contrib_module_one']['de']->type, 'Translation of contrib_module_one found');
|
||||
$this->assertEquals($this->timestampOld, $result['contrib_module_one']['de']->timestamp, 'Translation timestamp found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_LOCAL, $result['contrib_module_two']['de']->type, 'Translation of contrib_module_two found');
|
||||
$this->assertEquals($this->timestampNew, $result['contrib_module_two']['de']->timestamp, 'Translation timestamp found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_LOCAL, $result['locale_test']['de']->type, 'Translation of locale_test found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_LOCAL, $result['custom_module_one']['de']->type, 'Translation of custom_module_one found');
|
||||
|
||||
// Set the test conditions.
|
||||
$edit = [
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate/settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Get status of translation sources at both local and remote locations.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$result = locale_translation_get_status();
|
||||
$this->assertEquals(LOCALE_TRANSLATION_REMOTE, $result['contrib_module_one']['de']->type, 'Translation of contrib_module_one found');
|
||||
$this->assertEquals($this->timestampNew, $result['contrib_module_one']['de']->timestamp, 'Translation timestamp found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_LOCAL, $result['contrib_module_two']['de']->type, 'Translation of contrib_module_two found');
|
||||
$this->assertEquals($this->timestampNew, $result['contrib_module_two']['de']->timestamp, 'Translation timestamp found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_LOCAL, $result['contrib_module_three']['de']->type, 'Translation of contrib_module_three found');
|
||||
$this->assertEquals($this->timestampOld, $result['contrib_module_three']['de']->timestamp, 'Translation timestamp found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_LOCAL, $result['locale_test']['de']->type, 'Translation of locale_test found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_LOCAL, $result['custom_module_one']['de']->type, 'Translation of custom_module_one found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation import from remote sources.
|
||||
*
|
||||
* Test conditions:
|
||||
* - Source: remote and local files
|
||||
* - Import overwrite: all existing translations
|
||||
*/
|
||||
public function testUpdateImportSourceRemote(): void {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// Build the test environment.
|
||||
$this->setTranslationFiles();
|
||||
$this->setCurrentTranslations();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po');
|
||||
|
||||
// Set the update conditions for this test.
|
||||
$edit = [
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
|
||||
'overwrite' => LOCALE_TRANSLATION_OVERWRITE_ALL,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate/settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Get the translation status.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
|
||||
// Check the status on the Available translation status page.
|
||||
$this->assertSession()->responseContains('<label for="edit-langcodes-de" class="visually-hidden">Update German</label>');
|
||||
$this->assertSession()->pageTextContains('Updates for: Contributed module one, Contributed module two, Custom module one, Locale test');
|
||||
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
|
||||
$date_formatter = $this->container->get('date.formatter');
|
||||
$this->assertSession()->pageTextContains('Contributed module one (' . $date_formatter->format($this->timestampNew, 'html_date') . ')');
|
||||
$this->assertSession()->pageTextContains('Contributed module two (' . $date_formatter->format($this->timestampNew, 'html_date') . ')');
|
||||
|
||||
// Execute the translation update.
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->submitForm([], 'Update translations');
|
||||
|
||||
// Check if the translation has been updated, using the status cache.
|
||||
$status = locale_translation_get_status();
|
||||
$this->assertEquals(LOCALE_TRANSLATION_CURRENT, $status['contrib_module_one']['de']->type, 'Translation of contrib_module_one found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_CURRENT, $status['contrib_module_two']['de']->type, 'Translation of contrib_module_two found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_CURRENT, $status['contrib_module_three']['de']->type, 'Translation of contrib_module_three found');
|
||||
|
||||
// Check the new translation status.
|
||||
// The static cache needs to be flushed first to get the most recent data
|
||||
// from the database. The function was called earlier during this test.
|
||||
drupal_static_reset('locale_translation_get_file_history');
|
||||
$history = locale_translation_get_file_history();
|
||||
// Verify that the translation of contrib_module_one is imported and
|
||||
// updated.
|
||||
$this->assertGreaterThanOrEqual($this->timestampNow, $history['contrib_module_one']['de']->timestamp);
|
||||
$this->assertGreaterThanOrEqual($this->timestampNow, $history['contrib_module_one']['de']->last_checked);
|
||||
$this->assertEquals($this->timestampNew, $history['contrib_module_two']['de']->timestamp, 'Translation of contrib_module_two is imported');
|
||||
// Verify that the translation of contrib_module_two is updated.
|
||||
$this->assertGreaterThanOrEqual($this->timestampNow, $history['contrib_module_two']['de']->last_checked);
|
||||
$this->assertEquals($this->timestampMedium, $history['contrib_module_three']['de']->timestamp, 'Translation of contrib_module_three is not imported');
|
||||
$this->assertEquals($this->timestampMedium, $history['contrib_module_three']['de']->last_checked, 'Translation of contrib_module_three is not updated');
|
||||
|
||||
// Check whether existing translations have (not) been overwritten.
|
||||
// cSpell:disable
|
||||
$this->assertEquals('Januar_1', $this->t('January', [], ['langcode' => 'de']), 'Translation of January');
|
||||
$this->assertEquals('Februar_2', $this->t('February', [], ['langcode' => 'de']), 'Translation of February');
|
||||
$this->assertEquals('Marz_2', $this->t('March', [], ['langcode' => 'de']), 'Translation of March');
|
||||
$this->assertEquals('April_2', $this->t('April', [], ['langcode' => 'de']), 'Translation of April');
|
||||
$this->assertEquals('Mai_customized', $this->t('May', [], ['langcode' => 'de']), 'Translation of May');
|
||||
$this->assertEquals('Juni', $this->t('June', [], ['langcode' => 'de']), 'Translation of June');
|
||||
$this->assertEquals('Montag', $this->t('Monday', [], ['langcode' => 'de']), 'Translation of Monday');
|
||||
// cSpell:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation import from local sources.
|
||||
*
|
||||
* Test conditions:
|
||||
* - Source: local files only
|
||||
* - Import overwrite: all existing translations
|
||||
*/
|
||||
public function testUpdateImportSourceLocal(): void {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// Build the test environment.
|
||||
$this->setTranslationFiles();
|
||||
$this->setCurrentTranslations();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po');
|
||||
|
||||
// Set the update conditions for this test.
|
||||
$edit = [
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_LOCAL,
|
||||
'overwrite' => LOCALE_TRANSLATION_OVERWRITE_ALL,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate/settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Execute the translation update.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->submitForm([], 'Update translations');
|
||||
|
||||
// Check if the translation has been updated, using the status cache.
|
||||
$status = locale_translation_get_status();
|
||||
$this->assertEquals(LOCALE_TRANSLATION_CURRENT, $status['contrib_module_one']['de']->type, 'Translation of contrib_module_one found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_CURRENT, $status['contrib_module_two']['de']->type, 'Translation of contrib_module_two found');
|
||||
$this->assertEquals(LOCALE_TRANSLATION_CURRENT, $status['contrib_module_three']['de']->type, 'Translation of contrib_module_three found');
|
||||
|
||||
// Check the new translation status.
|
||||
// The static cache needs to be flushed first to get the most recent data
|
||||
// from the database. The function was called earlier during this test.
|
||||
drupal_static_reset('locale_translation_get_file_history');
|
||||
$history = locale_translation_get_file_history();
|
||||
// Verify that the translation of contrib_module_one is imported.
|
||||
$this->assertGreaterThanOrEqual($this->timestampMedium, $history['contrib_module_one']['de']->timestamp);
|
||||
$this->assertEquals($this->timestampMedium, $history['contrib_module_one']['de']->last_checked, 'Translation of contrib_module_one is updated');
|
||||
$this->assertEquals($this->timestampNew, $history['contrib_module_two']['de']->timestamp, 'Translation of contrib_module_two is imported');
|
||||
// Verify that the translation of contrib_module_two is updated.
|
||||
$this->assertGreaterThanOrEqual($this->timestampNow, $history['contrib_module_two']['de']->last_checked);
|
||||
$this->assertEquals($this->timestampMedium, $history['contrib_module_three']['de']->timestamp, 'Translation of contrib_module_three is not imported');
|
||||
$this->assertEquals($this->timestampMedium, $history['contrib_module_three']['de']->last_checked, 'Translation of contrib_module_three is not updated');
|
||||
|
||||
// Check whether existing translations have (not) been overwritten.
|
||||
// cSpell:disable
|
||||
$this->assertEquals('Januar_customized', $this->t('January', [], ['langcode' => 'de']), 'Translation of January');
|
||||
$this->assertEquals('Februar_2', $this->t('February', [], ['langcode' => 'de']), 'Translation of February');
|
||||
$this->assertEquals('Marz_2', $this->t('March', [], ['langcode' => 'de']), 'Translation of March');
|
||||
$this->assertEquals('April_2', $this->t('April', [], ['langcode' => 'de']), 'Translation of April');
|
||||
$this->assertEquals('Mai_customized', $this->t('May', [], ['langcode' => 'de']), 'Translation of May');
|
||||
$this->assertEquals('Juni', $this->t('June', [], ['langcode' => 'de']), 'Translation of June');
|
||||
$this->assertEquals('Montag', $this->t('Monday', [], ['langcode' => 'de']), 'Translation of Monday');
|
||||
// cSpell:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation import and only overwrite non-customized translations.
|
||||
*
|
||||
* Test conditions:
|
||||
* - Source: remote and local files
|
||||
* - Import overwrite: only overwrite non-customized translations
|
||||
*/
|
||||
public function testUpdateImportModeNonCustomized(): void {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// Build the test environment.
|
||||
$this->setTranslationFiles();
|
||||
$this->setCurrentTranslations();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po');
|
||||
|
||||
// Set the test conditions.
|
||||
$edit = [
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
|
||||
'overwrite' => LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate/settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Execute translation update.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->submitForm([], 'Update translations');
|
||||
|
||||
// Check whether existing translations have (not) been overwritten.
|
||||
// cSpell:disable
|
||||
$this->assertEquals('Januar_customized', $this->t('January', [], ['langcode' => 'de']), 'Translation of January');
|
||||
$this->assertEquals('Februar_customized', $this->t('February', [], ['langcode' => 'de']), 'Translation of February');
|
||||
$this->assertEquals('Marz_2', $this->t('March', [], ['langcode' => 'de']), 'Translation of March');
|
||||
$this->assertEquals('April_2', $this->t('April', [], ['langcode' => 'de']), 'Translation of April');
|
||||
$this->assertEquals('Mai_customized', $this->t('May', [], ['langcode' => 'de']), 'Translation of May');
|
||||
$this->assertEquals('Juni', $this->t('June', [], ['langcode' => 'de']), 'Translation of June');
|
||||
$this->assertEquals('Montag', $this->t('Monday', [], ['langcode' => 'de']), 'Translation of Monday');
|
||||
// cSpell:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation import and don't overwrite any translation.
|
||||
*
|
||||
* Test conditions:
|
||||
* - Source: remote and local files
|
||||
* - Import overwrite: don't overwrite any existing translation
|
||||
*/
|
||||
public function testUpdateImportModeNone(): void {
|
||||
$config = $this->config('locale.settings');
|
||||
|
||||
// Build the test environment.
|
||||
$this->setTranslationFiles();
|
||||
$this->setCurrentTranslations();
|
||||
$config->set('translation.default_filename', '%project-%version.%language._po');
|
||||
|
||||
// Set the test conditions.
|
||||
$edit = [
|
||||
'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
|
||||
'overwrite' => LOCALE_TRANSLATION_OVERWRITE_NONE,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate/settings');
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
|
||||
// Execute translation update.
|
||||
$this->drupalGet('admin/reports/translations/check');
|
||||
$this->drupalGet('admin/reports/translations');
|
||||
$this->submitForm([], 'Update translations');
|
||||
|
||||
// Check whether existing translations have (not) been overwritten.
|
||||
// cSpell:disable
|
||||
$this->assertTranslation('January', 'Januar_customized', 'de');
|
||||
$this->assertTranslation('February', 'Februar_customized', 'de');
|
||||
$this->assertTranslation('March', 'Marz', 'de');
|
||||
$this->assertTranslation('April', 'April_2', 'de');
|
||||
$this->assertTranslation('May', 'Mai_customized', 'de');
|
||||
$this->assertTranslation('June', 'Juni', 'de');
|
||||
$this->assertTranslation('Monday', 'Montag', 'de');
|
||||
// cSpell:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests automatic translation import when a module is enabled.
|
||||
*/
|
||||
public function testEnableUninstallModule(): void {
|
||||
// Make the hidden test modules look like a normal custom module.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
|
||||
// Check if there is no translation yet.
|
||||
$this->assertTranslation('Tuesday', '', 'de');
|
||||
|
||||
// Enable a module.
|
||||
$edit = [
|
||||
'modules[locale_test_translate][enable]' => 'locale_test_translate',
|
||||
];
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm($edit, 'Install');
|
||||
|
||||
// Check if translations have been imported.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 7 translations were added, 0 translations were updated and 0 translations were removed.");
|
||||
// cSpell:disable-next-line
|
||||
$this->assertTranslation('Tuesday', 'Dienstag', 'de');
|
||||
|
||||
$edit = [
|
||||
'uninstall[locale_test_translate]' => 1,
|
||||
];
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->submitForm($edit, 'Uninstall');
|
||||
$this->submitForm([], 'Uninstall');
|
||||
|
||||
// Check if the file data is removed from the database.
|
||||
$history = locale_translation_get_file_history();
|
||||
$this->assertFalse(isset($history['locale_test_translate']), 'Project removed from the file history');
|
||||
$projects = locale_translation_get_projects();
|
||||
$this->assertFalse(isset($projects['locale_test_translate']), 'Project removed from the project list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests automatic translation import when a language is added.
|
||||
*
|
||||
* When a language is added, the system will check for translations files of
|
||||
* enabled modules and will import them. When a language is removed the system
|
||||
* will remove all translations of that language from the database.
|
||||
*/
|
||||
public function testEnableLanguage(): void {
|
||||
// Make the hidden test modules look like a normal custom module.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
|
||||
// Enable a module.
|
||||
$edit = [
|
||||
'modules[locale_test_translate][enable]' => 'locale_test_translate',
|
||||
];
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm($edit, 'Install');
|
||||
|
||||
// Check if there is no Dutch translation yet.
|
||||
$this->assertTranslation('Extraday', '', 'nl');
|
||||
// cSpell:disable-next-line
|
||||
$this->assertTranslation('Tuesday', 'Dienstag', 'de');
|
||||
|
||||
// Add a language.
|
||||
$edit = [
|
||||
'predefined_langcode' => 'nl',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add language');
|
||||
|
||||
// Check if the right number of translations are added.
|
||||
$this->assertSession()->pageTextContains("One translation file imported. 8 translations were added, 0 translations were updated and 0 translations were removed.");
|
||||
// cSpell:disable-next-line
|
||||
$this->assertTranslation('Extra day', 'extra dag', 'nl');
|
||||
|
||||
// Check if the language data is added to the database.
|
||||
$connection = Database::getConnection();
|
||||
$result = $connection->select('locale_file', 'lf')
|
||||
->fields('lf', ['project'])
|
||||
->condition('langcode', 'nl')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertNotEmpty($result, 'Files added to file history');
|
||||
|
||||
// Remove a language.
|
||||
$this->drupalGet('admin/config/regional/language/delete/nl');
|
||||
$this->submitForm([], 'Delete');
|
||||
|
||||
// Check if the language data is removed from the database.
|
||||
$result = $connection->select('locale_file', 'lf')
|
||||
->fields('lf', ['project'])
|
||||
->condition('langcode', 'nl')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertFalse($result, 'Files removed from file history');
|
||||
|
||||
// Check that the Dutch translation is gone.
|
||||
$this->assertTranslation('Extra day', '', 'nl');
|
||||
// cSpell:disable-next-line
|
||||
$this->assertTranslation('Tuesday', 'Dienstag', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests automatic translation import when a custom language is added.
|
||||
*/
|
||||
public function testEnableCustomLanguage(): void {
|
||||
// Make the hidden test modules look like a normal custom module.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
|
||||
// Enable a module.
|
||||
$edit = [
|
||||
'modules[locale_test_translate][enable]' => 'locale_test_translate',
|
||||
];
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm($edit, 'Install');
|
||||
|
||||
// Create a custom language with language code 'xx' and a random
|
||||
// name.
|
||||
$langcode = 'xx';
|
||||
$name = $this->randomMachineName(16);
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
|
||||
// Ensure the translation file is automatically imported when the language
|
||||
// was added.
|
||||
$this->assertSession()->pageTextContains('One translation file imported.');
|
||||
$this->assertSession()->pageTextContains('One translation string was skipped because of disallowed or malformed HTML');
|
||||
|
||||
// Ensure the strings were successfully imported.
|
||||
$search = [
|
||||
'string' => 'lundi',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextNotContains('No strings available.');
|
||||
|
||||
// Ensure the multiline string was imported.
|
||||
$search = [
|
||||
'string' => 'Source string for multiline translation',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('Multiline translation string to make sure that import works with it.');
|
||||
|
||||
// Ensure 'Allowed HTML source string' was imported but the translation for
|
||||
// 'Another allowed HTML source string' was not because it contains invalid
|
||||
// HTML.
|
||||
$search = [
|
||||
'string' => 'HTML source string',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'all',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextContains('Allowed HTML source string');
|
||||
$this->assertSession()->pageTextNotContains('Another allowed HTML source string');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Functional;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests Locale update functions.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocalesLocationAddIndexUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles(): void {
|
||||
$this->databaseDumpFiles[] = $this->root . '/core/modules/system/tests/fixtures/update/drupal-10.3.0.filled.standard.php.gz';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale_update_10300().
|
||||
*
|
||||
* @see locale_update_10300
|
||||
*/
|
||||
public function testIndex(): void {
|
||||
$this->assertFalse(\Drupal::database()
|
||||
->schema()
|
||||
->indexExists('locales_location', 'type_name'));
|
||||
|
||||
// Run updates and test them.
|
||||
$this->runUpdates();
|
||||
|
||||
$this->assertTrue(\Drupal::database()
|
||||
->schema()
|
||||
->indexExists('locales_location', 'type_name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale_update_10300().
|
||||
*
|
||||
* @see locale_update_10300
|
||||
*/
|
||||
public function testExistingIndex(): void {
|
||||
$spec = [];
|
||||
$spec['locales_location'] = [
|
||||
'description' => 'Location information for source strings.',
|
||||
'fields' => [
|
||||
'lid' => [
|
||||
'type' => 'serial',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Unique identifier of this location.',
|
||||
],
|
||||
'sid' => [
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Unique identifier of this string.',
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 50,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The location type (file, config, path, etc).',
|
||||
],
|
||||
'name' => [
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'Type dependent location information (file name, path, etc).',
|
||||
],
|
||||
'version' => [
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 20,
|
||||
'not null' => TRUE,
|
||||
'default' => 'none',
|
||||
'description' => 'Version of Drupal where the location was found.',
|
||||
],
|
||||
],
|
||||
'primary key' => ['lid'],
|
||||
'foreign keys' => [
|
||||
'locales_source' => [
|
||||
'table' => 'locales_source',
|
||||
'columns' => ['sid' => 'lid'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'string_type' => ['sid', 'type'],
|
||||
'type_name' => ['type', 'name'],
|
||||
],
|
||||
];
|
||||
\Drupal::database()->schema()->addIndex('locales_location', 'type_name', ['type', 'name', 'sid'], $spec['locales_location']);
|
||||
|
||||
// Run updates and test them.
|
||||
$this->runUpdates();
|
||||
|
||||
// Ensure the update runs successfully even if an index existed prior to
|
||||
// the update.
|
||||
$schema = \Drupal::database()->schema();
|
||||
$this->assertTrue($schema->indexExists('locales_location', 'type_name'));
|
||||
}
|
||||
|
||||
}
|
||||
49
web/core/modules/locale/tests/src/Kernel/LocaleBatchTest.php
Normal file
49
web/core/modules/locale/tests/src/Kernel/LocaleBatchTest.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests locale batches.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleBatchTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'locale',
|
||||
'system',
|
||||
'language',
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks that the import batch finishes if the translation has already been imported.
|
||||
*/
|
||||
public function testBuildProjects(): void {
|
||||
$this->installConfig(['locale']);
|
||||
$this->installSchema('locale', ['locale_file']);
|
||||
$this->container->get('module_handler')->loadInclude('locale', 'batch.inc');
|
||||
|
||||
\Drupal::database()->insert('locale_file')
|
||||
->fields([
|
||||
'project' => 'drupal',
|
||||
'langcode' => 'en',
|
||||
'filename' => 'drupal.po',
|
||||
'version' => \Drupal::VERSION,
|
||||
'timestamp' => time(),
|
||||
])
|
||||
->execute();
|
||||
|
||||
$context = [];
|
||||
locale_translation_batch_fetch_import('drupal', 'en', [], $context);
|
||||
$this->assertEquals(1, $context['finished']);
|
||||
$this->assertEquals('Ignoring already imported translation for drupal.', $context['message']);
|
||||
}
|
||||
|
||||
}
|
||||
66
web/core/modules/locale/tests/src/Kernel/LocaleBuildTest.php
Normal file
66
web/core/modules/locale/tests/src/Kernel/LocaleBuildTest.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests building the translatable project information.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleBuildTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'locale',
|
||||
'locale_test',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks if a list of translatable projects gets built.
|
||||
*/
|
||||
public function testBuildProjects(): void {
|
||||
$this->container->get('module_handler')->loadInclude('locale', 'compare.inc');
|
||||
/** @var \Drupal\Core\Extension\ExtensionList $module_list */
|
||||
$module_list = \Drupal::service('extension.list.module');
|
||||
|
||||
// Make the test modules look like a normal custom module. I.e. make the
|
||||
// modules not hidden. locale_test_system_info_alter() modifies the project
|
||||
// info of the locale_test and locale_test_translate modules.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
|
||||
// Confirm the project name and core value before the module is altered.
|
||||
$projects = locale_translation_build_projects();
|
||||
$this->assertSame('locale_test', $projects['locale_test']->name);
|
||||
$this->assertSame('all', $projects['locale_test']->core);
|
||||
|
||||
$projects['locale_test']->langcode = 'de';
|
||||
$this->assertSame('/all/locale_test/locale_test-1.2.de.po', locale_translation_build_server_pattern($projects['locale_test'], '/%core/%project/%project-%version.%language.po'));
|
||||
|
||||
// Alter both the name and core value of the project.
|
||||
\Drupal::state()->set('locale.test_system_info_alter_name_core', TRUE);
|
||||
drupal_static_reset('locale_translation_project_list');
|
||||
$module_list->reset();
|
||||
|
||||
// Confirm the name and core value are changed in $module->info.
|
||||
$module = $module_list->get('locale_test');
|
||||
$this->assertSame('locale_test_alter', $module->info['name']);
|
||||
$this->assertSame('8.6.7', $module->info['core']);
|
||||
$this->assertSame('locale_test', $module->getName());
|
||||
|
||||
// Confirm the name and core value are not changed in the project.
|
||||
$projects = locale_translation_build_projects();
|
||||
$this->assertSame('locale_test', $projects['locale_test']->name);
|
||||
$this->assertSame('all', $projects['locale_test']->core);
|
||||
|
||||
$projects['locale_test']->langcode = 'de';
|
||||
$this->assertSame('/all/locale_test/locale_test-1.2.de.po', locale_translation_build_server_pattern($projects['locale_test'], '/%core/%project/%project-%version.%language.po'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\block\Entity\Block;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the locale config manager operates correctly.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigManagerTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* A list of modules to install for this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'language',
|
||||
'locale',
|
||||
'locale_test',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* This test creates simple config on the fly breaking schema checking.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $strictConfigSchema = FALSE;
|
||||
|
||||
/**
|
||||
* Tests hasTranslation().
|
||||
*/
|
||||
public function testHasTranslation(): void {
|
||||
$this->installSchema('locale', ['locales_location', 'locales_source', 'locales_target']);
|
||||
$this->installConfig(['locale_test']);
|
||||
$locale_config_manager = \Drupal::service('locale.config_manager');
|
||||
|
||||
$language = ConfigurableLanguage::createFromLangcode('de');
|
||||
$language->save();
|
||||
$result = $locale_config_manager->hasTranslation('locale_test.no_translation', $language->getId());
|
||||
$this->assertFalse($result, 'There is no translation for locale_test.no_translation configuration.');
|
||||
|
||||
$result = $locale_config_manager->hasTranslation('locale_test.translation', $language->getId());
|
||||
$this->assertTrue($result, 'There is a translation for locale_test.translation configuration.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getStringTranslation().
|
||||
*/
|
||||
public function testGetStringTranslation(): void {
|
||||
$this->installSchema('locale', ['locales_location', 'locales_source', 'locales_target']);
|
||||
$this->installConfig(['locale_test']);
|
||||
|
||||
$locale_config_manager = \Drupal::service('locale.config_manager');
|
||||
|
||||
$language = ConfigurableLanguage::createFromLangcode('de');
|
||||
$language->save();
|
||||
|
||||
$translation_before = $locale_config_manager->getStringTranslation('locale_test.no_translation', $language->getId(), 'Test', '');
|
||||
$this->assertTrue($translation_before->isNew());
|
||||
$translation_before->setString('translation')->save();
|
||||
|
||||
$translation_after = $locale_config_manager->getStringTranslation('locale_test.no_translation', $language->getId(), 'Test', '');
|
||||
$this->assertFalse($translation_after->isNew());
|
||||
$translation_after->setString('updated_translation')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getDefaultConfigLangcode().
|
||||
*/
|
||||
public function testGetDefaultConfigLangcode(): void {
|
||||
// Install the Language module's configuration so we can use the
|
||||
// module_installer service.
|
||||
$this->installConfig(['language']);
|
||||
$this->assertNull(\Drupal::service('locale.config_manager')->getDefaultConfigLangcode('locale_test_translate.settings'), 'Before installing a module the locale config manager can not access the shipped configuration.');
|
||||
\Drupal::service('module_installer')->install(['locale_test_translate']);
|
||||
$this->assertEquals('en', \Drupal::service('locale.config_manager')->getDefaultConfigLangcode('locale_test_translate.settings'), 'After installing a module the locale config manager can get the shipped configuration langcode.');
|
||||
|
||||
$simple_config = \Drupal::configFactory()->getEditable('locale_test_translate.simple_config_extra');
|
||||
$simple_config->set('foo', 'bar')->save();
|
||||
$this->assertNull(\Drupal::service('locale.config_manager')->getDefaultConfigLangcode($simple_config->getName()), 'Simple config created through the API is not treated as shipped configuration.');
|
||||
|
||||
$block = Block::create([
|
||||
'id' => 'test_default_config',
|
||||
'theme' => 'stark',
|
||||
'status' => TRUE,
|
||||
'region' => 'content',
|
||||
'plugin' => 'local_tasks_block',
|
||||
'settings' => [
|
||||
'id' => 'local_tasks_block',
|
||||
'label' => $this->randomMachineName(),
|
||||
'provider' => 'core',
|
||||
'label_display' => FALSE,
|
||||
'primary' => TRUE,
|
||||
'secondary' => TRUE,
|
||||
],
|
||||
]);
|
||||
$block->save();
|
||||
|
||||
// Install the theme after creating the block as installing the theme will
|
||||
// install the block provided by the locale_test module.
|
||||
\Drupal::service('theme_installer')->install(['stark']);
|
||||
|
||||
// The test_default_config block provided by the locale_test module will not
|
||||
// be installed because a block with the same ID already exists.
|
||||
$this->installConfig(['locale_test']);
|
||||
$this->assertNull(\Drupal::service('locale.config_manager')->getDefaultConfigLangcode('block.block.test_default_config'), 'The block.block.test_default_config is not shipped configuration.');
|
||||
// Delete the block so we can install the one provided by the locale_test
|
||||
// module.
|
||||
$block->delete();
|
||||
$this->installConfig(['locale_test']);
|
||||
$this->assertEquals('en', \Drupal::service('locale.config_manager')->getDefaultConfigLangcode('block.block.test_default_config'), 'The block.block.test_default_config is shipped configuration.');
|
||||
|
||||
// Test the special case for configurable_language config entities.
|
||||
$fr_language = ConfigurableLanguage::createFromLangcode('fr');
|
||||
$fr_language->save();
|
||||
$this->assertEquals('en', \Drupal::service('locale.config_manager')->getDefaultConfigLangcode('language.entity.fr'), 'The language.entity.fr is treated as shipped configuration because it is a configurable_language config entity and in the standard language list.');
|
||||
$custom_language = ConfigurableLanguage::createFromLangcode('custom');
|
||||
$custom_language->save();
|
||||
$this->assertNull(\Drupal::service('locale.config_manager')->getDefaultConfigLangcode('language.entity.custom'), 'The language.entity.custom is not shipped configuration because it is not in the standard language list.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests default configuration handling with a foreign default language.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigSubscriberForeignTest extends LocaleConfigSubscriberTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container): void {
|
||||
parent::register($container);
|
||||
|
||||
$language = Language::$defaultValues;
|
||||
$language['id'] = 'hu';
|
||||
$language['name'] = 'Hungarian';
|
||||
$container->setParameter('language.default_values', $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpLanguages(): void {
|
||||
parent::setUpLanguages();
|
||||
ConfigurableLanguage::createFromLangcode('hu')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpLocale(): void {
|
||||
parent::setUpLocale();
|
||||
$this->setUpTranslation('locale_test.translation', 'test', 'English test', 'Hungarian test', 'hu', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the language of default configuration was updated.
|
||||
*/
|
||||
public function testDefaultConfigLanguage(): void {
|
||||
$this->assertEquals('hu', $this->configFactory->getEditable('locale_test.no_translation')->get('langcode'));
|
||||
$this->assertEquals('hu', $this->configFactory->getEditable('locale_test.translation')->get('langcode'));
|
||||
$this->assertEquals('Hungarian test', $this->configFactory->getEditable('locale_test.translation')->get('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating translations of shipped configuration.
|
||||
*/
|
||||
public function testCreateActiveTranslation(): void {
|
||||
$config_name = 'locale_test.no_translation';
|
||||
$this->saveLanguageActive($config_name, 'test', 'Test (Hungarian)', 'hu');
|
||||
$this->assertTranslation($config_name, 'Test (Hungarian)', 'hu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests importing community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleCreateActiveTranslation(): void {
|
||||
$config_name = 'locale_test.no_translation';
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'Test', 'Test (Hungarian)', 'hu', TRUE);
|
||||
$this->assertTranslation($config_name, 'Test (Hungarian)', 'hu', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating translations of shipped configuration.
|
||||
*/
|
||||
public function testUpdateActiveTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->saveLanguageActive($config_name, 'test', 'Updated Hungarian test', 'hu');
|
||||
$this->assertTranslation($config_name, 'Updated Hungarian test', 'hu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleUpdateActiveTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'English test', 'Updated Hungarian test', 'hu', TRUE);
|
||||
$this->assertTranslation($config_name, 'Updated Hungarian test', 'hu', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting a translation override.
|
||||
*/
|
||||
public function testDeleteTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->deleteLanguageOverride($config_name, 'test', 'English test', 'de');
|
||||
// The German translation in this case will be forced to the Hungarian
|
||||
// source so its not overwritten with locale data later.
|
||||
$this->assertTranslation($config_name, 'Hungarian test', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting translations of shipped configuration.
|
||||
*/
|
||||
public function testDeleteActiveTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->configFactory->getEditable($config_name)->delete();
|
||||
// Deleting active configuration should not change the locale translation.
|
||||
$this->assertTranslation($config_name, 'Hungarian test', 'hu', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleDeleteActiveTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
$this->deleteLocaleTranslationData($config_name, 'test', 'English test', 'hu');
|
||||
// Deleting the locale translation should not change active config.
|
||||
$this->assertEquals('Hungarian test', $this->configFactory->getEditable($config_name)->get('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that adding English creates a translation override.
|
||||
*/
|
||||
public function testEnglish(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
ConfigurableLanguage::createFromLangcode('en')->save();
|
||||
// Adding a language on the UI would normally call updateConfigTranslations.
|
||||
$this->localeConfigManager->updateConfigTranslations([$config_name], ['en']);
|
||||
$this->assertConfigOverride($config_name, 'test', 'English test', 'en');
|
||||
|
||||
$this->configFactory->getEditable('locale.settings')->set('translate_english', TRUE)->save();
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'English test', 'Updated English test', 'en');
|
||||
$this->assertTranslation($config_name, 'Updated English test', 'en', FALSE);
|
||||
|
||||
$this->saveLanguageOverride($config_name, 'test', 'Updated English', 'en');
|
||||
$this->assertTranslation($config_name, 'Updated English', 'en');
|
||||
|
||||
$this->deleteLocaleTranslationData($config_name, 'test', 'English test', 'en');
|
||||
$this->assertNoConfigOverride($config_name, 'en');
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a language override.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $value
|
||||
* The configuration value to save.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function saveLanguageActive($config_name, $key, $value, $langcode): void {
|
||||
$this
|
||||
->configFactory
|
||||
->getEditable($config_name)
|
||||
->set($key, $value)
|
||||
->save();
|
||||
$this->assertActiveConfig($config_name, $key, $value, $langcode);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,511 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\locale\StringInterface;
|
||||
use Drupal\locale\TranslationString;
|
||||
|
||||
/**
|
||||
* Tests that shipped configuration translations are updated correctly.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigSubscriberTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'locale', 'system', 'locale_test'];
|
||||
|
||||
/**
|
||||
* The configurable language manager used in this test.
|
||||
*
|
||||
* @var \Drupal\language\ConfigurableLanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The configuration factory used in this test.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The string storage used in this test.
|
||||
*
|
||||
* @var \Drupal\locale\StringStorageInterface
|
||||
*/
|
||||
protected $stringStorage;
|
||||
|
||||
/**
|
||||
* The locale configuration manager used in this test.
|
||||
*
|
||||
* @var \Drupal\locale\LocaleConfigManager
|
||||
*/
|
||||
protected $localeConfigManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpDefaultLanguage();
|
||||
|
||||
$this->installSchema('locale', ['locales_source', 'locales_target', 'locales_location']);
|
||||
|
||||
$this->setupLanguages();
|
||||
|
||||
$this->installConfig(['locale_test']);
|
||||
// Simulate this hook invoked which would happen if in a non-kernel test
|
||||
// or normal environment.
|
||||
// @see locale_modules_installed()
|
||||
// @see locale_system_update()
|
||||
locale_system_set_config_langcodes();
|
||||
$langcodes = array_keys(\Drupal::languageManager()->getLanguages());
|
||||
$locale_config_manager = \Drupal::service('locale.config_manager');
|
||||
$names = $locale_config_manager->getComponentNames();
|
||||
$locale_config_manager->updateConfigTranslations($names, $langcodes);
|
||||
|
||||
$this->configFactory = $this->container->get('config.factory');
|
||||
$this->stringStorage = $this->container->get('locale.storage');
|
||||
$this->localeConfigManager = $this->container->get('locale.config_manager');
|
||||
$this->languageManager = $this->container->get('language_manager');
|
||||
|
||||
$this->setUpLocale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up default language for this test.
|
||||
*/
|
||||
protected function setUpDefaultLanguage(): void {
|
||||
// Keep the default English.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up languages needed for this test.
|
||||
*/
|
||||
protected function setUpLanguages(): void {
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the locale storage strings to be in line with configuration.
|
||||
*/
|
||||
protected function setUpLocale(): void {
|
||||
// Set up the locale database the same way we have in the config samples.
|
||||
$this->setUpNoTranslation('locale_test.no_translation', 'test', 'Test', 'de');
|
||||
$this->setUpTranslation('locale_test.translation', 'test', 'English test', 'German test', 'de');
|
||||
$this->setUpTranslation('locale_test.translation_multiple', 'test', 'English test', 'German test', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating translations of shipped configuration.
|
||||
*/
|
||||
public function testCreateTranslation(): void {
|
||||
$config_name = 'locale_test.no_translation';
|
||||
|
||||
$this->saveLanguageOverride($config_name, 'test', 'Test (German)', 'de');
|
||||
$this->assertTranslation($config_name, 'Test (German)', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating translations configuration with multi value settings.
|
||||
*/
|
||||
public function testCreateTranslationMultiValue(): void {
|
||||
$config_name = 'locale_test.translation_multiple';
|
||||
|
||||
$this->saveLanguageOverride(
|
||||
$config_name,
|
||||
'test_multiple',
|
||||
[
|
||||
'string' => 'String (German)',
|
||||
'another_string' => 'Another string (German)',
|
||||
],
|
||||
'de');
|
||||
$this->saveLanguageOverride(
|
||||
$config_name,
|
||||
'test_after_multiple',
|
||||
[
|
||||
'string' => 'After string (German)',
|
||||
'another_string' => 'After another string (German)',
|
||||
],
|
||||
'de');
|
||||
$strings = $this->stringStorage->getTranslations([
|
||||
'type' => 'configuration',
|
||||
'name' => $config_name,
|
||||
'language' => 'de',
|
||||
'translated' => TRUE,
|
||||
]);
|
||||
$this->assertCount(5, $strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests importing community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleCreateTranslation(): void {
|
||||
$config_name = 'locale_test.no_translation';
|
||||
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'Test', 'Test (German)', 'de');
|
||||
$this->assertTranslation($config_name, 'Test (German)', 'de', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating translations of shipped configuration.
|
||||
*/
|
||||
public function testUpdateTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
|
||||
$this->saveLanguageOverride($config_name, 'test', 'Updated German test', 'de');
|
||||
$this->assertTranslation($config_name, 'Updated German test', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleUpdateTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
|
||||
$this->saveLocaleTranslationData($config_name, 'test', 'English test', 'Updated German test', 'de');
|
||||
$this->assertTranslation($config_name, 'Updated German test', 'de', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting translations of shipped configuration.
|
||||
*/
|
||||
public function testDeleteTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
|
||||
$this->deleteLanguageOverride($config_name, 'test', 'English test', 'de');
|
||||
// Instead of deleting the translation, we need to keep a translation with
|
||||
// the source value and mark it as customized to prevent the deletion being
|
||||
// reverted by importing community translations.
|
||||
$this->assertTranslation($config_name, 'English test', 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting community translations of shipped configuration.
|
||||
*/
|
||||
public function testLocaleDeleteTranslation(): void {
|
||||
$config_name = 'locale_test.translation';
|
||||
|
||||
$this->deleteLocaleTranslationData($config_name, 'test', 'English test', 'de');
|
||||
$this->assertNoTranslation($config_name, 'de');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a configuration string without a translation.
|
||||
*
|
||||
* The actual configuration is already available by installing locale_test
|
||||
* module, as it is done in LocaleConfigSubscriberTest::setUp(). This sets up
|
||||
* the necessary source string and verifies that everything is as expected to
|
||||
* avoid false positives.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source
|
||||
* The source string.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function setUpNoTranslation($config_name, $key, $source, $langcode): void {
|
||||
$this->localeConfigManager->updateConfigTranslations([$config_name], [$langcode]);
|
||||
$this->assertNoConfigOverride($config_name, $key);
|
||||
$this->assertNoTranslation($config_name, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a configuration string with a translation.
|
||||
*
|
||||
* The actual configuration is already available by installing locale_test
|
||||
* module, as it is done in LocaleConfigSubscriberTest::setUp(). This sets up
|
||||
* the necessary source and translation strings and verifies that everything
|
||||
* is as expected to avoid false positives.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source
|
||||
* The source string.
|
||||
* @param string $translation
|
||||
* The translation string.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param bool $is_active
|
||||
* Whether the update will affect the active configuration.
|
||||
*/
|
||||
protected function setUpTranslation($config_name, $key, $source, $translation, $langcode, $is_active = FALSE): void {
|
||||
// Create source and translation strings for the configuration value and add
|
||||
// the configuration name as a location. This would be performed by
|
||||
// locale_translate_batch_import() invoking
|
||||
// LocaleConfigManager::updateConfigTranslations() normally.
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager
|
||||
->getStringTranslation($config_name, $langcode, $source, '')
|
||||
->setString($translation)
|
||||
->setCustomized(FALSE)
|
||||
->save();
|
||||
$this->configFactory->reset($config_name);
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager->updateConfigTranslations([$config_name], [$langcode]);
|
||||
|
||||
if ($is_active) {
|
||||
$this->assertActiveConfig($config_name, $key, $translation, $langcode);
|
||||
}
|
||||
else {
|
||||
$this->assertConfigOverride($config_name, $key, $translation, $langcode);
|
||||
}
|
||||
$this->assertTranslation($config_name, $translation, $langcode, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a language override.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string|array $value
|
||||
* The configuration value to save.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function saveLanguageOverride($config_name, $key, $value, $langcode): void {
|
||||
$translation_override = $this->languageManager
|
||||
->getLanguageConfigOverride($langcode, $config_name);
|
||||
$translation_override
|
||||
->set($key, $value)
|
||||
->save();
|
||||
$this->configFactory->reset($config_name);
|
||||
|
||||
$this->assertConfigOverride($config_name, $key, $value, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves translation data from locale module.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source
|
||||
* The source string.
|
||||
* @param string $translation
|
||||
* The translation string to save.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param bool $is_active
|
||||
* Whether the update will affect the active configuration.
|
||||
*/
|
||||
protected function saveLocaleTranslationData($config_name, $key, $source, $translation, $langcode, $is_active = FALSE): void {
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager
|
||||
->getStringTranslation($config_name, $langcode, $source, '')
|
||||
->setString($translation)
|
||||
->save();
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager->updateConfigTranslations([$config_name], [$langcode]);
|
||||
$this->configFactory->reset($config_name);
|
||||
|
||||
if ($is_active) {
|
||||
$this->assertActiveConfig($config_name, $key, $translation, $langcode);
|
||||
}
|
||||
else {
|
||||
$this->assertConfigOverride($config_name, $key, $translation, $langcode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a language override.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source_value
|
||||
* The source configuration value to verify the correct value is returned
|
||||
* from the configuration factory after the deletion.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function deleteLanguageOverride($config_name, $key, $source_value, $langcode): void {
|
||||
$translation_override = $this->languageManager
|
||||
->getLanguageConfigOverride($langcode, $config_name);
|
||||
$translation_override
|
||||
->clear($key)
|
||||
->save();
|
||||
$this->configFactory->reset($config_name);
|
||||
|
||||
$this->assertNoConfigOverride($config_name, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes translation data from locale module.
|
||||
*
|
||||
* This will invoke LocaleConfigSubscriber through the event dispatcher. To
|
||||
* make sure the configuration was persisted correctly, the configuration
|
||||
* value is checked. Because LocaleConfigSubscriber temporarily disables the
|
||||
* override state of the configuration factory we check that the correct value
|
||||
* is restored afterwards.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $source_value
|
||||
* The source configuration value to verify the correct value is returned
|
||||
* from the configuration factory after the deletion.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
protected function deleteLocaleTranslationData($config_name, $key, $source_value, $langcode): void {
|
||||
$this->localeConfigManager
|
||||
->getStringTranslation($config_name, $langcode, $source_value, '')
|
||||
->delete();
|
||||
$this->localeConfigManager->reset();
|
||||
$this->localeConfigManager->updateConfigTranslations([$config_name], [$langcode]);
|
||||
$this->configFactory->reset($config_name);
|
||||
|
||||
$this->assertNoConfigOverride($config_name, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures configuration override is not present anymore.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertNoConfigOverride(string $config_name, string $langcode): void {
|
||||
$config_langcode = $this->configFactory->getEditable($config_name)->get('langcode');
|
||||
$override = $this->languageManager->getLanguageConfigOverride($langcode, $config_name);
|
||||
$this->assertNotEquals($langcode, $config_langcode);
|
||||
$this->assertTrue($override->isNew());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures configuration was saved correctly.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string|array $value
|
||||
* The configuration value.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertConfigOverride(string $config_name, string $key, $value, string $langcode): void {
|
||||
$config_langcode = $this->configFactory->getEditable($config_name)->get('langcode');
|
||||
$override = $this->languageManager->getLanguageConfigOverride($langcode, $config_name);
|
||||
$this->assertNotEquals($langcode, $config_langcode);
|
||||
$this->assertEquals($value, $override->get($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures configuration was saved correctly.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $key
|
||||
* The configuration key.
|
||||
* @param string $value
|
||||
* The configuration value.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertActiveConfig(string $config_name, string $key, string $value, string $langcode): void {
|
||||
$config = $this->configFactory->getEditable($config_name);
|
||||
$this->assertEquals($langcode, $config->get('langcode'));
|
||||
$this->assertSame($value, $config->get($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures no translation exists.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertNoTranslation(string $config_name, string $langcode): void {
|
||||
$strings = $this->stringStorage->getTranslations([
|
||||
'type' => 'configuration',
|
||||
'name' => $config_name,
|
||||
'language' => $langcode,
|
||||
'translated' => TRUE,
|
||||
]);
|
||||
$this->assertSame([], $strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts if a specific translation exists and its customization status.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The configuration name.
|
||||
* @param string|array $translation
|
||||
* The translation.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param bool $customized
|
||||
* (optional) Asserts if the translation is customized or not.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertTranslation(string $config_name, $translation, string $langcode, bool $customized = TRUE): void {
|
||||
// Make sure a string exists.
|
||||
$strings = $this->stringStorage->getTranslations([
|
||||
'type' => 'configuration',
|
||||
'name' => $config_name,
|
||||
'language' => $langcode,
|
||||
'translated' => TRUE,
|
||||
]);
|
||||
$this->assertCount(1, $strings);
|
||||
$string = reset($strings);
|
||||
$this->assertInstanceOf(StringInterface::class, $string);
|
||||
/** @var \Drupal\locale\StringInterface $string */
|
||||
$this->assertSame($translation, $string->getString());
|
||||
$this->assertTrue($string->isTranslation());
|
||||
$this->assertInstanceOf(TranslationString::class, $string);
|
||||
/** @var \Drupal\locale\TranslationString $string */
|
||||
// Make sure the string is marked as customized so that it does not get
|
||||
// overridden when the string translations are updated.
|
||||
$this->assertEquals($customized, (bool) $string->customized);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the configurable language manager and locale operate correctly.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleConfigurableLanguageManagerTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* A list of modules to install for this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = ['language', 'locale'];
|
||||
|
||||
/**
|
||||
* Tests retrieving languages from the language manager.
|
||||
*/
|
||||
public function testGetLanguages(): void {
|
||||
$this->installSchema('locale', ['locales_source', 'locales_target', 'locales_location']);
|
||||
$default_language = ConfigurableLanguage::create([
|
||||
'label' => $this->randomMachineName(),
|
||||
'id' => 'default',
|
||||
'weight' => 0,
|
||||
]);
|
||||
$default_language->save();
|
||||
|
||||
// Set new default language.
|
||||
\Drupal::service('language.default')->set($default_language);
|
||||
\Drupal::service('string_translation')->setDefaultLangcode($default_language->getId());
|
||||
|
||||
$languages = \Drupal::service('language_manager')->getLanguages(LanguageInterface::STATE_ALL);
|
||||
$this->assertEquals(['default', 'und', 'zxx'], array_keys($languages));
|
||||
|
||||
$configurableLanguage = ConfigurableLanguage::create([
|
||||
'label' => $this->randomMachineName(),
|
||||
'id' => 'test',
|
||||
'weight' => 1,
|
||||
]);
|
||||
// Simulate a configuration sync by setting the flag otherwise the locked
|
||||
// language weights would be updated whilst saving.
|
||||
// @see \Drupal\language\Entity\ConfigurableLanguage::postSave()
|
||||
$configurableLanguage->setSyncing(TRUE)->save();
|
||||
|
||||
$languages = \Drupal::service('language_manager')->getLanguages(LanguageInterface::STATE_ALL);
|
||||
$this->assertEquals(['default', 'test', 'und', 'zxx'], array_keys($languages));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\Core\Config\NullStorage;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\locale\LocaleDefaultConfigStorage;
|
||||
|
||||
/**
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleDefaultConfigStorageTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'locale',
|
||||
'locale_test',
|
||||
'locale_test_translate',
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests retrieval of component names from locale default config storage.
|
||||
*/
|
||||
public function testGetComponentNames(): void {
|
||||
$storage = new LocaleDefaultConfigStorage(
|
||||
new NullStorage(),
|
||||
\Drupal::languageManager(),
|
||||
'testing',
|
||||
);
|
||||
|
||||
$expected = [
|
||||
'locale_test.no_translation',
|
||||
'locale_test.translation',
|
||||
'locale_test.translation_multiple',
|
||||
'locale_test_translate.settings',
|
||||
'block.block.test_default_config',
|
||||
];
|
||||
$actual = $storage->getComponentNames(
|
||||
'module',
|
||||
[
|
||||
\Drupal::moduleHandler()->getModule('locale_test'),
|
||||
\Drupal::moduleHandler()->getModule('locale_test_translate'),
|
||||
],
|
||||
);
|
||||
$this->assertSame($expected, $actual);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests locale translation safe string handling.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleStringIsSafeTest extends KernelTestBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'locale_test'];
|
||||
|
||||
/**
|
||||
* Tests for locale_string_is_safe().
|
||||
*/
|
||||
public function testLocaleStringIsSafe(): void {
|
||||
// Check a translatable string without HTML.
|
||||
$string = 'Hello world!';
|
||||
$result = locale_string_is_safe($string);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Check a translatable string which includes trustable HTML.
|
||||
$string = 'Hello <strong>world</strong>!';
|
||||
$result = locale_string_is_safe($string);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Check an untranslatable string which includes unsafe HTML (according to
|
||||
// the locale_string_is_safe() function definition).
|
||||
$string = 'Hello <img src="world.png" alt="world" />!';
|
||||
$result = locale_string_is_safe($string);
|
||||
$this->assertFalse($result);
|
||||
|
||||
// Check a translatable string which includes a token in an href attribute.
|
||||
$string = 'Hi <a href="[current-user:url]">user</a>';
|
||||
$result = locale_string_is_safe($string);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Check a translatable string which includes a wbr tag.
|
||||
$string = 'DrupalLocaleModule<wbr>Test<wbr>Example';
|
||||
$result = locale_string_is_safe($string);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a translated and tokenized string is properly escaped by Twig.
|
||||
*
|
||||
* In each assert* call we add a new line at the expected result to match the
|
||||
* newline at the end of the template file.
|
||||
*/
|
||||
public function testLocalizedTokenizedString(): void {
|
||||
$tests_to_do = [
|
||||
1 => [
|
||||
'original' => 'Go to the <a href="[locale_test:security_test1]">frontpage</a>',
|
||||
'replaced' => 'Go to the <a href="javascript:alert(&#039;Hello!&#039;);">frontpage</a>',
|
||||
],
|
||||
2 => [
|
||||
'original' => 'Hello <strong>[locale_test:security_test2]</strong>!',
|
||||
'replaced' => 'Hello <strong>&lt;script&gt;alert(&#039;Hello!&#039;);&lt;/script&gt;</strong>!',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($tests_to_do as $i => $test) {
|
||||
$original_string = $test['original'];
|
||||
$rendered_original_string = \Drupal::theme()->render('locale_test_tokenized', ['content' => $original_string]);
|
||||
// Twig assumes that strings are unsafe so it escapes them, and so the
|
||||
// original and the rendered version should be different.
|
||||
$this->assertNotEquals(
|
||||
$original_string . "\n",
|
||||
$rendered_original_string,
|
||||
'Security test ' . $i . ' before translation'
|
||||
);
|
||||
|
||||
// Pass the original string to the t() function to get it marked as safe.
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$safe_string = $this->t($original_string);
|
||||
$rendered_safe_string = \Drupal::theme()->render('locale_test_tokenized', ['content' => $safe_string]);
|
||||
// t() function always marks the string as safe so it won't be escaped,
|
||||
// and should be the same as the original.
|
||||
$this->assertSame($original_string . "\n", (string) $rendered_safe_string, 'Security test ' . $i . ' after translation before token replacement');
|
||||
|
||||
// Replace tokens in the safe string to inject it with dangerous content.
|
||||
// @see locale_test_tokens().
|
||||
$unsafe_string = \Drupal::token()->replace($safe_string);
|
||||
$rendered_unsafe_string = \Drupal::theme()->render('locale_test_tokenized', ['content' => $unsafe_string]);
|
||||
// Token replacement changes the string so it is not marked as safe
|
||||
// anymore. Check it is escaped the way we expect.
|
||||
$this->assertEquals($test['replaced'] . "\n", $rendered_unsafe_string, 'Security test ' . $i . ' after translation after token replacement');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
284
web/core/modules/locale/tests/src/Kernel/LocaleStringTest.php
Normal file
284
web/core/modules/locale/tests/src/Kernel/LocaleStringTest.php
Normal file
@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\locale\StringInterface;
|
||||
|
||||
/**
|
||||
* Tests the locale string storage, string objects and data API.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleStringTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'locale',
|
||||
];
|
||||
|
||||
/**
|
||||
* The locale storage.
|
||||
*
|
||||
* @var \Drupal\locale\StringStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Add a default locale storage for all these tests.
|
||||
$this->storage = $this->container->get('locale.storage');
|
||||
// Create two languages: Spanish and German.
|
||||
foreach (['es', 'de'] as $langcode) {
|
||||
ConfigurableLanguage::createFromLangcode($langcode)->save();
|
||||
}
|
||||
$this->installSchema('locale', [
|
||||
'locales_location',
|
||||
'locales_source',
|
||||
'locales_target',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests CRUD API.
|
||||
*/
|
||||
public function testStringCrudApi(): void {
|
||||
// Create source string.
|
||||
$source = $this->buildSourceString()->save();
|
||||
$this->assertNotEmpty($source->lid);
|
||||
|
||||
// Load strings by lid and source.
|
||||
$string1 = $this->storage->findString(['lid' => $source->lid]);
|
||||
$this->assertEquals($source, $string1);
|
||||
$string2 = $this->storage->findString(['source' => $source->source, 'context' => $source->context]);
|
||||
$this->assertEquals($source, $string2);
|
||||
$string3 = $this->storage->findString(['source' => $source->source, 'context' => '']);
|
||||
$this->assertNull($string3);
|
||||
|
||||
// Check version handling and updating.
|
||||
$this->assertEquals('none', $source->version);
|
||||
$string = $this->storage->findTranslation(['lid' => $source->lid]);
|
||||
$this->assertEquals(\Drupal::VERSION, $string->version);
|
||||
|
||||
// Create translation and find it by lid and source.
|
||||
$langcode = 'es';
|
||||
$translation = $this->createTranslation($source, $langcode);
|
||||
$this->assertEquals(LOCALE_NOT_CUSTOMIZED, $translation->customized);
|
||||
$string1 = $this->storage->findTranslation(['language' => $langcode, 'lid' => $source->lid]);
|
||||
$this->assertEquals($translation->translation, $string1->translation);
|
||||
$string2 = $this->storage->findTranslation([
|
||||
'language' => $langcode,
|
||||
'source' => $source->source,
|
||||
'context' => $source->context,
|
||||
]);
|
||||
$this->assertEquals($translation->translation, $string2->translation);
|
||||
$translation
|
||||
->setCustomized()
|
||||
->save();
|
||||
$translation = $this->storage->findTranslation(['language' => $langcode, 'lid' => $source->lid]);
|
||||
$this->assertEquals(LOCALE_CUSTOMIZED, $translation->customized);
|
||||
|
||||
// Delete translation.
|
||||
$translation->delete();
|
||||
$deleted = $this->storage->findTranslation(['language' => $langcode, 'lid' => $source->lid]);
|
||||
$this->assertNull($deleted->translation);
|
||||
|
||||
// Create some translations and then delete string and all of its
|
||||
// translations.
|
||||
$lid = $source->lid;
|
||||
$this->createAllTranslations($source);
|
||||
$search = $this->storage->getTranslations(['lid' => $source->lid]);
|
||||
$this->assertCount(3, $search);
|
||||
|
||||
$source->delete();
|
||||
$string = $this->storage->findString(['lid' => $lid]);
|
||||
$this->assertNull($string);
|
||||
$deleted = $search = $this->storage->getTranslations(['lid' => $lid]);
|
||||
$this->assertEmpty($deleted);
|
||||
|
||||
// Tests that locations of different types and arbitrary lengths can be
|
||||
// added to a source string. Too long locations will be cut off.
|
||||
$source_string = $this->buildSourceString();
|
||||
$source_string->addLocation('javascript', $this->randomString(8));
|
||||
$source_string->addLocation('configuration', $this->randomString(50));
|
||||
$source_string->addLocation('code', $this->randomString(100));
|
||||
$source_string->addLocation('path', $location = $this->randomString(300));
|
||||
$source_string->save();
|
||||
|
||||
$rows = $this->container->get('database')->select('locales_location')
|
||||
->fields('locales_location')
|
||||
->condition('sid', $source_string->lid)
|
||||
->execute()
|
||||
->fetchAllAssoc('type');
|
||||
$this->assertCount(4, $rows);
|
||||
$this->assertEquals(substr($location, 0, 255), $rows['path']->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Search API loading multiple objects.
|
||||
*/
|
||||
public function testStringSearchApi(): void {
|
||||
$language_count = 3;
|
||||
// Strings 1 and 2 will have some common prefix.
|
||||
// Source 1 will have all translations, not customized.
|
||||
// Source 2 will have all translations, customized.
|
||||
// Source 3 will have no translations.
|
||||
$prefix = $this->randomMachineName(100);
|
||||
$source1 = $this->buildSourceString(['source' => $prefix . $this->randomMachineName(100)])->save();
|
||||
$source2 = $this->buildSourceString(['source' => $prefix . $this->randomMachineName(100)])->save();
|
||||
$source3 = $this->buildSourceString()->save();
|
||||
|
||||
// Load all source strings.
|
||||
$strings = $this->storage->getStrings([]);
|
||||
$this->assertCount(3, $strings);
|
||||
// Load all source strings matching a given string.
|
||||
$filter_options['filters'] = ['source' => $prefix];
|
||||
$strings = $this->storage->getStrings([], $filter_options);
|
||||
$this->assertCount(2, $strings);
|
||||
|
||||
// Not customized translations.
|
||||
$translate1 = $this->createAllTranslations($source1);
|
||||
// Customized translations.
|
||||
$this->createAllTranslations($source2, ['customized' => LOCALE_CUSTOMIZED]);
|
||||
// Try quick search function with different field combinations.
|
||||
$langcode = 'es';
|
||||
$found = $this->storage->findTranslation([
|
||||
'language' => $langcode,
|
||||
'source' => $source1->source,
|
||||
'context' => $source1->context,
|
||||
]);
|
||||
$this->assertNotNull($found, 'Translation not found searching by source and context.');
|
||||
$this->assertNotNull($found->language);
|
||||
$this->assertNotNull($found->translation);
|
||||
$this->assertFalse($found->isNew());
|
||||
$this->assertEquals($translate1[$langcode]->translation, $found->translation);
|
||||
// Now try a translation not found.
|
||||
$found = $this->storage->findTranslation([
|
||||
'language' => $langcode,
|
||||
'source' => $source3->source,
|
||||
'context' => $source3->context,
|
||||
]);
|
||||
$this->assertNotNull($found);
|
||||
$this->assertSame($source3->lid, $found->lid);
|
||||
$this->assertNull($found->translation);
|
||||
$this->assertTrue($found->isNew());
|
||||
|
||||
// Load all translations. For next queries we'll be loading only translated
|
||||
// strings.
|
||||
$translations = $this->storage->getTranslations(['translated' => TRUE]);
|
||||
$this->assertCount(2 * $language_count, $translations);
|
||||
|
||||
// Load all customized translations.
|
||||
$translations = $this->storage->getTranslations(['customized' => LOCALE_CUSTOMIZED, 'translated' => TRUE]);
|
||||
$this->assertCount($language_count, $translations);
|
||||
|
||||
// Load all Spanish customized translations.
|
||||
$translations = $this->storage->getTranslations([
|
||||
'language' => 'es',
|
||||
'customized' => LOCALE_CUSTOMIZED,
|
||||
'translated' => TRUE,
|
||||
]);
|
||||
$this->assertCount(1, $translations);
|
||||
|
||||
// Load all source strings without translation (1).
|
||||
$translations = $this->storage->getStrings(['translated' => FALSE]);
|
||||
$this->assertCount(1, $translations);
|
||||
|
||||
// Load Spanish translations using string filter.
|
||||
$filter_options['filters'] = ['source' => $prefix];
|
||||
$translations = $this->storage->getTranslations(['language' => 'es'], $filter_options);
|
||||
$this->assertCount(2, $translations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates random source string object.
|
||||
*
|
||||
* @param array $values
|
||||
* The values array.
|
||||
*
|
||||
* @return \Drupal\locale\StringInterface
|
||||
* A locale string.
|
||||
*/
|
||||
protected function buildSourceString(array $values = []) {
|
||||
return $this->storage->createString($values += [
|
||||
'source' => $this->randomMachineName(100),
|
||||
'context' => $this->randomMachineName(20),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates translations for source string and all languages.
|
||||
*
|
||||
* @param \Drupal\locale\StringInterface $source
|
||||
* The source string.
|
||||
* @param array $values
|
||||
* The values array.
|
||||
*
|
||||
* @return array
|
||||
* Translation list.
|
||||
*/
|
||||
protected function createAllTranslations(StringInterface $source, array $values = []): array {
|
||||
$list = [];
|
||||
/** @var \Drupal\Core\Language\LanguageManagerInterface $language_manager */
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
foreach ($language_manager->getLanguages() as $language) {
|
||||
$list[$language->getId()] = $this->createTranslation($source, $language->getId(), $values);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates single translation for source string.
|
||||
*
|
||||
* @param \Drupal\locale\StringInterface $source
|
||||
* The source string.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param array $values
|
||||
* The values array.
|
||||
*
|
||||
* @return \Drupal\locale\StringInterface
|
||||
* The translated string object.
|
||||
*/
|
||||
protected function createTranslation(StringInterface $source, $langcode, array $values = []) {
|
||||
return $this->storage->createTranslation($values + [
|
||||
'lid' => $source->lid,
|
||||
'language' => $langcode,
|
||||
'translation' => $this->randomMachineName(100),
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that strings are correctly deleted.
|
||||
*/
|
||||
public function testDeleteStrings(): void {
|
||||
$source = $this->storage->createString([
|
||||
'source' => 'Revision ID',
|
||||
])->save();
|
||||
|
||||
$this->storage->createTranslation([
|
||||
'lid' => $source->lid,
|
||||
'language' => 'fr',
|
||||
'translation' => 'Translated Revision ID',
|
||||
])->save();
|
||||
|
||||
// Confirm that the string has been created.
|
||||
$this->assertNotEmpty($this->storage->findString(['lid' => $source->lid]));
|
||||
|
||||
$this->storage->deleteStrings(['lid' => $source->lid]);
|
||||
|
||||
// Confirm that the string has been deleted.
|
||||
$this->assertEmpty($this->storage->findString(['lid' => $source->lid]));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests locale translation project handling.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslationProjectsTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'locale_test', 'system'];
|
||||
|
||||
/**
|
||||
* The module handler used in this test.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The locale project storage used in this test.
|
||||
*
|
||||
* @var \Drupal\locale\LocaleProjectStorageInterface
|
||||
*/
|
||||
protected $projectStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->moduleHandler = $this->container->get('module_handler');
|
||||
$this->projectStorage = $this->container->get('locale.project');
|
||||
\Drupal::state()->set('locale.remove_core_project', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale_translation_clear_cache_projects().
|
||||
*/
|
||||
public function testLocaleTranslationClearCacheProjects(): void {
|
||||
$this->moduleHandler->loadInclude('locale', 'inc', 'locale.translation');
|
||||
|
||||
$expected = [];
|
||||
$this->assertSame($expected, locale_translation_get_projects());
|
||||
|
||||
$this->projectStorage->set('foo', []);
|
||||
$expected['foo'] = new \stdClass();
|
||||
$this->assertEquals($expected, locale_translation_get_projects());
|
||||
|
||||
$this->projectStorage->set('bar', []);
|
||||
locale_translation_clear_cache_projects();
|
||||
$expected['bar'] = new \stdClass();
|
||||
$this->assertEquals($expected, locale_translation_get_projects());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\locale\LocaleTranslation;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\locale\LocaleTranslation
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslationTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'locale',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installSchema('locale', [
|
||||
'locales_location',
|
||||
'locales_source',
|
||||
'locales_target',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that \Drupal\locale\LocaleTranslation is serializable.
|
||||
*/
|
||||
public function testSerializable(): void {
|
||||
/** @var \Drupal\locale\LocaleTranslation $translation */
|
||||
$translation = $this->container->get('string_translator.locale.lookup');
|
||||
$this->assertInstanceOf(LocaleTranslation::class, $translation);
|
||||
// Ensure that the \Drupal\locale\LocaleTranslation::$translations property
|
||||
// has some cached translations in it. Without this, serialization will not
|
||||
// actually be tested fully.
|
||||
$translation->getStringTranslation('es', 'test', '');
|
||||
|
||||
// Prove that serialization and deserialization works without errors.
|
||||
$this->assertNotNull($translation);
|
||||
$unserialized = unserialize(serialize($translation));
|
||||
$this->assertInstanceOf(LocaleTranslation::class, $unserialized);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests for updating the interface translations of projects.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleUpdateTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'locale',
|
||||
'locale_test',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks if a list of translatable projects gets build.
|
||||
*/
|
||||
public function testUpdateProjects(): void {
|
||||
$this->container->get('module_handler')->loadInclude('locale', 'compare.inc');
|
||||
|
||||
// Make the test modules look like a normal custom module. I.e. make the
|
||||
// modules not hidden. locale_test_system_info_alter() modifies the project
|
||||
// info of the locale_test and locale_test_translate modules.
|
||||
\Drupal::state()->set('locale.test_system_info_alter', TRUE);
|
||||
|
||||
// Check if interface translation data is collected from hook_info.
|
||||
$projects = locale_translation_project_list();
|
||||
$this->assertArrayNotHasKey('locale_test_translate', $projects);
|
||||
$this->assertEquals('core/modules/locale/test/test.%language.po', $projects['locale_test']['info']['interface translation server pattern']);
|
||||
$this->assertEquals('locale_test', $projects['locale_test']['name']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Kernel\Migrate;
|
||||
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Upgrade variables to locale.settings.yml.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateLocaleConfigsTest extends MigrateDrupal6TestBase {
|
||||
|
||||
use SchemaCheckTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['locale', 'language'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->executeMigration('locale_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migration of locale variables to locale.settings.yml.
|
||||
*/
|
||||
public function testLocaleSettings(): void {
|
||||
$config = $this->config('locale.settings');
|
||||
$this->assertTrue($config->get('cache_strings'));
|
||||
$this->assertSame('languages', $config->get('javascript.directory'));
|
||||
$this->assertConfigSchema(\Drupal::service('config.typed'), 'locale.settings', $config->get());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Unit;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Tests locale.bulk.inc.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleBulkDeprecationTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('locale.config_manager', $this->createMock('Drupal\locale\LocaleConfigManager'));
|
||||
$language_manager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface');
|
||||
$language_manager->expects($this->any())
|
||||
->method('getDefaultLanguage')
|
||||
->willReturn($this->createMock('Drupal\Core\Language\LanguageInterface'));
|
||||
$container->set('language_manager', $language_manager);
|
||||
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
include_once DRUPAL_ROOT . '/core/modules/locale/locale.bulk.inc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deprecation of locale_config_batch_refresh_name().
|
||||
*
|
||||
* @group legacy
|
||||
*
|
||||
* @see locale_config_batch_refresh_name()
|
||||
*/
|
||||
public function testDeprecatedLocaleConfigBatchRefreshName(): void {
|
||||
$this->expectDeprecation('locale_config_batch_refresh_name() is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use locale_config_batch_update_config_translations() instead. See https://www.drupal.org/node/3475054');
|
||||
$names = ['English', 'German'];
|
||||
$langcodes = ['en', 'de'];
|
||||
locale_config_batch_refresh_name($names, $langcodes, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deprecation of locale_config_batch_set_config_langcodes().
|
||||
*
|
||||
* @group legacy
|
||||
*
|
||||
* @see locale_config_batch_set_config_langcodes()
|
||||
*/
|
||||
public function testDeprecatedLocaleConfigBatchSetConfigLangcodes(): void {
|
||||
$this->expectDeprecation('locale_config_batch_set_config_langcodes() is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Use locale_config_batch_update_default_config_langcodes() instead. See https://www.drupal.org/node/3475054');
|
||||
$context = [];
|
||||
locale_config_batch_set_config_langcodes($context);
|
||||
}
|
||||
|
||||
}
|
||||
426
web/core/modules/locale/tests/src/Unit/LocaleLookupTest.php
Normal file
426
web/core/modules/locale/tests/src/Unit/LocaleLookupTest.php
Normal file
@ -0,0 +1,426 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Unit;
|
||||
|
||||
use Drupal\Component\Gettext\PoItem;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\locale\LocaleLookup;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\locale\LocaleLookup
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleLookupTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* A mocked storage to use when instantiating LocaleTranslation objects.
|
||||
*
|
||||
* @var \Drupal\locale\StringStorageInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* A mocked cache object.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* A mocked lock object.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $lock;
|
||||
|
||||
/**
|
||||
* A mocked user object built from AccountInterface.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* A mocked config factory built with UnitTestCase::getConfigFactoryStub().
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactory|\PHPUnit\Framework\MockObject\MockBuilder
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* A mocked language manager built from LanguageManagerInterface.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->storage = $this->createMock('Drupal\locale\StringStorageInterface');
|
||||
$this->cache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface');
|
||||
$this->lock = $this->createMock('Drupal\Core\Lock\LockBackendInterface');
|
||||
$this->lock->expects($this->never())
|
||||
->method($this->anything());
|
||||
|
||||
$this->user = $this->createMock('Drupal\Core\Session\AccountInterface');
|
||||
$this->user->expects($this->any())
|
||||
->method('getRoles')
|
||||
->willReturn(['anonymous']);
|
||||
|
||||
$this->configFactory = $this->getConfigFactoryStub(['locale.settings' => ['cache_strings' => FALSE]]);
|
||||
|
||||
$this->languageManager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface');
|
||||
$this->requestStack = new RequestStack();
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('current_user', $this->user);
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale lookups without fallback.
|
||||
*
|
||||
* @covers ::resolveCacheMiss
|
||||
*/
|
||||
public function testResolveCacheMissWithoutFallback(): void {
|
||||
$args = [
|
||||
'language' => 'en',
|
||||
'source' => 'test',
|
||||
'context' => 'irrelevant',
|
||||
];
|
||||
|
||||
$result = (object) [
|
||||
'translation' => 'test',
|
||||
];
|
||||
|
||||
$this->cache->expects($this->once())
|
||||
->method('get')
|
||||
->with('locale:en:irrelevant:anonymous', FALSE);
|
||||
|
||||
$this->storage->expects($this->once())
|
||||
->method('findTranslation')
|
||||
->with($this->equalTo($args))
|
||||
->willReturn($result);
|
||||
|
||||
$locale_lookup = $this->getMockBuilder('Drupal\locale\LocaleLookup')
|
||||
->setConstructorArgs([
|
||||
'en',
|
||||
'irrelevant',
|
||||
$this->storage,
|
||||
$this->cache,
|
||||
$this->lock,
|
||||
$this->configFactory,
|
||||
$this->languageManager,
|
||||
$this->requestStack,
|
||||
])
|
||||
->onlyMethods(['persist'])
|
||||
->getMock();
|
||||
$locale_lookup->expects($this->never())
|
||||
->method('persist');
|
||||
$this->assertSame('test', $locale_lookup->get('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale lookups with fallback.
|
||||
*
|
||||
* Note that context is irrelevant here. It is not used but it is required.
|
||||
*
|
||||
* @covers ::resolveCacheMiss
|
||||
*
|
||||
* @dataProvider resolveCacheMissWithFallbackProvider
|
||||
*/
|
||||
public function testResolveCacheMissWithFallback($langcode, $string, $context, $expected): void {
|
||||
// These are fake words!
|
||||
// cSpell:disable
|
||||
$translations = [
|
||||
'en' => [
|
||||
'test' => 'test',
|
||||
'fake' => 'fake',
|
||||
'missing pl' => 'missing pl',
|
||||
'missing cs' => 'missing cs',
|
||||
'missing both' => 'missing both',
|
||||
],
|
||||
'pl' => [
|
||||
'test' => 'test po polsku',
|
||||
'fake' => 'ściema',
|
||||
'missing cs' => 'zaginiony czech',
|
||||
],
|
||||
'cs' => [
|
||||
'test' => 'test v české',
|
||||
'fake' => 'falešný',
|
||||
'missing pl' => 'chybějící pl',
|
||||
],
|
||||
];
|
||||
// cSpell:enable
|
||||
$this->storage->expects($this->any())
|
||||
->method('findTranslation')
|
||||
->willReturnCallback(function ($argument) use ($translations) {
|
||||
if (isset($translations[$argument['language']][$argument['source']])) {
|
||||
return (object) ['translation' => $translations[$argument['language']][$argument['source']]];
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
});
|
||||
|
||||
$this->languageManager->expects($this->any())
|
||||
->method('getFallbackCandidates')
|
||||
->willReturnCallback(function (array $context = []) {
|
||||
switch ($context['langcode']) {
|
||||
case 'pl':
|
||||
return ['cs', 'en'];
|
||||
|
||||
case 'cs':
|
||||
return ['en'];
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
$this->cache->expects($this->once())
|
||||
->method('get')
|
||||
->with('locale:' . $langcode . ':' . $context . ':anonymous', FALSE);
|
||||
|
||||
$locale_lookup = new LocaleLookup($langcode, $context, $this->storage, $this->cache, $this->lock, $this->configFactory, $this->languageManager, $this->requestStack);
|
||||
$this->assertSame($expected, $locale_lookup->get($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testResolveCacheMissWithFallback().
|
||||
*/
|
||||
public static function resolveCacheMissWithFallbackProvider() {
|
||||
// cSpell:disable
|
||||
return [
|
||||
['cs', 'test', 'irrelevant', 'test v české'],
|
||||
['cs', 'fake', 'irrelevant', 'falešný'],
|
||||
['cs', 'missing pl', 'irrelevant', 'chybějící pl'],
|
||||
['cs', 'missing cs', 'irrelevant', 'missing cs'],
|
||||
['cs', 'missing both', 'irrelevant', 'missing both'],
|
||||
|
||||
// Testing PL with fallback to cs, en.
|
||||
['pl', 'test', 'irrelevant', 'test po polsku'],
|
||||
['pl', 'fake', 'irrelevant', 'ściema'],
|
||||
['pl', 'missing pl', 'irrelevant', 'chybějící pl'],
|
||||
['pl', 'missing cs', 'irrelevant', 'zaginiony czech'],
|
||||
['pl', 'missing both', 'irrelevant', 'missing both'],
|
||||
];
|
||||
// cSpell:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale lookups with persistent tracking.
|
||||
*
|
||||
* @covers ::resolveCacheMiss
|
||||
*/
|
||||
public function testResolveCacheMissWithPersist(): void {
|
||||
$args = [
|
||||
'language' => 'en',
|
||||
'source' => 'test',
|
||||
'context' => 'irrelevant',
|
||||
];
|
||||
|
||||
$result = (object) [
|
||||
'translation' => 'test',
|
||||
];
|
||||
|
||||
$this->storage->expects($this->once())
|
||||
->method('findTranslation')
|
||||
->with($this->equalTo($args))
|
||||
->willReturn($result);
|
||||
|
||||
$this->configFactory = $this->getConfigFactoryStub(['locale.settings' => ['cache_strings' => TRUE]]);
|
||||
$locale_lookup = $this->getMockBuilder('Drupal\locale\LocaleLookup')
|
||||
->setConstructorArgs([
|
||||
'en',
|
||||
'irrelevant',
|
||||
$this->storage,
|
||||
$this->cache,
|
||||
$this->lock,
|
||||
$this->configFactory,
|
||||
$this->languageManager,
|
||||
$this->requestStack,
|
||||
])
|
||||
->onlyMethods(['persist'])
|
||||
->getMock();
|
||||
$locale_lookup->expects($this->once())
|
||||
->method('persist');
|
||||
|
||||
$this->assertSame('test', $locale_lookup->get('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale lookups without a found translation.
|
||||
*
|
||||
* @covers ::resolveCacheMiss
|
||||
*/
|
||||
public function testResolveCacheMissNoTranslation(): void {
|
||||
$string = $this->createMock('Drupal\locale\StringInterface');
|
||||
$string->expects($this->once())
|
||||
->method('addLocation')
|
||||
->willReturnSelf();
|
||||
$this->storage->expects($this->once())
|
||||
->method('findTranslation')
|
||||
->willReturn(NULL);
|
||||
$this->storage->expects($this->once())
|
||||
->method('createString')
|
||||
->willReturn($string);
|
||||
|
||||
$request = Request::create('/test');
|
||||
$this->requestStack->push($request);
|
||||
|
||||
$locale_lookup = $this->getMockBuilder('Drupal\locale\LocaleLookup')
|
||||
->setConstructorArgs([
|
||||
'en',
|
||||
'irrelevant',
|
||||
$this->storage,
|
||||
$this->cache,
|
||||
$this->lock,
|
||||
$this->configFactory,
|
||||
$this->languageManager,
|
||||
$this->requestStack,
|
||||
])
|
||||
->onlyMethods(['persist'])
|
||||
->getMock();
|
||||
$locale_lookup->expects($this->never())
|
||||
->method('persist');
|
||||
|
||||
$this->assertTrue($locale_lookup->get('test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests locale lookups with old plural style of translations.
|
||||
*
|
||||
* @param array $translations
|
||||
* The source with translations.
|
||||
* @param string $langcode
|
||||
* The language code of translation string.
|
||||
* @param string $string
|
||||
* The string for translation.
|
||||
* @param bool $is_fix
|
||||
* The flag about expected fix translation.
|
||||
*
|
||||
* @covers ::resolveCacheMiss
|
||||
* @dataProvider providerFixOldPluralTranslationProvider
|
||||
*/
|
||||
public function testFixOldPluralStyleTranslations($translations, $langcode, $string, $is_fix): void {
|
||||
$this->storage->expects($this->any())
|
||||
->method('findTranslation')
|
||||
->willReturnCallback(function ($argument) use ($translations) {
|
||||
if (isset($translations[$argument['language']][$argument['source']])) {
|
||||
return (object) ['translation' => $translations[$argument['language']][$argument['source']]];
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
});
|
||||
$this->languageManager->expects($this->any())
|
||||
->method('getFallbackCandidates')
|
||||
->willReturnCallback(function (array $context = []) {
|
||||
switch ($context['langcode']) {
|
||||
case 'by':
|
||||
return ['ru'];
|
||||
}
|
||||
});
|
||||
$this->cache->expects($this->once())
|
||||
->method('get')
|
||||
->with('locale:' . $langcode . '::anonymous', FALSE);
|
||||
|
||||
$locale_lookup = new LocaleLookup($langcode, '', $this->storage, $this->cache, $this->lock, $this->configFactory, $this->languageManager, $this->requestStack);
|
||||
if ($is_fix) {
|
||||
$this->assertStringNotContainsString('@count[2]', $locale_lookup->get($string));
|
||||
}
|
||||
else {
|
||||
$this->assertStringContainsString('@count[2]', $locale_lookup->get($string));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testResolveCacheMissWithFallback().
|
||||
*/
|
||||
public static function providerFixOldPluralTranslationProvider() {
|
||||
$translations = [
|
||||
'by' => [
|
||||
'word1' => '@count[2] word-by',
|
||||
'word2' => implode(PoItem::DELIMITER, ['word-by', '@count[2] word-by']),
|
||||
],
|
||||
'ru' => [
|
||||
'word3' => '@count[2] word-ru',
|
||||
'word4' => implode(PoItem::DELIMITER, ['word-ru', '@count[2] word-ru']),
|
||||
],
|
||||
];
|
||||
return [
|
||||
'no-plural' => [$translations, 'by', 'word1', FALSE],
|
||||
'no-plural from other language' => [$translations, 'by', 'word3', FALSE],
|
||||
'plural' => [$translations, 'by', 'word2', TRUE],
|
||||
'plural from other language' => [$translations, 'by', 'word4', TRUE],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getCid
|
||||
*
|
||||
* @dataProvider getCidProvider
|
||||
*/
|
||||
public function testGetCid(array $roles, $expected): void {
|
||||
$this->user = $this->createMock('Drupal\Core\Session\AccountInterface');
|
||||
$this->user->expects($this->any())
|
||||
->method('getRoles')
|
||||
->willReturn($roles);
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('current_user', $this->user);
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$locale_lookup = $this->getMockBuilder('Drupal\locale\LocaleLookup')
|
||||
->setConstructorArgs([
|
||||
'en',
|
||||
'irrelevant',
|
||||
$this->storage,
|
||||
$this->cache,
|
||||
$this->lock,
|
||||
$this->configFactory,
|
||||
$this->languageManager,
|
||||
$this->requestStack,
|
||||
])
|
||||
->getMock();
|
||||
|
||||
$o = new \ReflectionObject($locale_lookup);
|
||||
$method = $o->getMethod('getCid');
|
||||
$cid = $method->invoke($locale_lookup, 'getCid');
|
||||
|
||||
$this->assertEquals($expected, $cid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testGetCid().
|
||||
*/
|
||||
public static function getCidProvider() {
|
||||
return [
|
||||
[
|
||||
['a'], 'locale:en:irrelevant:a',
|
||||
],
|
||||
[
|
||||
['a', 'b'], 'locale:en:irrelevant:a:b',
|
||||
],
|
||||
[
|
||||
['b', 'a'], 'locale:en:irrelevant:a:b',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Unit;
|
||||
|
||||
use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
|
||||
use Drupal\locale\LocaleProjectStorage;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\locale\LocaleProjectStorage
|
||||
* @group locale
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class LocaleProjectStorageTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The local project storage.
|
||||
*
|
||||
* @var \Drupal\locale\LocaleProjectStorage
|
||||
*/
|
||||
private LocaleProjectStorage $projectStorage;
|
||||
|
||||
/**
|
||||
* The key value memory factory.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueMemoryFactory
|
||||
*/
|
||||
private KeyValueMemoryFactory $keyValueMemoryFactory;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->keyValueMemoryFactory = new KeyValueMemoryFactory();
|
||||
$this->projectStorage = new LocaleProjectStorage($this->keyValueMemoryFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that projects are sorted by weight and key.
|
||||
*/
|
||||
public function testSorting(): void {
|
||||
// There are no projects.
|
||||
$this->assertSame([], $this->projectStorage->getAll());
|
||||
|
||||
// Add project 'b'.
|
||||
$this->projectStorage->set('b', ['name' => 'b']);
|
||||
$this->assertSame(['b'], array_keys($this->projectStorage->getAll()));
|
||||
|
||||
// Add project 'c' and confirm alphabetical order.
|
||||
$this->projectStorage->set('c', ['name' => 'c']);
|
||||
$this->assertSame(['b', 'c'], array_keys($this->projectStorage->getAll()));
|
||||
|
||||
// Add project 'a' and confirm 'a' is first.
|
||||
$this->projectStorage->set('a', ['name' => 'a']);
|
||||
$this->assertSame(['a', 'b', 'c'], array_keys($this->projectStorage->getAll()));
|
||||
|
||||
// Add project 'd' with a negative weight and confirm 'd' is first.
|
||||
$this->projectStorage->set('d', ['name' => 'd', 'weight' => -1]);
|
||||
$this->assertSame(['d', 'a', 'b', 'c'], array_keys($this->projectStorage->getAll()));
|
||||
|
||||
// Add project 'aa' with a positive weight and confirm 'aa' is last.
|
||||
$this->projectStorage->set('aa', ['name' => 'aa', 'weight' => 1]);
|
||||
$this->assertSame(['d', 'a', 'b', 'c', 'aa'], array_keys($this->projectStorage->getAll()));
|
||||
|
||||
// Delete project 'a'.
|
||||
$this->projectStorage->delete('a');
|
||||
$this->assertSame(['d', 'b', 'c', 'aa'], array_keys($this->projectStorage->getAll()));
|
||||
|
||||
// Add project 'e' with a lower negative weight than 'd' and confirm 'e' is
|
||||
// first.
|
||||
$this->projectStorage->set('e', ['name' => 'e', 'weight' => -5]);
|
||||
$this->assertSame(['e', 'd', 'b', 'c', 'aa'], array_keys($this->projectStorage->getAll()));
|
||||
|
||||
// Pretend there is a container rebuild by generating a new
|
||||
// LocaleProjectStorage object with the same data.
|
||||
$this->projectStorage = new LocaleProjectStorage($this->keyValueMemoryFactory);
|
||||
$this->projectStorage->set('z', ['name' => 'z']);
|
||||
$this->assertSame(['e', 'd', 'b', 'c', 'z', 'aa'], array_keys($this->projectStorage->getAll()));
|
||||
|
||||
// Now delete all projects.
|
||||
$this->projectStorage->deleteAll();
|
||||
$this->assertSame([], $this->projectStorage->getAll());
|
||||
|
||||
// Add project 'z' before project 'a' and confirm 'a' is first.
|
||||
$this->projectStorage->set('z', ['name' => 'z']);
|
||||
$this->projectStorage->set('a', ['name' => 'a']);
|
||||
$this->assertSame(['a', 'z'], array_keys($this->projectStorage->getAll()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleted projects are not included in the count.
|
||||
*/
|
||||
public function testDelete(): void {
|
||||
$this->projectStorage->set('b', ['name' => 'b']);
|
||||
$this->assertSame(['name' => 'b'], $this->projectStorage->get('b'));
|
||||
$this->assertSame(1, $this->projectStorage->countProjects());
|
||||
$this->projectStorage->delete('b');
|
||||
$this->assertNull($this->projectStorage->get('b'));
|
||||
$this->assertSame(0, $this->projectStorage->countProjects());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Unit;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\locale\LocaleTranslation;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\locale\LocaleTranslation
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleTranslationTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* A mocked storage to use when instantiating LocaleTranslation objects.
|
||||
*
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* A mocked lock to use when instantiating LocaleTranslation objects.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected LockBackendInterface $lock;
|
||||
|
||||
/**
|
||||
* A mocked cache to use when instantiating LocaleTranslation objects.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected CacheBackendInterface $cache;
|
||||
|
||||
/**
|
||||
* A mocked language manager built from LanguageManagerInterface.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->storage = $this->createMock('Drupal\locale\StringStorageInterface');
|
||||
$this->cache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface');
|
||||
$this->lock = $this->createMock('Drupal\Core\Lock\LockBackendInterface');
|
||||
$this->languageManager = $this->createMock('Drupal\Core\Language\LanguageManagerInterface');
|
||||
$this->requestStack = new RequestStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for \Drupal\locale\LocaleTranslation::destruct().
|
||||
*/
|
||||
public function testDestruct(): void {
|
||||
$translation = new LocaleTranslation($this->storage, $this->cache, $this->lock, $this->getConfigFactoryStub(), $this->languageManager, $this->requestStack);
|
||||
// Prove that destruction works without errors when translations are empty.
|
||||
$this->assertNull($translation->destruct());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Unit\Menu;
|
||||
|
||||
use Drupal\Tests\Core\Menu\LocalTaskIntegrationTestBase;
|
||||
|
||||
/**
|
||||
* Tests locale local tasks.
|
||||
*
|
||||
* @group locale
|
||||
*/
|
||||
class LocaleLocalTasksTest extends LocalTaskIntegrationTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
$this->directoryList = [
|
||||
'locale' => 'core/modules/locale',
|
||||
];
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks locale listing local tasks.
|
||||
*
|
||||
* @dataProvider getLocalePageRoutes
|
||||
*/
|
||||
public function testLocalePageLocalTasks($route): void {
|
||||
$tasks = [
|
||||
0 => ['locale.translate_page', 'locale.translate_import', 'locale.translate_export', 'locale.settings'],
|
||||
];
|
||||
$this->assertLocalTasks($route, $tasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of routes to test.
|
||||
*/
|
||||
public static function getLocalePageRoutes() {
|
||||
return [
|
||||
['locale.translate_page'],
|
||||
['locale.translate_import'],
|
||||
['locale.translate_export'],
|
||||
['locale.settings'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
37
web/core/modules/locale/tests/src/Unit/StringBaseTest.php
Normal file
37
web/core/modules/locale/tests/src/Unit/StringBaseTest.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\locale\Unit;
|
||||
|
||||
use Drupal\locale\SourceString;
|
||||
use Drupal\locale\StringStorageException;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\locale\StringBase
|
||||
* @group locale
|
||||
*/
|
||||
class StringBaseTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::save
|
||||
*/
|
||||
public function testSaveWithoutStorage(): void {
|
||||
$string = new SourceString(['source' => 'test']);
|
||||
$this->expectException(StringStorageException::class);
|
||||
$this->expectExceptionMessage('The string cannot be saved because its not bound to a storage: test');
|
||||
$string->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::delete
|
||||
*/
|
||||
public function testDeleteWithoutStorage(): void {
|
||||
$string = new SourceString(['lid' => 1, 'source' => 'test']);
|
||||
$this->expectException(StringStorageException::class);
|
||||
$this->expectExceptionMessage('The string cannot be deleted because its not bound to a storage: test');
|
||||
$string->delete();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user