diff --git a/core/lib/Drupal/Component/Gettext/BatchStateInterface.php b/core/lib/Drupal/Component/Gettext/BatchStateInterface.php
new file mode 100644
index 0000000..df4e59e
--- /dev/null
+++ b/core/lib/Drupal/Component/Gettext/BatchStateInterface.php
@@ -0,0 +1,50 @@
+
+ * class MyReader implements BatchStateInterface {
+ * function __construct(){
+ * // empty
+ * }
+ * ...
+ * function getState() {
+ * return array(
+ * 'class' => __CLASS__,
+ * 'my_key' => 'my value',
+ * );
+ * }
+ * }
+ *
+ */
+interface BatchStateInterface {
+
+ /**
+ * Returns the current state used for resetting state later on.
+ *
+ * The state is used to reconstruct the state of the object by calling
+ * setState().
+ *
+ * The Class implemeting this interface must have an empty constructor.
+ *
+ * @return array
+ * key/value pairs of which one must be 'class' => __CLASS__
+ */
+ function getState();
+
+ /**
+ * Sets the object ready to roll.
+ *
+ * After calling setState it is assumed the object is ready to do it's work.
+ */
+ function setState(array $state);
+}
diff --git a/core/lib/Drupal/Component/Gettext/PoStreamReader.php b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
index bd916a7..0e08667 100644
--- a/core/lib/Drupal/Component/Gettext/PoStreamReader.php
+++ b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
@@ -9,6 +9,7 @@ namespace Drupal\Component\Gettext;
use Drupal\Component\Gettext\PoReaderInterface;
use Drupal\Component\Gettext\PoStreamInterface;
+use Drupal\Component\Gettext\BatchStateInterface;
use Drupal\Component\Gettext\PoHeader;
/**
@@ -17,7 +18,7 @@ use Drupal\Component\Gettext\PoHeader;
* The PO file format parsing is implemented according to the documentation at
* http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
*/
-class PoStreamReader implements PoStreamInterface, PoReaderInterface {
+class PoStreamReader implements PoStreamInterface, PoReaderInterface, BatchStateInterface {
/**
* Source line number of the stream being parsed.
@@ -205,6 +206,36 @@ class PoStreamReader implements PoStreamInterface, PoReaderInterface {
}
/**
+ * Implements Drupal\Component\Gettext\BatchStateInterface::setState().
+ */
+ public function setState(array $state) {
+ $this->setURI($state['uri']);
+ $this->setLangcode($state['langcode']);
+ // Make sure to (re)read the PoHeader
+ $this->open();
+ // Move to last read position.
+ if (isset($state['seekpos'])) {
+ fseek($this->_fd, $state['seekpos']);
+ }
+ if (isset($state['lineno'])) {
+ $this->lineno = $state['lineno'];
+ }
+ }
+
+ /**
+ * Implements Drupal\Component\Gettext\BatchStateInterface::getState().
+ */
+ public function getState() {
+ return array(
+ 'class' => __CLASS__,
+ 'uri' => $this->_uri,
+ 'langcode' => $this->_langcode,
+ 'seekpos' => ftell($this->_fd),
+ 'lineno' => $this->lineno,
+ );
+ }
+
+ /**
* Read the header from the PO stream.
*
* The header is a special case PoItem, using the empty string as source and
diff --git a/core/modules/locale/lib/Drupal/locale/Gettext.php b/core/modules/locale/lib/Drupal/locale/Gettext.php
index f3ba3b9..748f817 100644
--- a/core/modules/locale/lib/Drupal/locale/Gettext.php
+++ b/core/modules/locale/lib/Drupal/locale/Gettext.php
@@ -66,7 +66,7 @@ class Gettext {
*
* @see Drupal\locale\PoDatabaseWriter
*/
- static function fileToDatabase($file, $langcode, $overwrite_options, $customized = LOCALE_NOT_CUSTOMIZED) {
+ static function fileToDatabase($file, $langcode, $overwrite_options, $customized = LOCALE_NOT_CUSTOMIZED, $seekpos = NULL, $items = -1) {
// Instantiate and initialize the stream reader for this file.
$reader = new PoStreamReader();
$reader->setLangcode($langcode);
@@ -96,13 +96,25 @@ class Gettext {
// Attempt to pipe all items from the file to the database.
try {
- $writer->writeItems($reader, -1);
+ if ($seekpos) {
+ $current_state = $reader->getState();
+ // Reset the lineno.
+ unset($current_state['lineno']);
+ $current_state['seekpos'] = $seekpos;
+ $reader->setState($current_state);
+ }
+ $writer->writeItems($reader, $items);
}
catch (Exception $exception) {
throw $exception;
}
// Report back with an array of status information.
- return $writer->getReport();
+ $report = $writer->getReport();
+
+ // Add the seek position to the report.
+ $current_state = $reader->getState();
+ $report['seekpos'] = $current_state['seekpos'];
+ return $report;
}
}
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 8a9c2fe..1c83a27 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -108,45 +108,8 @@ function locale_translate_import_form_submit($form, &$form_state) {
drupal_set_message(t('The language %language has been created.', array('%language' => t($language->name))));
}
$customized = $form_state['values']['customized'] ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED;
-
- // Now import strings into the language
- try {
- // Try to allocate enough time to parse and import the data.
- drupal_set_time_limit(240);
-
- $report = GetText::fileToDatabase($file, $language->langcode, $form_state['values']['overwrite_options'], $customized);
- $additions = $report['additions'];
- $updates = $report['updates'];
- $deletes = $report['deletes'];
- $skips = $report['skips'];
-
- menu_router_rebuild();
- // Clear cache and force refresh of JavaScript translations.
- _locale_invalidate_js($language->langcode);
- cache()->deletePrefix('locale:');
-
- drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
- watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $language->langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes));
- if ($skips) {
- if (module_exists('dblog')) {
- $skip_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')));
- }
- else {
- $skip_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.');
- }
- drupal_set_message($skip_message, 'error');
- watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING);
- }
- $variables = array('%filename' => $file->filename);
- drupal_set_message(t('The translation import of %filename is done.', $variables));
- watchdog('locale', 'The translation import of %filename is done.', $variables);
-
- } catch (Exception $exc) {
- drupal_set_message(print_r($exc, TRUE));
- $variables = array('%filename' => $file->filename);
- drupal_set_message(t('The translation import of %filename failed.', $variables), 'error');
- watchdog('locale', 'The translation import of %filename failed.', $variables, WATCHDOG_ERROR);
- }
+ $batch = locale_translate_batch_build(array($file->uri => $file), $form_state['values']['overwrite_options'], $customized, $form_state['values']['langcode'], TRUE);
+ batch_set($batch);
}
else {
drupal_set_message(t('File to import not found.'), 'error');
@@ -285,10 +248,10 @@ function locale_translate_export_form_submit($form, &$form_state) {
/**
* Set a batch for newly added language.
*/
-function locale_translate_add_language_set_batch($langcode) {
+function locale_translate_add_language_set_batch($langcode, $overwrite_options = array(), $customized = LOCALE_NOT_CUSTOMIZED) {
// See if we have language files to import for the newly added language,
// collect and import them.
- if ($batch = locale_translate_batch_import_files($langcode, TRUE)) {
+ if ($batch = locale_translate_batch_import_files($langcode, $overwrite_options, $customized, TRUE)) {
batch_set($batch);
}
}
@@ -308,7 +271,7 @@ function locale_translate_add_language_set_batch($langcode) {
* l10n_update functionality to feed in translation files alike.
* See http://drupal.org/node/1191488.
*/
-function locale_translate_batch_import_files($langcode = NULL, $finish_feedback = FALSE, $force = FALSE) {
+function locale_translate_batch_import_files($langcode = NULL, $overwrite_options = array(), $customized = LOCALE_NOT_CUSTOMIZED, $finish_feedback = FALSE, $force = FALSE) {
$files = array();
if (!empty($langcode)) {
$langcodes = array($langcode);
@@ -335,7 +298,7 @@ function locale_translate_batch_import_files($langcode = NULL, $finish_feedback
}
}
}
- return locale_translate_batch_build($files, $finish_feedback);
+ return locale_translate_batch_build($files, $overwrite_options, $customized, NULL, $finish_feedback);
}
/**
@@ -364,13 +327,13 @@ function locale_translate_get_interface_translation_files($langcode = NULL) {
* @return
* A batch structure or FALSE if $files was empty.
*/
-function locale_translate_batch_build($files, $finish_feedback = FALSE) {
+function locale_translate_batch_build($files, $overwrite_options = array(), $customized = LOCALE_NOT_CUSTOMIZED, $langcode = NULL, $finish_feedback = FALSE) {
$t = get_t();
if (count($files)) {
$operations = array();
foreach ($files as $file) {
// We call locale_translate_batch_import for every batch operation.
- $operations[] = array('locale_translate_batch_import', array($file->uri));
+ $operations[] = array('locale_translate_batch_import', array($file->uri, $overwrite_options, $customized, $langcode));
}
$batch = array(
'operations' => $operations,
@@ -398,21 +361,47 @@ function locale_translate_batch_build($files, $finish_feedback = FALSE) {
* @param $context
* Contains a list of files imported.
*/
-function locale_translate_batch_import($filepath, &$context) {
+function locale_translate_batch_import($filepath, $overwrite_options, $customized, $langcode = NULL, &$context) {
// The filename is either {langcode}.po or {prefix}.{langcode}.po, so
// we can extract the language code to use for the import from the end.
- if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
+ if ($langcode || preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
$file = entity_create('file', array('filename' => drupal_basename($filepath), 'uri' => $filepath));
- // We need only the last match
- $langcode = array_pop($langcode);
+ // We need only the last match.
+ if (is_array($langcode)) {
+ $langcode = array_pop($langcode);
+ }
try {
- $report = GetText::fileToDatabase($file, $langcode, array(), LOCALE_NOT_CUSTOMIZED);
- $file->langcode = $langcode;
- $file->timestamp = filemtime($file->uri);
- locale_translate_update_file_history($file);
- $context['results']['files'][$filepath] = $filepath;
- $context['results']['stats'][$filepath] = $report;
- } catch (Exception $exception) {
+ if (empty($context['sandbox'])) {
+ $context['sandbox']['parse_state'] = array(
+ 'filesize' => filesize($file->uri),
+ 'chunk_size' => 10,
+ );
+ }
+ $report = GetText::fileToDatabase($file, $langcode, $overwrite_options, $customized, $context['sandbox']['parse_state']['seekpos'], $context['sandbox']['parse_state']['chunk_size']);
+ // Not yet finished with reading, mark progress based on size and
+ // position.
+ // Maximize the progress bar at 95% before completion because batch API
+ // could trigger end of operation before file reading is done for floating
+ // point inaccuracies. See http://drupal.org/node/1089472
+ if ($report['seekpos'] < filesize($file->uri)) {
+ $context['sandbox']['parse_state']['seekpos'] = $report['seekpos'];
+ $context['finished'] = min(0.95, $report['seekpos'] / filesize($file->uri));
+ $context['message'] = t('Importing file: %filename (@percent%)', array('%filename' => $file->filename, '@percent' => (int) ($context['finished'] * 100)));
+ }
+ else {
+ // We are finished here.
+ $context['finished'] = 1;
+ $file->langcode = $langcode;
+ $file->timestamp = filemtime($file->uri);
+ locale_translate_update_file_history($file);
+ $context['results']['files'][$filepath] = $filepath;
+ }
+ // Add the values from the report to the stats for this file.
+ foreach ($report as $key => $value) {
+ $context['results']['stats'][$filepath][$key] += $report[$key];
+ }
+ }
+ catch (Exception $exception) {
$context['results']['files'][$filepath] = $filepath;
$context['results']['failed_files'][$filepath] = $filepath;
}
@@ -448,6 +437,11 @@ function locale_translate_batch_finished($success, $results) {
drupal_set_message($skip_message, 'error');
watchdog('locale', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING);
}
+ menu_router_rebuild();
+
+ // Clear cache and force refresh of JavaScript translations.
+ _locale_invalidate_js();
+ cache()->deletePrefix('locale:');
}
}