diff --git a/core/modules/locale/lib/Drupal/locale/Gettext.php b/core/modules/locale/lib/Drupal/locale/Gettext.php
index 43432e4..b21023a 100644
--- a/core/modules/locale/lib/Drupal/locale/Gettext.php
+++ b/core/modules/locale/lib/Drupal/locale/Gettext.php
@@ -53,9 +53,8 @@ static function filesToArray($langcode, array $files) {
*
* @param stdClass $file
* File object with an URI property pointing at the file's path.
- * @todo: Describe Required parts of the file object.
- * - 'langcode': The language code.
- *
+ * - "langcode": The language the language the strings will be added to.
+ * - "uri": File URI.
* @param array $options
* An array with options that can have the following elements:
* - 'overwrite_options': Overwrite options array as defined in
diff --git a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
index 337bd90..eb02890 100644
--- a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
+++ b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
@@ -160,14 +160,14 @@ function setHeader(PoHeader $header) {
// Check for options.
$options = $this->getOptions();
if (empty($options)) {
- throw new \Exception("Options should be set before assigning a PoHeader.");
+ throw new \Exception('Options should be set before assigning a PoHeader.');
}
$overwrite_options = $options['overwrite_options'];
// Check for langcode.
$langcode = $this->_langcode;
if (empty($langcode)) {
- throw new \Exception("Langcode should be set before assigning a PoHeader.");
+ throw new \Exception('Langcode should be set before assigning a PoHeader.');
}
if (array_sum($overwrite_options) || empty($locale_plurals[$langcode]['plurals'])) {
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
deleted file mode 100644
index 126fbbc..0000000
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
+++ /dev/null
@@ -1,202 +0,0 @@
- 'Translation file import status',
- 'description' => 'Tests the status of imported translation files.',
- 'group' => 'Locale',
- );
- }
-
- function setUp() {
- parent::setUp();
- // Create and login user.
- $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages'));
- $this->drupalLogin($admin_user);
-
- // Copy test po files to the translations directory.
- file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.de.po', 'translations://', FILE_EXISTS_REPLACE);
- file_unmanaged_copy(drupal_get_path('module', 'locale') . '/tests/test.xx.po', 'translations://', FILE_EXISTS_REPLACE);
- }
-
- /**
- * Add a language.
- *
- * @param $langcode
- * The language of the langcode to add.
- */
- function addLanguage($langcode) {
- $edit = array('predefined_langcode' => $langcode);
- $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
- drupal_static_reset('language_list');
- $this->assertTrue(language_load($langcode), t('Language %langcode added.', array('%langcode' => $langcode)));
- }
-
- /**
- * Get translations for an array of strings.
- *
- * @param $strings
- * An array of strings to translate.
- * @param $langcode
- * The language code of the language to translate to.
- */
- function checkTranslations($strings, $langcode) {
- foreach ($strings as $source => $translation) {
- $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', array(':source' => $source, ':langcode' => $langcode))->fetchField();
- $this->assertEqual((string) $translation, (string) $db_translation);
- }
- }
-
- /**
- * Import a single interface translation file.
- *
- * @param $langcode
- * Langcode of the po file and language to import.
- * @param int $timestamp_difference
- * (optional) Timestamp offset, used to mock older or newer files.
- *
- * @return stdClass
- * A file object of type stdClass.
- */
- function mockImportedPoFile($langcode, $timestamp_difference = 0) {
- $testfile_uri = 'translations://test.' . $langcode . '.po';
-
- $file = locale_translate_file_create($testfile_uri);
- $file->original_timestamp = $file->timestamp;
- $file->timestamp = $file->timestamp + $timestamp_difference;
- $file = locale_translate_file_attach_properties($file, array('langcode' => $langcode));
-
- // Fill the {locale_file} with a custom timestamp.
- if ($timestamp_difference != 0) {
- locale_translation_update_file_history($file);
- }
-
- $count = db_query('SELECT COUNT(*) FROM {locale_file} WHERE langcode = :langcode', array(':langcode' => $langcode))->fetchField();
- $this->assertEqual(1, $count, format_plural($count, '@count file registered in {locale_file}.', '@count files registered in {locale_file}.'));
-
- $result = db_query('SELECT langcode, uri FROM {locale_file}')->fetchAssoc();
- $this->assertEqual($result['uri'], $testfile_uri, t('%uri is in {locale_file}.', array('%uri' => $result['uri'])));
- $this->assertEqual($result['langcode'], $langcode, t('Langcode is %langcode.', array('%langcode' => $langcode)));
- return $file;
- }
-
- /**
- * Test the basic bulk import functionality.
- */
- function testBulkImport() {
- $langcode = 'de';
-
- // Translations should not exist.
- $strings = array(
- 'Monday' => '',
- 'Tuesday' => '',
- );
- $this->checkTranslations($strings, $langcode);
-
- // Add language.
- $this->addLanguage($langcode);
-
- // The file was imported, translations should exist.
- $strings = array(
- 'Monday' => 'Montag',
- 'Tuesday' => 'Dienstag',
- );
- $this->checkTranslations($strings, $langcode);
- }
-
- /**
- * Update a pre-existing file.
- */
- function testBulkImportUpdateExisting() {
- $langcode = 'de';
-
- // Translations should not exist.
- $strings = array(
- 'Monday' => '',
- 'Tuesday' => '',
- );
- $this->checkTranslations($strings, $langcode);
-
- // Fill the {locale_file} table with an older file.
- $file = $this->mockImportedPoFile($langcode, -1);
-
- // Add language.
- $this->addLanguage($langcode);
-
- // The file was imported, translations should exist.
- $strings = array(
- 'Monday' => 'Montag',
- 'Tuesday' => 'Dienstag',
- );
- $this->checkTranslations($strings, $langcode);
-
- $timestamp = db_query('SELECT timestamp FROM {locale_file} WHERE uri = :uri', array(':uri' => $file->uri))->fetchField();
- // Ensure that $timestamp is now greater than the manipulated timestamp.
- $this->assertTrue($timestamp > $file->timestamp, 'Timestamp on locale_file updated.');
- }
-
- /**
- * Don't update a pre-existing file.
- */
- function testBulkImportNotUpdateExisting() {
- $langcode = 'de';
-
- // Translations should not exist.
- $strings = array(
- 'Monday' => '',
- 'Tuesday' => '',
- );
- $this->checkTranslations($strings, $langcode);
-
- // Fill the {locale_file} table with a newer file.
- $file = $this->mockImportedPoFile($langcode, 1);
-
- // Add language.
- $this->addLanguage($langcode);
-
- // The file was not imported, the translation should not exist.
- $strings = array(
- 'Monday' => '',
- 'Tuesday' => '',
- );
- $this->checkTranslations($strings, $langcode);
-
- $timestamp = db_query('SELECT timestamp FROM {locale_file} WHERE uri = :uri', array(':uri' => $file->uri))->fetchField();
- $this->assertEqual($timestamp, $file->timestamp);
- }
-
- /**
- * Delete translation files after deleting a language.
- */
- function testDeleteLanguage() {
- $langcode = 'de';
- $this->addLanguage($langcode);
- $file_uri = 'translations://po_' . $this->randomName() . '.' . $langcode . '.po';
- file_put_contents(drupal_realpath($file_uri), $this->randomName());
- $this->assertTrue(is_file($file_uri), 'Translation file is created.');
- language_delete($langcode);
- $this->assertTrue($file_uri);
- $this->assertFalse(is_file($file_uri), 'Translation file deleted after deleting language');
- }
-}
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
index 4dca9e7..fd3667c 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
@@ -58,7 +58,7 @@ function testStandalonePoFile() {
$this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), t('The language has been automatically created.'));
// The import should have created 8 strings.
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 8, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 8, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
// This import should have saved plural forms to have 2 variants.
$locale_plurals = variable_get('locale_translation_plurals', array());
@@ -74,8 +74,9 @@ function testStandalonePoFile() {
));
// The import should have created 1 string and rejected 2.
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
- $skip_message = format_plural(2, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => url('admin/reports/dblog')));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
+
+ $skip_message = format_plural(2, 'One translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => url('admin/reports/dblog')));
$this->assertRaw($skip_message, t('Unsafe strings were skipped.'));
// Try importing a zero byte sized .po file.
@@ -84,7 +85,7 @@ function testStandalonePoFile() {
));
// The import should have created 0 string and rejected 0.
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 0, '%delete' => 0)), 'The empty translation file was successfully imported.');
+ $this->assertRaw(t('One translation file could not be imported. See the log for details.', array('@url' => url('admin/reports/dblog'))), 'The empty translation file was successfully imported.');
// Try importing a .po file which doesn't exist.
$name = $this->randomName(16);
@@ -103,7 +104,7 @@ function testStandalonePoFile() {
));
// The import should have created 1 string.
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
// Ensure string wasn't overwritten.
$search = array(
'string' => 'Montag',
@@ -125,7 +126,7 @@ function testStandalonePoFile() {
));
// The import should have updated 2 strings.
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), t('The translation file was successfully imported.'));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), t('The translation file was successfully imported.'));
// Ensure string was overwritten.
$search = array(
'string' => 'Montag',
@@ -145,7 +146,7 @@ function testStandalonePoFile() {
));
// The import should have created 6 strings.
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 6, '%update' => 0, '%delete' => 0)), t('The customized translation file was successfully imported.'));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 6, '%update' => 0, '%delete' => 0)), t('The customized translation file was successfully imported.'));
// The database should now contain 6 customized strings (two imported
// strings are not translated).
@@ -161,7 +162,7 @@ function testStandalonePoFile() {
));
// The import should have created 1 string.
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The customized translation file was successfully imported.'));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The customized translation file was successfully imported.'));
// Ensure string wasn't overwritten.
$search = array(
'string' => 'januari',
@@ -180,7 +181,7 @@ function testStandalonePoFile() {
));
// The import should have updated 2 strings.
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), t('The customized translation file was successfully imported.'));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), t('The customized translation file was successfully imported.'));
// Ensure string was overwritten.
$search = array(
'string' => 'januari',
@@ -193,60 +194,6 @@ function testStandalonePoFile() {
}
/**
- * Test automatic import of a module's translation files.
- */
- function testAutomaticModuleTranslationImportLanguageEnable() {
- // Code for the language - manually set to match the test translation file.
- $langcode = 'xx';
- // The English name for the language.
- $name = $this->randomName(16);
-
- // Create a custom language.
- $edit = array(
- 'predefined_langcode' => 'custom',
- 'langcode' => $langcode,
- 'name' => $name,
- 'direction' => '0',
- );
- $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
-
- // Ensure the translation file was automatically imported when language was
- // added.
- $this->assertText(t('One translation file imported.'), t('Language file automatically imported.'));
- $this->assertText(t('A translation string was skipped because of disallowed or malformed HTML'), t('Language file automatically imported.'));
-
- // Ensure strings were successfully imported.
- $search = array(
- 'string' => 'lundi',
- 'langcode' => $langcode,
- 'translation' => 'translated',
- );
- $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
- $this->assertNoText(t('No strings available.'), t('String successfully imported.'));
-
- // Ensure multiline string was imported.
- $search = array(
- 'string' => 'Source string for multiline translation',
- 'langcode' => $langcode,
- 'translation' => 'all',
- );
- $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
- $this->assertText('Multiline translation string to make sure that import works with it.', t('String successfully imported.'));
-
- // Ensure 'Allowed HTML source string' was imported but the translation for
- // 'Another allowed HTML source string' was not because it contains invalid
- // HTML.
- $search = array(
- 'string' => 'HTML source string',
- 'langcode' => $langcode,
- 'translation' => 'all',
- );
- $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
- $this->assertText('Allowed HTML source string', t('String successfully imported.'));
- $this->assertNoText('Another allowed HTML source string', t('String with disallowed translation not imported.'));
- }
-
- /**
* Test msgctxt context support.
*/
function testLanguageContext() {
@@ -270,7 +217,7 @@ function testEmptyMsgstr() {
'langcode' => $langcode,
));
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
$this->assertIdentical(t('Operations', array(), array('langcode' => $langcode)), 'Műveletek', t('String imported and translated.'));
// Try importing a .po file.
@@ -278,7 +225,7 @@ function testEmptyMsgstr() {
'langcode' => $langcode,
'overwrite_options[not_customized]' => TRUE,
));
- $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 0, '%delete' => 1)), t('The translation file was successfully imported.'));
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 0, '%update' => 0, '%delete' => 1)), t('The translation file was successfully imported.'));
$str = "Operations";
$search = array(
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
index b38e0d0..07a1887 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
@@ -22,34 +22,27 @@ class LocaleUpdateTest extends WebTestBase {
private $tranlations_directory;
/**
- * Timestamp for a translation file or existing translations.
+ * Timestamp for an old translation.
*
* @var integer
*/
private $timestamp_old;
/**
- * Timestamp for a translation file or existing translations.
+ * Timestamp for a medium aged translation.
*
* @var integer
*/
private $timestamp_medium;
/**
- * Timestamp for a translation file or existing translations.
+ * Timestamp for a new translation.
*
* @var integer
*/
private $timestamp_new;
/**
- * Timestamp for a translation file or existing translations.
- *
- * @var integer
- */
- private $timestamp_current;
-
- /**
* Modules to enable.
*
* @var array
@@ -74,7 +67,7 @@ function setUp() {
parent::setUp();
module_load_include('compare.inc', 'locale');
module_load_include('fetch.inc', 'locale');
- $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
+ $admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
$this->drupalLogin($admin_user);
$this->drupalPost('admin/config/regional/language/add', array('predefined_langcode' => 'de'), t('Add language'));
@@ -99,6 +92,19 @@ private function setTranslationsDirectory($path) {
}
/**
+ * Add a language.
+ *
+ * @param $langcode
+ * The language code of the language to add.
+ */
+ function addLanguage($langcode) {
+ $edit = array('predefined_langcode' => $langcode);
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+ drupal_static_reset('language_list');
+ $this->assertTrue(language_load($langcode), t('Language %langcode added.', array('%langcode' => $langcode)));
+ }
+
+ /**
* Creates a translation file and tests its timestamp.
*
* @param string $path
@@ -126,7 +132,7 @@ private function makePoFile($path, $filename, $timestamp = NULL, $translations =
EOF;
- // Convert array of translations to Gettext source an translation strings.
+ // Convert array of translations to Gettext source and translation strings.
if ($translations) {
foreach ($translations as $source => $target) {
$text .= 'msgid "'. $source . '"' . "\n";
@@ -151,20 +157,37 @@ private function makePoFile($path, $filename, $timestamp = NULL, $translations =
/**
* Setup the environment containting local and remote translation files.
*
- * Update test require a simulated environment for local and remote 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.
*/
private function setTranslationFiles() {
$config = 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.
- state()->set('locale_translation_test_projects', TRUE);
+ state()->set('locale.test_projects_alter', TRUE);
// Set up the environment.
$public_path = variable_get('file_public_path', conf_path() . '/files');
@@ -193,7 +216,8 @@ private function setTranslationFiles() {
}
/**
- * Setup existing translations in the database and setup the status of existing translations.
+ * Setup existing translations in the database and setup the status of
+ * existing translations.
*/
private function setCurrentTranslations() {
// Add non customized translations to the database.
@@ -215,7 +239,7 @@ private function setCurrentTranslations() {
))->save();
}
- // Add non customized translations to the database.
+ // Add customized translations to the database.
$customized_translations = array(
'January' => 'Januar_customized',
'February' => 'Februar_customized',
@@ -267,54 +291,58 @@ private function setCurrentTranslations() {
}
/**
- * Test for translation status storage and translation status comparison.
+ * Check the translation of an array of strings.
+ *
+ * @param string $source
+ * Translation source string
+ * @param string $translation
+ * Translation to check. Use empty string to check for a not existing
+ * translation.
+ * @param $langcode
+ * Language code of the language to translate to.
*/
- function testUpdateCompare() {
- // Create and login user.
- $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages'));
- $this->drupalLogin($admin_user);
+ function assertTranslation($source, $translation, $langcode, $message = '') {
+ $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', array(':source' => $source, ':langcode' => $langcode))->fetchField();
+ $this->assertEqual((string) $translation, (string) $db_translation, $message ? $message : format_string('Correct translation of %source (%language)', array('%source' => $source, '%language' => $langcode)));
+ }
+ /**
+ * Check if a list of translatable projects gets build.
+ */
+ function testUpdateProjects() {
module_load_include('compare.inc', 'locale');
- // Check if hidden modules are not included.
- $projects = locale_translation_project_list();
- $this->assertFalse(isset($projects['locale_test']), 'Hidden module not found');
-
// 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_disabled modules.
- state()->set('locale_translation_test_system_info_alter', TRUE);
-
- // Reset static system list caches to reflect info changes.
- drupal_static_reset('locale_translation_project_list');
- system_list_reset();
+ state()->set('locale.test_system_info_alter', TRUE);
// Check if interface translation data is collected from hook_info.
$projects = locale_translation_project_list();
+ $this->assertFalse(isset($projects['locale_test_disabled']), 'Hidden module not found');
$this->assertEqual($projects['locale_test']['info']['interface translation server pattern'], 'core/modules/locale/test/test.%language.po', 'Interface translation parameter found in project info.');
$this->assertEqual($projects['locale_test']['name'] , 'locale_test', format_string('%key found in project info.', array('%key' => 'interface translation project')));
+ }
- // Get the locale settings.
+ /**
+ * Check if a list of translatable projects can include hidden projects.
+ */
+ function testUpdateProjectsHidden() {
+ module_load_include('compare.inc', 'locale');
$config = config('locale.settings');
- // Check if disabled modules are detected.
- $config->set('translation.check_disabled_modules', TRUE)->save();
- drupal_static_reset('locale_translation_project_list');
- $projects = locale_translation_project_list();
- $this->assertTrue(isset($projects['locale_test_disabled']), 'Disabled module found');
+ // Make the test modules look like a normal custom module.
+ state()->set('locale.test_system_info_alter', TRUE);
- // Check the fully processed list of project data of both enabled and
- // disabled modules.
- $config->set('translation.check_disabled_modules', TRUE)->save();
- drupal_static_reset('locale_translation_project_list');
- $projects = locale_translation_get_projects();
- $this->assertEqual($projects['drupal']->name, 'drupal', 'Core project found');
- $this->assertEqual($projects['locale_test']->server_pattern, 'core/modules/locale/test/test.%language.po', 'Interface translation parameter found in project info.');
- $this->assertEqual($projects['locale_test_disabled']->status, '0', 'Disabled module found');
- $config->delete('translation.check_disabled_modules');
+ // Set test condition: include disabled modules when building a project list.
+ $edit = array(
+ 'check_disabled_modules' => TRUE,
+ );
+ $this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration'));
- // Return the locale test modules back to their hidden state.
- state()->delete('locale_translation_test_system_info_alter');
+ $projects = locale_translation_project_list();
+ $this->assertTrue(isset($projects['locale_test_disabled']), 'Disabled module found');
+ $this->assertTrue(isset($projects['locale_test']), 'Enabled module found');
}
/**
@@ -336,7 +364,7 @@ function testUpdateCheckStatus() {
$config = config('locale.settings');
// A flag is set to let the locale_test module replace the project data with
// a set of test projects.
- state()->set('locale_translation_test_projects', TRUE);
+ state()->set('locale.test_projects_alter', TRUE);
// Create local and remote translations files.
$this->setTranslationFiles();
@@ -351,7 +379,7 @@ function testUpdateCheckStatus() {
// Get status of translation sources at local file system.
$config->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)->save();
$this->drupalGet('admin/reports/translations/check');
- $result = state()->get('locale_translation_status');
+ $result = state()->get('locale.translation_status');
$this->assertEqual($result['contrib_module_one']['de']->type, 'local', 'Translation of contrib_module_one found');
$this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestamp_old, 'Translation timestamp found');
$this->assertEqual($result['contrib_module_two']['de']->type, 'local', 'Translation of contrib_module_two found');
@@ -365,9 +393,9 @@ function testUpdateCheckStatus() {
);
$this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration'));
- // Get status of translation sources at both local and remote the locations.
+ // Get status of translation sources at both local and remote locations.
$this->drupalGet('admin/reports/translations/check');
- $result = state()->get('locale_translation_status');
+ $result = state()->get('locale.translation_status');
$this->assertEqual($result['contrib_module_one']['de']->type, 'remote', 'Translation of contrib_module_one found');
$this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestamp_new, 'Translation timestamp found');
$this->assertEqual($result['contrib_module_two']['de']->type, 'local', 'Translation of contrib_module_two found');
@@ -379,24 +407,6 @@ function testUpdateCheckStatus() {
}
/**
- * Starting point:
- * Time stamp of translations:
- * contrib_module_one
- * remote: new
- * local: old
- * current: medium
- * contrib_module_two
- * remote: old
- * local: new
- * current: medium
- * contrib_module_three
- * remote: old
- * local: old
- * current: medium
- *
- */
-
- /**
* Test translation import from remote sources.
*
* Test conditions:
@@ -424,13 +434,13 @@ function testUpdateImportSourceRemote() {
$this->drupalPost('admin/reports/translations', array(), t('Update'));
// Check if the translation has been updated, using the status cache.
- $status = state()->get('locale_translation_status');
+ $status = state()->get('locale.translation_status');
$this->assertEqual($status['contrib_module_one']['de']->type, 'current', 'Translation of contrib_module_one found');
$this->assertEqual($status['contrib_module_two']['de']->type, 'current', 'Translation of contrib_module_two found');
$this->assertEqual($status['contrib_module_three']['de']->type, 'current', 'Translation of contrib_module_three found');
// Check the new translation status.
- // The static cache needs to be flushed firt to get the most recent data
+ // 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();
@@ -452,8 +462,7 @@ function testUpdateImportSourceRemote() {
}
/**
- * Test translation import from local sources
- *
+ * Test translation import from local sources.
*
* Test conditions:
* - Source: local files only
@@ -480,7 +489,7 @@ function testUpdateImportSourceLocal() {
$this->drupalPost('admin/reports/translations', array(), t('Update'));
// Check if the translation has been updated, using the status cache.
- $status = state()->get('locale_translation_status');
+ $status = state()->get('locale.translation_status');
$this->assertEqual($status['contrib_module_one']['de']->type, 'current', 'Translation of contrib_module_one found');
$this->assertEqual($status['contrib_module_two']['de']->type, 'current', 'Translation of contrib_module_two found');
$this->assertEqual($status['contrib_module_three']['de']->type, 'current', 'Translation of contrib_module_three found');
@@ -536,7 +545,7 @@ function testUpdateImportWithoutDirectory() {
$this->drupalPost('admin/reports/translations', array(), t('Update'));
// Check if the translation has been updated, using the status cache.
- $status = state()->get('locale_translation_status');
+ $status = state()->get('locale.translation_status');
$this->assertEqual($status['contrib_module_one']['de']->type, 'current', 'Translation of contrib_module_one found');
$this->assertEqual($status['contrib_module_two']['de']->type, 'current', 'Translation of contrib_module_two found');
$this->assertEqual($status['contrib_module_three']['de']->type, 'current', 'Translation of contrib_module_three found');
@@ -563,7 +572,8 @@ function testUpdateImportWithoutDirectory() {
}
/**
- * Test translation import with and only overwrite non-customized translations.
+ * Test translation import with a translations directory and only overwrite
+ * non-customized translations.
*
* Test conditions:
* - Source: remote and local files
@@ -600,7 +610,8 @@ function testUpdateImportModeNonCustomized() {
}
/**
- * Test translation import with and don't overwrite any translation.
+ * Test translation import with a translations directory and don't overwrite
+ * any translation.
*
* Test conditions:
* - Source: remote and local files
@@ -627,12 +638,153 @@ function testUpdateImportModeNone() {
$this->drupalPost('admin/reports/translations', array(), t('Update'));
// Check whether existing translations have (not) been overwritten.
- $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January');
- $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_customized', 'Translation of February');
- $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz', 'Translation of March');
- $this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
- $this->assertEqual(t('May', array(), array('langcode' => 'de')), 'Mai_customized', 'Translation of May');
- $this->assertEqual(t('June', array(), array('langcode' => 'de')), 'Juni', 'Translation of June');
- $this->assertEqual(t('Monday', array(), array('langcode' => 'de')), 'Montag', 'Translation of Monday');
+ $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');
}
+
+ /**
+ * Test automatic translation import when a module is enabled.
+ */
+ function testEnableDisableModule() {
+ // Check is there is no translation yet.
+ $this->assertTranslation('Tuesday', '', 'de');
+
+ // Enable a module.
+ $edit = array(
+ 'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
+ );
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+
+ // Check if translations have been imported.
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
+ array('%number' => 7, '%update' => 0, '%delete' => 0)), 'One translation file imported.');
+ $this->assertTranslation('Tuesday', 'Dienstag', 'de');
+
+ // Disable and uninstall a module.
+ $edit = array(
+ 'modules[Testing][locale_test_translate][enable]' => FALSE,
+ );
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $edit = array(
+ 'uninstall[locale_test_translate]' => 1,
+ );
+ $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall'));
+ $this->drupalPost(NULL, array(), t('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');
+ }
+
+ /**
+ * Test automatic translation import when a langauge is enabled.
+ *
+ * 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 langague from the database.
+ */
+ function testEnableDisableLanguage() {
+ // Enable a module.
+ $edit = array(
+ 'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
+ );
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+
+ // Check is there is no Dutch translation yet.
+ $this->assertTranslation('Extraday', '', 'nl');
+ $this->assertTranslation('Tuesday', 'Dienstag', 'de');
+
+ // Add a language
+ $edit = array(
+ 'predefined_langcode' => 'nl',
+ );
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+
+ // Check if the right number of translations are added.
+ $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
+ array('%number' => 8, '%update' => 0, '%delete' => 0)), 'One language added.');
+ $this->assertTranslation('Extraday', 'extra dag', 'nl');
+
+ // Check if the language data is added to the database.
+ $result = db_query("SELECT project FROM {locale_file} WHERE langcode='nl'")->fetchField();
+ $this->assertTrue((boolean) $result, 'Files removed from file history');
+
+ // Remove a language
+ $this->drupalPost('admin/config/regional/language/delete/nl', array(), t('Delete'));
+
+ // Check if the language data is removed from the database.
+ $result = db_query("SELECT project FROM {locale_file} WHERE langcode='nl'")->fetchField();
+ $this->assertFalse($result, 'Files removed from file history');
+
+ // Check is there is no more Duth translation.
+ $this->assertTranslation('Extraday', '', 'nl');
+ $this->assertTranslation('Tuesday', 'Dienstag', 'de');
+ }
+
}
+
+/*
+ function testAutomaticModuleTranslationImportLanguageEnable() {
+ // Enable a module.
+ $edit = array(
+ 'modules[Testing][locale_test_translate][enable]' => 'locale_test_translate',
+ );
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+
+ // Code for the language - manually set to match the test translation file.
+ $langcode = 'xx';
+ // The English name for the language.
+ $name = $this->randomName(16);
+
+ // Create a custom language.
+ $edit = array(
+ 'predefined_langcode' => 'custom',
+ 'langcode' => $langcode,
+ 'name' => $name,
+ 'direction' => '0',
+ );
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
+
+ // Ensure the translation file was automatically imported when language was
+ // added.
+ $this->assertText(t('One translation file imported.'), t('Language file automatically imported.'));
+ $this->assertText(t('A translation string was skipped because of disallowed or malformed HTML'), t('Language file automatically imported.'));
+
+ // Ensure strings were successfully imported.
+ $search = array(
+ 'string' => 'lundi',
+ 'langcode' => $langcode,
+ 'translation' => 'translated',
+ );
+ $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+ $this->assertNoText(t('No strings available.'), t('String successfully imported.'));
+
+ // Ensure multiline string was imported.
+ $search = array(
+ 'string' => 'Source string for multiline translation',
+ 'langcode' => $langcode,
+ 'translation' => 'all',
+ );
+ $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+ $this->assertText('Multiline translation string to make sure that import works with it.', t('String successfully imported.'));
+
+ // Ensure 'Allowed HTML source string' was imported but the translation for
+ // 'Another allowed HTML source string' was not because it contains invalid
+ // HTML.
+ $search = array(
+ 'string' => 'HTML source string',
+ 'langcode' => $langcode,
+ 'translation' => 'all',
+ );
+ $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+ $this->assertText('Allowed HTML source string', t('String successfully imported.'));
+ $this->assertNoText('Another allowed HTML source string', t('String with disallowed translation not imported.'));
+ }
+*/
\ No newline at end of file
diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc
index 1be5fda..7bb35ef 100644
--- a/core/modules/locale/locale.batch.inc
+++ b/core/modules/locale/locale.batch.inc
@@ -33,14 +33,24 @@ function locale_translation_batch_status_fetch_remote($source, &$context) {
$remote_file = $source->files['remote'];
$result = locale_translation_http_check($remote_file->uri);
- // Update the file object with the result data. In case of a redirect we
- // store the resulting uri.
- if ($result && !empty($result->updated)) {
- $remote_file->uri = isset($result->redirect_uri) ? $result->redirect_uri : $remote_file->uri;
- $remote_file->timestamp = $result->updated;
- $source->files['remote'] = $remote_file;
+ if ($result) {
+ // Update the file object with the result data. In case of a redirect we
+ // store the resulting uri. If a file is not found we don't update the
+ // file object, and store it unchanged.
+ if (isset($result->updated)) {
+ $remote_file->uri = isset($result->redirect_uri) ? $result->redirect_uri : $remote_file->uri;
+ $remote_file->timestamp = $result->updated;
+ $source->files['remote'] = $remote_file;
+ }
+ // Record success.
+ $context['results']['files'][$source->name] = $source->name;
+ }
+ else {
+ // An error occured when checking the file. Record the failure for
+ // reporting at the end of the batch.
+ $context['results']['failed_files'][] = $source->name;
}
- $context['results']['sources'][$source->name][$source->language] = $source;
+ $context['results']['sources'][$source->name][$source->langcode] = $source;
}
}
@@ -69,10 +79,13 @@ function locale_translation_batch_status_fetch_local($sources, &$context) {
// If remote data was collected before, we merge it into the newly
// collected result.
- if (isset($context['results']['sources'][$source->name][$source->language])) {
- $source->files['remote'] = $context['results']['sources'][$source->name][$source->language]->files['remote'];
+ if (isset($context['results']['sources'][$source->name][$source->langcode])) {
+ $source->files['remote'] = $context['results']['sources'][$source->name][$source->langcode]->files['remote'];
}
- $context['results']['sources'][$source->name][$source->language] = $source;
+
+ // Record success and store the updated source data.
+ $context['results']['files'][$source->name] = $source->name;
+ $context['results']['sources'][$source->name][$source->langcode] = $source;
}
}
}
@@ -83,7 +96,7 @@ function locale_translation_batch_status_fetch_local($sources, &$context) {
* In the preceding batch processes data of remote and local translation sources
* is collected. Here we compare the collected results and update the source
* object with the data of the most recent translation file. The end result is
- * stored in the 'locale_translation_status' state variable. Other
+ * stored in the 'locale.translation_status' state variable. Other
* processes can collect this data after the batch process is completed.
*
* @param array $context
@@ -115,8 +128,8 @@ function locale_translation_batch_status_compare(&$context) {
// If the project/language was translated before and it is more recent
// than the most recent translation, the translation is up to date. Which
// is marked in the source object with type "current".
- if (isset($history[$source->project][$source->language])) {
- $current = $history[$source->project][$source->language];
+ if (isset($history[$source->project][$source->langcode])) {
+ $current = $history[$source->project][$source->langcode];
// Add the current translation to the source object to save it in
// the status cache.
$source->files['current'] = $current;
@@ -152,14 +165,23 @@ function locale_translation_batch_status_compare(&$context) {
function locale_translation_batch_status_finished($success, $results) {
$t = get_t();
if ($success) {
- if (isset($results['sources']) && $results['sources']) {
+ if (isset($results['failed_files'])) {
+ if (module_exists('dblog')) {
+ $message = format_plural(count($results['failed_files']), 'One translation file could not be checked. See the log for details.', '@count translation files could not be checked. See the log for details.', array('@url' => url('admin/reports/dblog')));
+ }
+ else {
+ $message = format_plural(count($results['failed_files']), 'One translation files could not be checked. See the log for details.', '@count translation files could not be checked. See the log for details.');
+ }
+ drupal_set_message($message, 'error');
+ }
+ if (isset($results['files'])) {
drupal_set_message(format_plural(
count($results['sources']),
'Checked available interface translation updates for one project.',
'Checked available interface translation updates for @count projects.'
));
}
- else {
+ if (!isset($results['failed_files']) && !isset($results['files'])) {
drupal_set_message(t('Nothing to check.'));
}
}
@@ -183,8 +205,14 @@ function locale_translation_batch_status_finished($success, $results) {
* @see locale_translation_batch_fetch_update_status().
* @see locale_translation_batch_status_compare().
*/
-function locale_translation_batch_fetch_sources($projects, $languages, &$context) {
- $context['results']['input'] = locale_translation_load_sources($projects, $languages);
+function locale_translation_batch_fetch_sources($projects, $langcodes, &$context) {
+ $context['results']['input'] = locale_translation_load_sources($projects, $langcodes);
+
+ // If this batch operation is preceded by the status check operations, the
+ // results of those operation are stored in the context. We remove them here
+ // to keep the result records clean.
+ unset($context['results']['files']);
+ unset($context['results']['failed_files']);
}
/**
@@ -218,8 +246,6 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context)
$source->files['download'] = $file;
}
else {
- // @todo Error handling when download fails.
- watchdog('locale', 'Unable to download translation file @uri.', array('@uri' => $source->files['remote']->uri), WATCHDOG_ERROR);
$context['results']['failed_files'][] = $source->files['remote'];
}
$context['results']['sources'][$project][$langcode] = $source;
@@ -228,7 +254,7 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context)
}
/**
- * Batch process: Import translation file..
+ * Batch process: Import translation file.
*
* This batch operations imports either a local gettext file or a downloaded
* remote gettext file. In case of a downloaded file the location of the
@@ -242,10 +268,11 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context)
* @param string $langcode
* Language code.
* @param array $options
- * Array of import options. See locale_translate_batch_import_files().
+ * Array of import options.
* @param $context
* Batch context array.
*
+ * @see locale_translate_batch_import_files().
* @see locale_translation_batch_fetch_sources().
* @see locale_translation_batch_fetch_download().
* @see locale_translation_batch_fetch_update_status().
@@ -262,9 +289,9 @@ function locale_translation_batch_fetch_import($project, $langcode, $options, &$
// If we are working on a remote file we will import the downloaded file and
// use the result data from the
if ($source->type == 'remote') {
- if (isset($context['results']['sources'][$source->project][$source->language]->files['download'])) {
+ if (isset($context['results']['sources'][$source->project][$source->langcode]->files['download'])) {
$import_type = 'download';
- $source_result = $context['results']['sources'][$source->project][$source->language];
+ $source_result = $context['results']['sources'][$source->project][$source->langcode];
}
}
else {
@@ -272,31 +299,31 @@ function locale_translation_batch_fetch_import($project, $langcode, $options, &$
$source_result = $source;
}
- // @todo Fault handling. Using try/catch? For other batch functions too?
-
$file = $source_result->files[$import_type];
module_load_include('bulk.inc', 'locale');
$options += array(
'message' => 'Importing: ' . $file->filename,
);
-
+ // Import the translation file. For large files the batch operations is
+ // progressive and will be called repeatedly untill finished.
locale_translate_batch_import($file, $options, $context);
- // Import is finished.
+
+ // The import is finished.
if (isset($context['finished']) && $context['finished'] == 1) {
- // Import is successfull.
+ // The import is successfull.
if (isset($context['results']['files'][$file->uri])) {
$context['message'] = $t('Imported: %name.', array('%name' => $file->filename));
- // Store the data of imported source. This data can later be saved in {locale_file}.
+ // Keep the data of imported source. In following batch operations
+ // it will be saved in the {locale_file} table.
$source_result->files['import'] = $source_result->files[$source->type];
// Downloaded files are stored in the temporary files directory. On
// succesfull import, the files are moved to the local destination
- // directory if files should be kept locally. Otherwise the temporary
- // files are deleted.
+ // directory if files should be kept locally. Otherwise the
+ // temporary files are deleted.
if ($import_type == 'download' && variable_get('locale_translate_file_directory', conf_path() . '/files/translations') && isset($source_result->files['local'])) {
// Move the temporary file to the local translations directory.
- // @todo What to do if the file can not be moved? If it throws an error (watchdog), should we set a batch error?
if (file_unmanaged_move($file->uri, $source_result->files['local']->uri, FILE_EXISTS_REPLACE)) {
// The downloaded file is now moved to the local file location.
// From this point forward we can treat it as if we imported a
@@ -328,7 +355,7 @@ function locale_translation_batch_fetch_import($project, $langcode, $options, &$
}
}
}
- $context['results']['sources'][$source->project][$source->language] = $source_result;
+ $context['results']['sources'][$source->project][$source->langcode] = $source_result;
}
}
}
@@ -355,17 +382,18 @@ function locale_translation_batch_fetch_update_status(&$context) {
foreach ($context['results']['sources'] as $project => $langcodes) {
foreach ($langcodes as $langcode => $source) {
- // Store the state of the translation in {locale_file} table. During the
- // batch execution the data of imported file is kept in
- // $context['results']['sources'] will now be stored in the database.
+ // Store the state of the imported translations in {locale_file} table.
+ // During the batch execution data of imported files is temporary stored
+ // in $context['results']['sources']. Now is will be stored in the
+ // database. Afterwards the temporary import and download data can be
+ // deleted.
if (isset($source->files['import'])) {
$file = $source->files['import'];
locale_translation_update_file_history($file);
- // @todo Leave unset() disabled for debugging.
- //unset($source->files['import']);
+ unset($source->files['import']);
}
- // @todo Leave unset() disabled for debugging.
- //unset($source->files['download']);
+ unset($source->files['download']);
+
// The source data is now up to date. Data of local and/or remote source
// file is up to date including an updated time stamp. In a next batch
// operation this can be use to update the translation status.
@@ -398,13 +426,36 @@ function locale_translation_batch_fetch_finished($success, $results) {
* @return stdClass
* Result object containing the HTTP request headers, response code, headers,
* data, redirect status and updated timestamp.
+ * TRUE if the file is not found.
+ * FALSE if a fault occured.
*/
function locale_translation_http_check($uri, $headers = array()) {
$result = drupal_http_request($uri, array('headers' => $headers, 'method' => 'HEAD'));
- if ($result && $result->code == '200') {
- $result->updated = isset($result->headers['last-modified']) ? strtotime($result->headers['last-modified']) : 0;
+ if (!isset($result->error)) {
+ if ($result->code == 200) {
+ $result->updated = isset($result->headers['last-modified']) ? strtotime($result->headers['last-modified']) : 0;
+ }
+ return $result;
}
- return $result;
+ else {
+ switch ($result->code) {
+ case 404:
+ // File not found occurs when a translation file is not yet available
+ // at the translation server. But also if a custom module or custom
+ // theme does not define the location of a translation file. By default
+ // the file is checked at the translation server, but it will not be
+ // found there.
+ watchdog('locale', 'File not found: @uri.', array('@uri' => $uri));
+ return TRUE;
+ case 0:
+ watchdog('locale', 'Error occurred when trying to check @remote: @errormessage.', array('@errormessage' => $result->error, '@remote' => $uri), WATCHDOG_ERROR);
+ break;
+ default:
+ watchdog('locale', 'HTTP error @errorcode occurred when trying to check @remote.', array('@errorcode' => $result->code, '@remote' => $uri), WATCHDOG_ERROR);
+ break;
+ }
+ }
+ return FALSE;
}
/**
@@ -419,9 +470,9 @@ function locale_translation_http_check($uri, $headers = array()) {
* - "langcode": Translation language.
* - "version": Project version.
* - "filename": File name.
+ *
* @return object
- * File object if download successful.
- * FALSE on failure.
+ * File object if download was successful. FALSE on failure.
*/
function locale_translation_download_source($source_file) {
if ($uri = system_retrieve_file($source_file->uri, 'temporary://')) {
@@ -434,5 +485,6 @@ function locale_translation_download_source($source_file) {
$file->filename = $source_file->filename;
return $file;
}
+ watchdog('locale', 'Unable to download translation file @uri.', array('@uri' => $source->files['remote']->uri), WATCHDOG_ERROR);
return FALSE;
}
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index fd50243..586d626 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -266,112 +266,42 @@ function locale_translate_export_form_submit($form, &$form_state) {
}
/**
- * Sets a batch for a newly-added language.
+ * Get interface translation files present in the translations directory.
*
- * @param array $options
- * An array with options that can have the following elements:
- * - 'langcode': The language code, required.
- * - 'overwrite_options': Overwrite options array as defined in
- * Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array.
- * - 'customized': Flag indicating whether the strings imported from $file
- * are customized translations or come from a community source. Use
- * LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to
- * LOCALE_NOT_CUSTOMIZED.
- * - 'finish_feedback': Whether or not to give feedback to the user when the
- * batch is finished. Optional, defaults to TRUE.
- */
-function locale_translate_add_language_set_batch($options) {
- $options += array(
- 'overwrite_options' => array(),
- 'customized' => LOCALE_NOT_CUSTOMIZED,
- 'finish_feedback' => TRUE,
- );
- // See if we have language files to import for the newly added language,
- // collect and import them.
- if ($batch = locale_translate_batch_import_files($options)) {
- batch_set($batch);
- }
-}
-
-/**
- * Prepare a batch to import all translations.
+ * @param array $projects
+ * Project names from which to get the translation files and history.
+ * Defaults to all projects.
+ * @param array $langcodes
+ * Language codes from which to get the translation files and history.
+ * Defaults to all languagues
*
- * @param array $options
- * An array with options that can have the following elements:
- * - 'langcode': The language code. Optional, defaults to NULL, which means
- * that the language will be detected from the name of the files.
- * - 'overwrite_options': Overwrite options array as defined in
- * Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array.
- * - 'customized': Flag indicating whether the strings imported from $file
- * are customized translations or come from a community source. Use
- * LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to
- * LOCALE_NOT_CUSTOMIZED.
- * - 'finish_feedback': Whether or not to give feedback to the user when the
- * batch is finished. Optional, defaults to TRUE.
- *
- * @param $force
- * (optional) Import all available files, even if they were imported before.
- *
- * @todo
- * Integrate with update status to identify projects needed and integrate
- * l10n_update functionality to feed in translation files alike.
- * See http://drupal.org/node/1191488.
+ * @return array
+ * An array of interface translation files keyed by their URI.
*/
-function locale_translate_batch_import_files($options, $force = FALSE) {
- $options += array(
- 'overwrite_options' => array(),
- 'customized' => LOCALE_NOT_CUSTOMIZED,
- 'finish_feedback' => TRUE,
- );
+function locale_translate_get_interface_translation_files($projects = array(), $langcodes = array()) {
+ module_load_include('compare.inc', 'locale');
$files = array();
- if (!empty($options['langcode'])) {
- $langcodes = array($options['langcode']);
- }
- else {
- // If langcode was not provided, make sure to only import files for the
- // languages we have enabled.
- $langcodes = array_keys(language_list());
- }
- foreach ($langcodes as $langcode) {
- $files = array_merge($files, locale_translate_get_interface_translation_files($langcode));
- }
- if (!$force) {
- $result = db_select('locale_file', 'lf')
- ->fields('lf', array('langcode', 'uri', 'timestamp'))
- ->condition('langcode', $langcodes)
- ->execute()
- ->fetchAllAssoc('uri');
- foreach ($result as $uri => $info) {
- if (isset($files[$uri]) && filemtime($uri) <= $info->timestamp) {
- // The file is already imported and not changed since the last import.
- // Remove it from file list and don't import it again.
- unset($files[$uri]);
+ $projects = $projects ? $projects : array_keys(locale_translation_get_projects());
+ $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
+
+ // Scan the translations directory for files matching a name pattern
+ // containing a project name and language code: {project}.{langcode}.po or
+ // {project}-{version}.{langcode}.po.
+ // Only files of known projects and languages will be returned.
+ if ($directory = variable_get('locale_translate_file_directory', conf_path() . '/files/translations')) {
+ $result = file_scan_directory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', array('recurse' => FALSE));
+
+ foreach ($result as $filepath => $file) {
+ // Update the file object with project name and version from the file name.
+ $file = locale_translate_file_attach_properties($file);
+ if (in_array($file->project, $projects)) {
+ if (in_array($file->langcode, $langcodes)) {
+ $files[$file->uri] = $file;
+ }
}
}
}
- return locale_translate_batch_build($files, $options);
-}
-
-/**
- * Get an array of available interface translation file.
- *
- * @param $langcode
- * The langcode for the interface translation files. Pass NULL to get all
- * available interface translation files.
- *
- * @return array
- * An array of interface translation files.
- */
-function locale_translate_get_interface_translation_files($langcode = NULL) {
- $directory = variable_get('locale_translate_file_directory', conf_path() . '/files/translations');
- $return = file_scan_directory($directory, '!' . (!empty($langcode) ? '\.' . preg_quote($langcode, '!') : '') . '\.po$!', array('recurse' => FALSE));
-
- foreach ($return as $filepath => $file) {
- $file->uri = 'translations://' . $file->filename;
- $return[$file->uri] = $file;
- unset($return[$filepath]);
- }
- return $return;
+ return $files;
}
/**
@@ -430,16 +360,14 @@ function locale_translate_batch_build($files, $options) {
/**
* Perform interface translation import as a batch step.
*
- * @todo Rework description.
- * The given filepath is matched against ending with '{langcode}.po'. When
- * matched the filepath is added to batch context.
- *
- * @param $file
- * File object ... @todo
+ * @param object $file
+ * A file object of the gettext file to be imported. The file object must
+ * contain a language parameter (other than LANGUAGE_NOT_SPECIFIED). This
+ * is used as the language of the import.
*
* @param array $options
* An array with options that can have the following elements:
- * - 'langcode': The language code, required.
+ * - 'langcode': The language code.
* - 'overwrite_options': Overwrite options array as defined in
* Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array.
* - 'customized': Flag indicating whether the strings imported from $file
@@ -457,6 +385,7 @@ function locale_translate_batch_import($file, $options, &$context) {
'overwrite_options' => array(),
'customized' => LOCALE_NOT_CUSTOMIZED,
);
+
if (isset($file->langcode) && $file->langcode != LANGUAGE_NOT_SPECIFIED) {
try {
@@ -497,25 +426,24 @@ function locale_translate_batch_import($file, $options, &$context) {
$context['results']['files'][$file->uri] = $file;
$context['results']['languages'][$file->uri] = $file->langcode;
}
- // Add the values from the report to the stats for this file.
+
+ // Add the reported values to the statistics for this file.
+ // Each import itteration reports statistics in an array. The results of
+ // each itteration are added and merged here and stored per file.
if (!isset($context['results']['stats']) || !isset($context['results']['stats'][$file->uri])) {
$context['results']['stats'][$file->uri] = array();
}
foreach ($report as $key => $value) {
- if (is_numeric($value)) {
- $context['results']['stats'][$file->uri] += array($key => 0);
- $context['results']['stats'][$file->uri][$key] += $value;
- }
- elseif (is_array($value)) {
- $context['results']['stats'][$file->uri] += array($key => array());
- $context['results']['stats'][$file->uri][$key] = array_merge($context['results']['stats'][$file->uri][$key], $value);
- }
if (is_numeric($report[$key])) {
if (!isset($context['results']['stats'][$file->uri][$key])) {
$context['results']['stats'][$file->uri][$key] = 0;
}
$context['results']['stats'][$file->uri][$key] += $report[$key];
}
+ elseif (is_array($value)) {
+ $context['results']['stats'][$file->uri] += array($key => array());
+ $context['results']['stats'][$file->uri][$key] = array_merge($context['results']['stats'][$file->uri][$key], $value);
+ }
}
}
catch (Exception $exception) {
@@ -533,9 +461,17 @@ function locale_translate_batch_import($file, $options, &$context) {
* Contains a list of imported files.
*/
function locale_translate_batch_import_save($context) {
- foreach ($context['results']['files'] as $file) {
- $file->last_checked = REQUEST_TIME;
- locale_translation_update_file_history($file);
+ if (isset($context['results']['files'])) {
+ foreach ($context['results']['files'] as $file) {
+ // Update the file history if both project and version are known. This table
+ // is used by the automated translation update function which tracks
+ // translation status of module and themes in the system. Other data
+ // than installed projects would stay in the table for ever never.
+ if ($file->project && $file->version) {
+ $file->last_checked = REQUEST_TIME;
+ locale_translation_update_file_history($file);
+ }
+ }
}
}
@@ -556,8 +492,6 @@ function locale_translate_batch_finished($success, $results) {
drupal_set_message($message, 'error');
}
if (isset($results['files'])) {
- drupal_set_message(format_plural(count($results['files']), 'One translation file imported.', '@count translation files imported.'));
-
$skipped_files = array();
// If there are no results and/or no stats (eg. coping with an empty .po
// file), simply do nothing.
@@ -576,16 +510,21 @@ function locale_translate_batch_finished($success, $results) {
$strings = array_unique($strings);
$langcodes = array_unique(array_values($results['languages']));
}
- drupal_set_message(t('The translations were successfully imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
- watchdog('locale', 'The translations were successfully imported. %number translations added, %update updated and %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+ drupal_set_message(format_plural(count($results['files']),
+ 'One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.',
+ '@count translation files imported. %number translations were added, %update translations were updated and %delete translations were removed.',
+ array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)
+ ));
+ watchdog('locale', 'Translations imported: %number added, %update updated, %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+
if ($skips) {
if (module_exists('dblog')) {
- $message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => url('admin/reports/dblog')));
+ $message = format_plural($skips, 'One translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', array('@url' => url('admin/reports/dblog')));
}
else {
- $message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.');
+ $message = format_plural($skips, 'One translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.');
}
- drupal_set_message($message, 'error');
+ drupal_set_message($message, 'warning');
watchdog('locale', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING);
}
@@ -617,26 +556,42 @@ function locale_translate_file_create($filepath) {
/**
* Generate file properties from filename and options.
*
- * @param $file
+ * An attempt is made to determine the translation language, project name and
+ * project version from the file name. Supported file name patterns are:
+ * {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po.
+ * Alternatively the translation language can be set using the $options.
+ *
+ * @param object $file
+ * A file object of the gettext file to be imported.
* @param array $options
+ * An array with options:
+ * - 'langcode': The language code. Overrides the file language.
*
- * @return mixed
+ * @return object
+ * Modified file object.
*/
function locale_translate_file_attach_properties($file, $options = array()) {
- // @todo: Check if regex works with missing project and version.
- // @todo: move the language part to the form submit function?
- // The filename is either {langcode}.po or {prefix}.{langcode}.po.
- // Try to get the project name, project version and language code file name.
- // The filename is either {langcode}.po or {project}-{version}.{langcode}.po.
- // We use the language from either the $options or the file name.
- preg_match('!([a-z_]+)\-([0-9a-z\.\-\+]+)\.([^\./]+)\.po$!', $file->filename, $matches);
- $file->project = isset($matches[1]) ? $matches[1] : '';
- $file->version = isset($matches[2]) ? $matches[2] : '';
- if (isset($options['langcode'])) {
- $file->langcode = $options['langcode'];
- }
- elseif (isset($matches[3])) {
- $file->langcode = $matches[3];
+ // Extract project, verion and language code from the file name. Supported:
+ // {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po
+ preg_match('!
+ ( # project OR project and version OR emtpy (group 1)
+ ([a-z_]+) # project name (group 2)
+ \. # .
+ | # OR
+ ([a-z_]+) # project name (group 3)
+ \- # -
+ ([0-9a-z\.\-\+]+) # version (group 4)
+ \. # .
+ | # OR
+ ) # (empty)
+ ([^\./]+) # language code (group 5)
+ \. # .
+ po # po extension
+ $!x', $file->filename, $matches);
+ if (isset($matches[5])) {
+ $file->project = $matches[2] . $matches[3];
+ $file->version = $matches[4];
+ $file->langcode = isset($options['langcode']) ? $options['langcode'] : $matches[5];
}
else {
$file->langcode = LANGUAGE_NOT_SPECIFIED;
@@ -645,28 +600,32 @@ function locale_translate_file_attach_properties($file, $options = array()) {
}
/**
- * Deletes all interface translation files depending on the langcode.
+ * Deletes interface translation files and translation history records.
*
- * @param $langcode
- * A langcode or NULL. Pass NULL to delete all interface translation files.
+ * @param array $projects
+ * Project names from which to delete the translation files and history.
+ * Defaults to all projects.
+ * @param array $langcodes
+ * Language codes from which to delete the translation files and history.
+ * Defaults to all languagues
+ *
+ * @return boolean
+ * TRUE if files are removed sucessfully. FALSE if one or more files could
+ * not be deleted.
*/
-function locale_translate_delete_translation_files($langcode) {
- $files = locale_translate_get_interface_translation_files($langcode);
- $return = TRUE;
- if (!empty($files)) {
+// @todo Move this function to translation.inc?
+function locale_translate_delete_translation_files($projects = array(), $langcodes = array()) {
+ $fail = FALSE;
+ locale_translation_file_history_delete($projects, $langcodes);
+
+ // Delete all translation files from the translations directory.
+ if ($files = locale_translate_get_interface_translation_files($projects, $langcodes)) {
foreach ($files as $file) {
$success = file_unmanaged_delete($file->uri);
if (!$success) {
- $return = FALSE;
- }
- else {
- // Remove the registered translation file if any.
- db_delete('locale_file')
- ->condition('langcode', $langcode)
- ->condition('uri', $file->uri)
- ->execute();
+ $fail = TRUE;
}
}
}
- return $return;
+ return !$fail;
}
diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc
index a604082..333ee53 100644
--- a/core/modules/locale/locale.compare.inc
+++ b/core/modules/locale/locale.compare.inc
@@ -16,6 +16,8 @@
* Clear the project data table.
*/
function locale_translation_flush_projects() {
+ // Followup issue: http://drupal.org/node/1842362
+ // Replace {locale_project} table by state() variable(s).
db_truncate('locale_project')->execute();
}
@@ -113,6 +115,9 @@ function locale_translation_build_projects() {
'status' => $project->status,
))
->execute();
+
+ // Invalidate the cache of translatable projects.
+ locale_translation_clear_cache_projects();
}
return $projects;
}
@@ -220,7 +225,7 @@ function locale_translation_build_server_pattern($project, $template) {
'%project' => $project->name,
'%version' => $project->version,
'%core' => $project->core,
- '%language' => isset($project->language) ? $project->language : '%language',
+ '%language' => isset($project->langcode) ? $project->langcode : '%language',
);
return strtr($template, $variables);
}
@@ -237,7 +242,6 @@ function locale_translation_build_server_pattern($project, $template) {
* Available sources indexed by project and language.
*/
function locale_translation_check_projects($projects = array(), $langcodes = array()) {
-// @todo What happens if LOCALE_TRANSLATION_USE_SOURCE_LOCAL and no local file is available??
if (config('locale.settings')->get('translation.use_source') == LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL) {
// Retrieve the status of both remote and local translation sources by
// using a batch process.
@@ -255,7 +259,7 @@ function locale_translation_check_projects($projects = array(), $langcodes = arr
* A batch process is used to check for po files at remote locations and (when
* configured) to check for po files in the local file system. The most recent
* translation source states are stored in the state variable
- * 'locale_translation_status'.
+ * 'locale.translation_status'.
*
* @param array $projects
* Array of project names to check. Defaults to all translatable projects.
@@ -274,23 +278,23 @@ function locale_translation_check_projects_batch($projects = array(), $langcodes
* The batch process fetches the state of both remote and (if configured) local
* translation files. The data of the most recent translation is stored per
* per project and per language. This data is stored in a state variable
- * 'locale_translation_status'. The timestamp it was last updated is stored
- * in the state variable 'locale_translation_status_last_update'.
+ * 'locale.translation_status'. The timestamp it was last updated is stored
+ * in the state variable 'locale.translation_last_checked'.
*
* @param array $projects
* Array of project names for which to check the state of translation files.
* Defaults to all translatable projects.
- * @param array $languages
+ * @param array $langcodes
* Array of language codes. Defaults to all translatable languages.
*
* @return array
* Batch definition array.
*/
-function locale_translation_batch_status_build($projects = array(), $languages = array()) {
+function locale_translation_batch_status_build($projects = array(), $langcodes = array()) {
$projects = $projects ? $projects : array_keys(locale_translation_get_projects());
- $languages = $languages ? $languages : array_keys(locale_translatable_language_list());
+ $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
- $operations = _locale_translation_batch_status_operations($projects, $languages);
+ $operations = _locale_translation_batch_status_operations($projects, $langcodes);
$batch = array(
'operations' => $operations,
@@ -335,7 +339,7 @@ function _locale_translation_batch_status_operations($projects, $langcodes) {
*
* Only po files in the local file system are checked. Any remote translation
* sources will be ignored. Results are stored in the state variable
- * 'locale_translation_status'.
+ * 'locale.translation_status'.
*
* Projects may contain a server_pattern option containing a pattern of the
* path to the po source files. If no server_pattern is defined the default
@@ -346,12 +350,12 @@ function _locale_translation_batch_status_operations($projects, $langcodes) {
* @param array $projects
* Array of project names for which to check the state of translation files.
* Defaults to all translatable projects.
- * @param array $languages
+ * @param array $langcodes
* Array of language codes. Defaults to all translatable languages.
*/
-function locale_translation_check_projects_local($projects = array(), $languages = array()) {
+function locale_translation_check_projects_local($projects = array(), $langcodes = array()) {
$projects = locale_translation_get_projects($projects);
- $languages = $languages ? $languages : array_keys(locale_translatable_language_list());
+ $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
$history = locale_translation_get_file_history();
$results = array();
@@ -359,7 +363,7 @@ function locale_translation_check_projects_local($projects = array(), $languages
// available. When found the source object is updated with the appropriate
// type and timestamp of the po file.
foreach ($projects as $name => $project) {
- foreach ($languages as $langcode) {
+ foreach ($langcodes as $langcode) {
$source = locale_translation_source_build($project, $langcode);
if (locale_translation_source_check_file($source)) {
$source->type = 'local';
@@ -370,8 +374,8 @@ function locale_translation_check_projects_local($projects = array(), $languages
// If the project/language was translated before and it is more recent
// than the most recent translation, the translation is up to date. Which
// is marked in the source object with type "current".
- if (isset($history[$source->project][$source->language])) {
- $current = $history[$source->project][$source->language];
+ if (isset($history[$source->project][$source->langcode])) {
+ $current = $history[$source->project][$source->langcode];
// Add the current translation to the source object to save it in
// the status cache.
$source->files['current'] = $current;
diff --git a/core/modules/locale/locale.fetch.inc b/core/modules/locale/locale.fetch.inc
index a6f2792..e5278e7 100755
--- a/core/modules/locale/locale.fetch.inc
+++ b/core/modules/locale/locale.fetch.inc
@@ -16,7 +16,7 @@
* @param array $projects
* Array of project names for which to update the translations. Defaults to
* all translatable projects.
- * @param array $languages
+ * @param array $langcodes
* Array of language codes. Defaults to all translatable languages.
* @param array $options
* Array of import options. See locale_translate_batch_import_files().
@@ -24,13 +24,15 @@
* @return array
* Batch definition array.
*/
-function locale_translation_batch_update_build($projects = array(), $languages = array(), $options = array()) {
+function locale_translation_batch_update_build($projects = array(), $langcodes = array(), $options = array()) {
+ // @todo Merge compare.inc and fetch.inc? Merge with batch.inc?
+ module_load_include('compare.inc', 'locale');
+ $t = get_t();
$projects = $projects ? $projects : array_keys(locale_translation_get_projects());
- $languages = $languages ? $languages : array_keys(locale_translatable_language_list());
+ $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
- $t = get_t();
- $operations = _locale_translation_batch_status_operations($projects, $languages);
- $operations = array_merge($operations, _locale_translation_fetch_operations($projects, $languages, $options));
+ $operations = _locale_translation_batch_status_operations($projects, $langcodes);
+ $operations = array_merge($operations, _locale_translation_fetch_operations($projects, $langcodes, $options));
$batch = array(
'operations' => $operations,
@@ -49,7 +51,7 @@ function locale_translation_batch_update_build($projects = array(), $languages =
* @param array $projects
* Array of project names for which to check the state of translation files.
* Defaults to all translatable projects.
- * @param array $languages
+ * @param array $langcodes
* Array of language codes. Defaults to all translatable languages.
* @param array $options
* Array of import options. See locale_translate_batch_import_files().
@@ -57,13 +59,13 @@ function locale_translation_batch_update_build($projects = array(), $languages =
* @return array
* Batch definition array.
*/
-function locale_translation_batch_fetch_build($projects = array(), $languages = array(), $options = array()) {
+function locale_translation_batch_fetch_build($projects = array(), $langcodes = array(), $options = array()) {
$projects = $projects ? $projects : array_keys(locale_translation_get_projects());
- $languages = $languages ? $languages : array_keys(locale_translatable_language_list());
+ $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
$t = get_t();
$batch = array(
- 'operations' => _locale_translation_fetch_operations($projects, $languages, $options),
+ 'operations' => _locale_translation_fetch_operations($projects, $langcodes, $options),
'title' => $t('Updating translation.'),
'init_message' => $t('Downloading and importing files.'),
'error_message' => $t('Error importing interface translations'),
@@ -79,7 +81,7 @@ function locale_translation_batch_fetch_build($projects = array(), $languages =
* @param array $projects
* Array of project names for which to check the state of translation files.
* Defaults to all translatable projects.
- * @param array $languages
+ * @param array $langcodes
* Array of language codes. Defaults to all translatable languages.
* @param array $options
* Array of import options.
@@ -87,14 +89,14 @@ function locale_translation_batch_fetch_build($projects = array(), $languages =
* @return array
* Array of batch operations.
*/
-function _locale_translation_fetch_operations($projects, $languages, $options) {
+function _locale_translation_fetch_operations($projects, $langcodes, $options) {
$operations = array();
$config = config('locale.settings');
- $operations[] = array('locale_translation_batch_fetch_sources', array($projects, $languages));
+ $operations[] = array('locale_translation_batch_fetch_sources', array($projects, $langcodes));
foreach ($projects as $project) {
- foreach ($languages as $langcode) {
+ foreach ($langcodes as $langcode) {
$operations[] = array('locale_translation_batch_fetch_download', array($project, $langcode));
$operations[] = array('locale_translation_batch_fetch_import', array($project, $langcode, $options));
}
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index f53ec9b..db230ff 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -251,7 +251,7 @@ function locale_stream_wrappers() {
'name' => t('Translation files'),
'class' => 'Drupal\locale\TranslationsStream',
'description' => t('Translation files'),
- 'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
+ 'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
),
);
return $wrappers;
@@ -288,11 +288,10 @@ function locale_language_delete($language) {
// Remove interface translation files.
module_load_include('inc', 'locale', 'locale.bulk');
- locale_translate_delete_translation_files($language->langcode);
-
- _locale_invalidate_js($language->langcode);
+ locale_translate_delete_translation_files(array(), array($language->langcode));
// Changing the language settings impacts the interface:
+ _locale_invalidate_js($language->langcode);
cache('page')->flush();
// Clearing all locale cache from database
@@ -448,6 +447,13 @@ function locale_modules_installed($modules) {
}
/**
+ * Implements hook_modules_uninstalled().
+ */
+function locale_modules_uninstalled($modules) {
+ locale_system_remove($modules);
+}
+
+/**
* Implements hook_themes_enabled().
*
* @todo This is technically wrong. We must not import upon enabling, but upon
@@ -458,34 +464,80 @@ function locale_themes_enabled($themes) {
}
/**
+ * Implements hook_themes_disabled().
+ */
+function locale_themes_disabled($themes) {
+ locale_system_remove($themes);
+}
+
+/**
* Imports translations when new modules or themes are installed.
*
- * This function will either import translation for the component change
- * right away, or start a batch if more files need to be imported.
+ * This function will start a batch to import translations for the added
+ * components.
*
- * @param $components
+ * @param array $components
* An array of component (theme and/or module) names to import
* translations for.
- *
- * @todo
- * This currently imports all .po files available, independent of
- * $components. Once we integrated with update status for project
- * identification, we should revisit and only import files for the
- * identified projects for the components.
*/
function locale_system_update($components) {
// Skip running the translation imports if in the installer,
// because it would break out of the installer flow. We have
// built-in support for translation imports in the installer.
if (!drupal_installation_attempted()) {
- include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc';
- if ($batch = locale_translate_batch_import_files(array(), TRUE)) {
+ module_load_include('compare.inc', 'locale');
+
+ // Update the list of translatable projects and start the import batch.
+ // Only when new projects are added the update batch will be triggered. Not
+ // each enabled module will introduce a new project. E.g. sub modules.
+ $projects = array_keys(locale_translation_build_projects());
+ if ($components = array_intersect($components, $projects)) {
+ module_load_include('fetch.inc', 'locale');
+ // Get translation status of the projects, download and update translations.
+ $options = _locale_translation_default_update_options();
+ $batch = locale_translation_batch_update_build($components, array(), $options);
batch_set($batch);
}
}
}
/**
+ * Delete translation history of modules and themes.
+ *
+ * Only the translation history is removed, not the source strings or
+ * translations. This is not possible because strings are shared between
+ * modules and we have no record of which string is used by which module.
+ *
+ * @param array $components
+ * An array of component (theme and/or module) names to remove
+ * translation history.
+ */
+function locale_system_remove($components) {
+ module_load_include('compare.inc', 'locale');
+
+ // Only when projects are removed, the translation files and records will be
+ // deleted. Not each disabled module will remove a project. E.g. sub modules.
+ $projects = array_keys(locale_translation_get_projects());
+ if ($components = array_intersect($components, $projects)) {
+ locale_translation_file_history_delete($components);
+
+ // Remove translation files.
+ module_load_include('inc', 'locale', 'locale.bulk');
+ locale_translate_delete_translation_files($components, array());
+
+ // Remove translatable projects.
+ // Followup issue http://drupal.org/node/1842362 to replace the
+ // {locale_project} table. Then change this to a function call.
+ db_delete('locale_project')
+ ->condition('name', $components)
+ ->execute();
+
+ // Clear the translation status.
+ locale_translation_clear_status();
+ }
+}
+
+/**
* Implements hook_js_alter().
*
* This function checks all JavaScript files currently added via drupal_add_js()
@@ -676,8 +728,11 @@ function locale_form_language_admin_add_form_alter_submit($form, $form_state) {
$langcode = $form_state['values']['predefined_langcode'];
}
- include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc';
- locale_translate_add_language_set_batch(array('langcode' => $langcode));
+ // Download and import translations for the newly added language.
+ module_load_include('fetch.inc', 'locale');
+ $options = _locale_translation_default_update_options();
+ $batch = locale_translation_batch_update_build(array(), array($langcode), $options);
+ batch_set($batch);
}
/**
@@ -813,13 +868,21 @@ function locale_translation_update_file_history($file) {
/**
* Delete the history of downloaded translations.
*
- * @param string $langcode
- * Language code of the file history to be deleted.
+ * @param array $projects
+ * Project name(s) to be deleted from the file history. If both project(s) and
+ * language code(s) are specified the conditions will be ANDed.
+ * @param array $langcode
+ * Language code(s) to be deleted from the file history.
*/
-function locale_translation_file_history_delete($langcode) {
- db_delete('locale_file')
- ->condition('language', $langcode)
- ->execute();
+function locale_translation_file_history_delete($projects = array(), $langcodes = array()) {
+ $query = db_delete('locale_file');
+ if (!empty($projects)) {
+ $query->condition('project', $projects);
+ }
+ if (!empty($langcodes)) {
+ $query->condition('langcode', $langcodes);
+ }
+ $query->execute();
}
/**
@@ -829,7 +892,10 @@ function locale_translation_file_history_delete($langcode) {
* Array of translation source data, structured by project name and langcode.
*/
function locale_translation_status_save($data) {
- $status = state()->get('locale_translation_status');
+ // Followup issue: http://drupal.org/node/1842362
+ // Split status storage per module/language and expire individually. This will
+ // improve performance for large sites.
+ $status = state()->get('locale.translation_status');
$status = empty($status) ? array() : $status;
// Merge the new data into the existing structured status array.
@@ -839,16 +905,16 @@ function locale_translation_status_save($data) {
}
}
- state()->set('locale_translation_status', $status);
- state()->set('locale_translation_status_last_update', REQUEST_TIME);
+ state()->set('locale.translation_status', $status);
+ state()->set('locale.translation_last_checked', REQUEST_TIME);
}
/**
* Clear the translation status cache.
*/
function locale_translation_clear_status() {
- state()->delete('locale_translation_status');
- state()->delete('locale_translation_status_last_update');
+ state()->delete('locale.translation_status');
+ state()->delete('locale.translation_last_checked');
}
/**
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index f01a6ab..f0c05e6 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -455,11 +455,14 @@ function locale_translate_edit_form_submit($form, &$form_state) {
function locale_translation_manual_status() {
module_load_include('compare.inc', 'locale');
- // @todo Do we need to clear the cache here before loading the projects?
+ // Check translation status of all translatable project in all languages.
+ // First we clear the cached list of projects. Although not strictly
+ // nescessary, this is helpfull in case the project list is out of sync.
locale_translation_flush_projects();
locale_translation_check_projects();
- // Execute a batch if required.
+ // Execute a batch if required. A batch is only used when remote files
+ // are checked.
if (batch_get()) {
batch_process('admin/reports/translations');
}
@@ -483,7 +486,7 @@ function locale_translate_settings($form, &$form_state) {
'1' => t('Daily'),
'7' => t('Weekly'),
),
- '#description' => t('Select how frequently you want to check for new interface translations for your currently installed modules and themes.'),
+ '#description' => t('Select how frequently you want to check for new interface translations for your currently installed modules and themes. Check updates now.', array('@url' => url('admin/reports/translations/check'))),
);
$form['check_disabled_modules'] = array(
@@ -504,8 +507,8 @@ function locale_translate_settings($form, &$form_state) {
'#title' => t('Translation source'),
'#default_value' => $config->get('translation.use_source'),
'#options' => array(
- LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL => t('Remote and local'),
- LOCALE_TRANSLATION_USE_SOURCE_LOCAL => t('Local only'),
+ LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL => t('Drupal translation server and local files'),
+ LOCALE_TRANSLATION_USE_SOURCE_LOCAL => t('Local files only'),
),
'#description' => t('The source of translation files for automatic interface translation.') . ' ' . $description,
);
@@ -535,7 +538,9 @@ function locale_translate_settings($form, &$form_state) {
}
/**
- * @todo
+ * Form validation handler for locale_translate_settings().
+ *
+ * @see locale_translate_settings()
*/
function locale_translate_settings_validate($form, &$form_state) {
if (empty($form['#translation_directory']) && $form_state['values']['use_source'] == LOCALE_TRANSLATION_USE_SOURCE_LOCAL) {
@@ -544,7 +549,9 @@ function locale_translate_settings_validate($form, &$form_state) {
}
/**
- * @todo
+ * Form submission handler for locale_translate_settings().
+ *
+ * @see locale_translate_settings()
*/
function locale_translate_settings_submit($form, &$form_state) {
$values = $form_state['values'];
@@ -588,7 +595,7 @@ function locale_translation_status_form($form, &$form_state) {
//locale_translation_flush_projects();
$languages = locale_translatable_language_list();
- $status = state()->get('locale_translation_status');
+ $status = state()->get('locale.translation_status');
if (!$languages) {
drupal_set_message(t('No translatable languages available. Add language first.', array('@add_lanuage' => url('admin/config/regional/language'))), 'warning');
@@ -617,24 +624,16 @@ function locale_translation_status_form($form, &$form_state) {
*/
function locale_translation_status_form_submit($form, &$form_state) {
module_load_include('fetch.inc', 'locale');
- $config = config('locale.settings');
-
$langcodes = array_filter($form_state['values']['langcodes']);
- // Set the batch import options.
- // @todo move to helper function?
- $options = array(
- 'customized' => LOCALE_NOT_CUSTOMIZED,
- 'overwrite_options' => array(
- 'not_customized' => $config->get('translation.overwrite_not_customized'),
- 'customized' => $config->get('translation.overwrite_customized'),
- ),
- );
+ // Set the translation import options. This determines if existing
+ // translations will be overwritten by imported strings.
+ $options = _locale_translation_default_update_options();
// If the status was updated recently we can immediately start fetching the
// translation updates. If the status is expired we clear it an run a batch to
// update the status and then fetch the translation updates.
- $last_checked = state()->get('locale_translation_status_last_update');
+ $last_checked = state()->get('locale.translation_last_checked');
if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) {
locale_translation_clear_status();
$batch = locale_translation_batch_update_build(array(), $langcodes, $options);
diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc
index a397001..a250e4d 100644
--- a/core/modules/locale/locale.translation.inc
+++ b/core/modules/locale/locale.translation.inc
@@ -6,17 +6,6 @@
*/
/**
- * Threshold for timestamp comparison.
- *
- * Eliminates a difference between the download time and the actual .po file
- * timestamp in seconds. The download time is stored in the database in
- * {locale_file}.timestamp.
- */
-// @todo Is this still required since we use filemtime() to get the timestamp of
-// downloaded and imported files?
-const LOCALE_TRANSLATION_TIMESTAMP_THRESHOLD = 2;
-
-/**
* Comparison result of source files timestamps.
*
* Timestamp of source 1 is less than the timestamp of source 2.
@@ -71,6 +60,7 @@ function locale_translation_get_projects($project_names = array()) {
// http://drupal.org/node/1777106 is a follow-up issue to make the check for
// possible out-of-date project information more robust.
if ($result->rowCount() == 0 && module_exists('update')) {
+ module_load_include('compare.inc', 'locale');
// At least the core project should be in the database, so we build the
// data if none are found.
locale_translation_build_projects();
@@ -90,29 +80,36 @@ function locale_translation_get_projects($project_names = array()) {
}
/**
+ * Clear the projects cache.
+ */
+function locale_translation_clear_cache_projects() {
+ drupal_static('locale_translation_get_projects', array());
+}
+
+/**
* Load cached translation sources containing current translation status.
*
* @param array $projects
* Array of project names. Defaults to all translatable projects.
- * @param array $languages
+ * @param array $langcodes
* Array of language codes. Defaults to all translatable languages.
*
* @return array
- * Array of source objects. Keyed by :.
+ * Array of source objects. Keyed with :.
*
* @see locale_translation_source_build().
*/
-function locale_translation_load_sources($projects = NULL, $languages = NULL) {
+function locale_translation_load_sources($projects = NULL, $langcodes = NULL) {
$sources = array();
$projects = $projects ? $projects : array_keys(locale_translation_get_projects());
- $languages = $languages ? $languages : array_keys(locale_translatable_language_list());
+ $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
// Load source data from locale_translation_status cache.
- $status = state()->get('locale_translation_status');
+ $status = state()->get('locale.translation_status');
// Use only the selected projects and languages for update.
foreach($projects as $project) {
- foreach ($languages as $langcode) {
+ foreach ($langcodes as $langcode) {
$sources[$project . ':' . $langcode] = isset($status[$project][$langcode]) ? $status[$project][$langcode] : NULL;
}
}
@@ -124,22 +121,23 @@ function locale_translation_load_sources($projects = NULL, $languages = NULL) {
*
* @param array $projects
* Array of project names. Defaults to all translatable projects.
- * @param array $languages
+ * @param array $langcodes
* Array of language codes. Defaults to all translatable languages.
*
* @return array
- * Array of source objects. Keyed by :.
+ * Array of source objects. Keyed with :.
*
* @see locale_translation_source_build().
*/
-function locale_translation_build_sources($projects = array(), $languages = array()) {
+function locale_translation_build_sources($projects = array(), $langcodes = array()) {
+ $sources = array();
$projects = locale_translation_get_projects($projects);
- $languages = $languages ? $languages : array_keys(locale_translatable_language_list());
+ $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
foreach ($projects as $project) {
- foreach ($languages as $langcode) {
+ foreach ($langcodes as $langcode) {
$source = locale_translation_source_build($project, $langcode);
- $sources[$source->name . ':' . $source->language] = $source;
+ $sources[$source->name . ':' . $source->langcode] = $source;
}
}
return $sources;
@@ -229,11 +227,14 @@ function locale_translation_source_check_file(&$source) {
* - "keep": TRUE to keep the downloaded file.
*/
function locale_translation_source_build($project, $langcode, $filename = NULL) {
+ // Followup issue: http://drupal.org/node/1842380
+ // Convert $source object to a TranslatableProject class and use a typed class
+ // for $source-file.
+
// Create a source object with data of the project object.
$source = clone $project;
$source->project = $project->name;
- // @todo Change this to langcode.
- $source->language = $langcode;
+ $source->langcode = $langcode;
$filename = $filename ? $filename : config('locale.settings')->get('translation.default_filename');
@@ -317,7 +318,7 @@ function _locale_translation_file_is_remote($uri) {
*/
function _locale_translation_source_compare($source1, $source2) {
if (isset($source1->timestamp) && isset($source2->timestamp)) {
- if (abs($source1->timestamp - $source2->timestamp) < LOCALE_TRANSLATION_TIMESTAMP_THRESHOLD) {
+ if ($source1->timestamp == $source2->timestamp) {
return LOCALE_TRANSLATION_SOURCE_COMPARE_EQ;
}
else {
@@ -334,3 +335,20 @@ function _locale_translation_source_compare($source1, $source2) {
return LOCALE_TRANSLATION_SOURCE_COMPARE_EQ;
}
}
+
+/**
+ * Return default import options for translation update.
+ *
+ * @return array
+ * Array of translation import options.
+ */
+function _locale_translation_default_update_options() {
+ $config = config('locale.settings');
+ return array(
+ 'customized' => LOCALE_NOT_CUSTOMIZED,
+ 'overwrite_options' => array(
+ 'not_customized' => $config->get('translation.overwrite_not_customized'),
+ 'customized' => $config->get('translation.overwrite_customized'),
+ ),
+ );
+}
\ No newline at end of file
diff --git a/core/modules/locale/tests/modules/locale_test/locale_test.install b/core/modules/locale/tests/modules/locale_test/locale_test.install
index 8f04e0f..3d9dff3 100644
--- a/core/modules/locale/tests/modules/locale_test/locale_test.install
+++ b/core/modules/locale/tests/modules/locale_test/locale_test.install
@@ -10,6 +10,6 @@
*/
function locale_test_uninstall() {
// Clear variables.
- state()->delete('locale_translation_test_system_info_alter');
- state()->delete('locale_translation_test_projects');
+ state()->delete('locale.test_system_info_alter');
+ state()->delete('locale.test_projects_alter');
}
diff --git a/core/modules/locale/tests/modules/locale_test/locale_test.module b/core/modules/locale/tests/modules/locale_test/locale_test.module
index e084915..a77472f 100644
--- a/core/modules/locale/tests/modules/locale_test/locale_test.module
+++ b/core/modules/locale/tests/modules/locale_test/locale_test.module
@@ -16,7 +16,7 @@ function locale_test_system_info_alter(&$info, $file, $type) {
// By default the locale_test modules are hidden and have a project specified.
// To test the module detection proces by locale_project_list() the
// test modules should mimic a custom module. I.e. be non-hidden.
- if (state()->get('locale_translation_test_system_info_alter')) {
+ if (state()->get('locale.test_system_info_alter')) {
if ($file->name == 'locale_test' || $file->name == 'locale_test_disabled') {
// Don't hide the module.
$info['hidden'] = FALSE;
@@ -33,11 +33,11 @@ function locale_test_system_info_alter(&$info, $file, $type) {
* names, versions, timestamps etc must be fixed because they must match the
* files created by the test script.
*
- * The "locale_translation_test_projects" state variable must be set by the
+ * The "locale.test_projects_alter" state variable must be set by the
* test script in order for this hook to take effect.
*/
function locale_test_locale_translation_projects_alter(&$projects) {
- if (state()->get('locale_translation_test_projects')) {
+ if (state()->get('locale.test_projects_alter')) {
// Instead of the default ftp.drupal.org we use the file system of the test
// instance to simulate a remote file location.
diff --git a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.info b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.info
new file mode 100644
index 0000000..5a68a07
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.info
@@ -0,0 +1,9 @@
+name = Locale test translate
+description = Translation test module for locale module testing.
+package = Testing
+version = 1.3
+core = 8.x
+
+; Definitions for interface translations.
+interface translation project = locale_test_translate
+interface translation server pattern = core/modules/locale/tests/test.%language.po
diff --git a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module
new file mode 100644
index 0000000..ede02fb
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module
@@ -0,0 +1,6 @@
+ 1);\\n"
+
+msgid "Monday"
+msgstr "maandag"
+
+msgid "Tuesday"
+msgstr "dinsdag"
+
+msgid "Wednesday"
+msgstr "woensdag"
+
+msgid "Thursday"
+msgstr "donderdag"
+
+msgid "Extraday"
+msgstr "extra dag"
+
+msgid "Friday"
+msgstr "vrijdag"
+
+msgid "Saturday"
+msgstr "zaterdag"
+
+msgid "Sunday"
+msgstr "zondag"