diff --git a/core/modules/locale/lib/Drupal/locale/ProjectTranslationState.php b/core/modules/locale/lib/Drupal/locale/ProjectTranslationState.php
new file mode 100644
index 0000000..894e4c5
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/ProjectTranslationState.php
@@ -0,0 +1,405 @@
+projectName = $project_name;
+ }
+
+ public function __destruct() {
+ foreach ($this->stateByLanguage as $langcode => $state) {
+ // @todo Need to check if changed?
+ $this->saveState($langcode);
+ }
+ }
+
+ /**
+ * Returns the machine name of the project.
+ */
+ public function getName() {
+ return $this->projectName;
+ }
+
+ /**
+ * Returns the human readable name of the project.
+ */
+ public function getHumanName() {
+ return $this->projectHumanName;
+ }
+
+ /**
+ * Sets the language this project is translated to.
+ *
+ * @param $langcode
+ */
+ public function setLangcode($langcode) {
+ // @todo Need to check if language is translatable?
+ $this->langcode = $langcode;
+ if (!isset($this->stateByLanguage[$langcode])) {
+ $this->initState();
+ }
+ $this->timestamp = &$this->stateByLanguage[$langcode]->timestamp;
+ $this->lastChecked = &$this->stateByLanguage[$langcode]->lastChecked;
+ $this->latestSource = &$this->stateByLanguage[$langcode]->latestSource;
+ $this->latestSourceTimestamp = &$this->stateByLanguage[$langcode]->latestSourceTimestamp;
+ $this->localSource = &$this->stateByLanguage[$langcode]->localSource;
+ $this->remoteSource = &$this->stateByLanguage[$langcode]->remoteSource;
+ }
+
+ /**
+ * Deletes stored data of specified language.
+ */
+ public function deleteLangcode($langcode) {
+ \Drupal::state()->delete('locale.translation_status.' . $this->projectName . '.' . $langcode);
+ }
+
+ /**
+ * Returns the local source state.
+ */
+ public function getLocalSource() {
+ if (!empty($this->localSource)) {
+ return $this->localSource;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Returns the remote source state.
+ */
+ public function getRemoteSource() {
+ if (!empty($this->remoteSource)) {
+ return $this->remoteSource;
+ }
+ return FALSE;
+ }
+
+ public function getLatestSourceTimestamp() {
+ return $this->latestSourceTimestamp;
+ }
+
+ public function getVersion() {
+ return $this->projectVersion;
+ }
+
+ /**
+ * Whether the remote source is valid and can be checked or downloaded.
+ *
+ * @return bool
+ * Returns FALSE if the project version is a dev release. No translations
+ * are available for dev releases.
+ */
+ public function remoteIsValid() {
+ // @todo Return FALSE if file was not found (404) less than x time ago.
+ if ($this->projectVersion && strpos('dev', $this->projectVersion) !== FALSE) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Whether a remote source file is available and can be downloaded.
+ *
+ * @return bool
+ * Returns FALSE if the project version is a dev release. No translations
+ * are available for dev releases.
+ */
+ public function remoteIsAvailable() {
+ return $this->remoteIsValid() && $this->latestSource == 'remote';
+ }
+
+ /**
+ * Whether the local source is available and can be imported.
+ *
+ * @return bool
+ * Returns FALSE if the project version is a dev release. No translations
+ * are available for dev releases.
+ */
+ public function localIsAvailable() {
+ return $this->latestSource == 'local';
+ }
+
+ public function remoteIsLatest() {
+ return $this->latestSource == 'remote';
+ }
+
+ public function localIsLatest() {
+ return $this->latestSource == 'local';
+ }
+
+ /**
+ * Updates the translation state with local source data.
+ *
+ * @param $data
+ */
+ public function updateLocalSource($data) {
+ $this->localSource = $data;
+ $this->localSource->lastChecked = REQUEST_TIME;
+
+ // If the local translation is the most recent, we update the latestSource
+ // status. Otherwise only mark the current translation as checked. If remote
+ // and local have the same timestamp, the local source is chosen.
+ if ($data->timestamp > $this->latestSourceTimestamp || ($data->timestamp >= $this->latestSourceTimestamp && $this->latestSource == 'remote')) {
+ $this->latestSource = 'local';
+ $this->latestSourceTimestamp = $data->timestamp;
+ }
+ else {
+ $this->lastChecked = REQUEST_TIME;
+ }
+ }
+
+ /**
+ * Updates the translation state with remote source data.
+ *
+ * @param $data
+ */
+ public function updateRemoteSource($data) {
+ $this->remoteSource = $data;
+ $this->remoteSource->lastChecked = REQUEST_TIME;
+
+ // If the remote translation is the most recent, we update the latestSource
+ // status. Otherwise mark the current translation as checked.
+ if ($data->timestamp > $this->latestSourceTimestamp) {
+ $this->latestSource = 'remote';
+ $this->latestSourceTimestamp = $data->timestamp;
+ }
+ else {
+ $this->lastChecked = REQUEST_TIME;
+ }
+ }
+
+ /**
+ * Updates the translation state with the timestamp of a newly imported translation.
+ *
+ * @param $timestamp
+ */
+ public function updateTimestamp($timestamp) {
+ $this->timestamp = $timestamp;
+ $this->lastChecked = REQUEST_TIME;
+
+ $this->latestSource = 'current';
+ $this->latestSourceTimestamp = $timestamp;
+ }
+
+ /**
+ * Whether this project is translated in language $langcode.
+ *
+ * @return bool
+ * Returns TRUE if this project is translated.
+ */
+ public function isTranslated() {
+ return $this->timestamp > 0;
+ }
+
+ /**
+ * Whether translation updates are available for this language.
+ *
+ * @return bool
+ * Returns TRUE if a local or remote translation update is available.
+ */
+ public function hasUpdates() {
+ return $this->latestSource == 'local' || $this->latestSource == 'remote' ;
+ }
+
+ /*
+ * Initializes the translation state by loading from cache or building a new one.
+ */
+ protected function initState() {
+ if (empty($this->projectCore)) {
+ $this->loadProject();
+ }
+ $this->loadState();
+ if (empty($this->stateByLanguage[$this->langcode])) {
+ $this->stateByLanguage[$this->langcode] = $this->buildState();
+ }
+ }
+
+ /**
+ * Loads the project data.
+ */
+ protected function loadProject() {
+ module_load_include('translation.inc', 'locale');
+ // @todo Make a singular function based on locale_translation_get_projects()
+ //$project = locale_translation_get_project($this->projectName);
+ // @todo Follow-up issue to convert project into an object, so we don't have
+ // to call a procedural function here. https://drupal.org/node/1832946
+ $project = locale_translation_get_projects(array($this->projectName));
+ $project = $project[$this->projectName];
+ if (empty($project)) {
+ // @todo Throw exception.
+ }
+ $this->projectHumanName = $this->projectName . ' (TODO)';
+ $this->projectVersion = $project->version;
+ $this->projectCore = $project->core;
+ $this->serverPattern = $project->server_pattern;
+ }
+
+ /**
+ * Builds a new translation state.
+ */
+ protected function buildState() {
+ $state = new \stdClass();
+ $state->timestamp = 0;
+ $state->lastChecked = 0;
+ $state->latestSource = '';
+ $state->latestSourceTimestamp = 0;
+ $state->localSource = $this->buildLocalSource();
+ $state->remoteSource = $this->buildRemoteSource();
+
+ return $state;
+ }
+
+ /**
+ * Builds a local translation source object.
+ */
+ protected function buildLocalSource() {
+ $filename = config('locale.settings')->get('translation.default_filename');
+ $source = new \stdClass();
+ if ($this->fileIsRemote($this->serverPattern)) {
+ $source->filename = $this->buildServerPattern($filename);
+ $source->directory = 'translations://';
+ $source->uri = $source->directory . $source->filename;
+ }
+ else {
+ $source->filename = $this->buildServerPattern(basename($this->serverPattern));
+ $source->directory = $this->buildServerPattern(drupal_dirname($this->serverPattern));
+ $source->uri = $source->directory . '/' . $source->filename;
+ }
+ return $source;
+ }
+
+ /**
+ * Builds a local translation source object.
+ */
+ protected function buildRemoteSource() {
+ $source = new \stdClass();
+ if ($this->fileIsRemote($this->serverPattern)) {
+ $source->filename = $this->buildServerPattern(basename($this->serverPattern));
+ $source->uri = $this->buildServerPattern($this->serverPattern);
+ }
+ return $source;
+ }
+
+ /**
+ * Builds uri of a translation file out of a server path replacement pattern.
+ *
+ * @param string $template
+ * Uri template string containing placeholders. Available placeholders:
+ * - "%project": Project name.
+ * - "%version": Project version.
+ * - "%core": Project core version.
+ * - "%language": Language code.
+ *
+ * @return string
+ * File uri with replaced placeholders.
+ */
+ protected function buildServerPattern($template) {
+ $variables = array(
+ '%project' => $this->projectName,
+ '%version' => $this->projectVersion,
+ '%core' => $this->projectCore,
+ '%language' => $this->langcode,
+ );
+ return strtr($template, $variables);
+ }
+
+ /**
+ * Loads the translation state.
+ */
+ protected function loadState() {
+ return $this->stateByLanguage[$this->langcode] = \Drupal::state()->get('locale.translation_status.' . $this->projectName . '.' . $this->langcode, NULL);
+ }
+
+ /**
+ * Saves the translation state.
+ *
+ * @param $langcode
+ */
+ protected function saveState() {
+ // @todo Need to check if data changed? (hash of lastChecked timestamps).
+ \Drupal::state()->set('locale.translation_status.' . $this->projectName . '.' . $this->langcode, $this->stateByLanguage[$this->langcode]);
+ }
+
+ protected function fileIsRemote($uri) {
+ $scheme = file_uri_scheme($uri);
+ if ($scheme) {
+ return !drupal_realpath($scheme . '://');
+ }
+ return FALSE;
+ }
+}
diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc
index 5085a2b..fee0b93 100644
--- a/core/modules/locale/locale.batch.inc
+++ b/core/modules/locale/locale.batch.inc
@@ -5,6 +5,7 @@
* Batch process to check the availability of remote or local po files.
*/
+use Drupal\locale\ProjectTranslationState;
use Guzzle\Http\Exception\BadResponseException;
use Guzzle\Http\Exception\RequestException;
@@ -40,27 +41,27 @@ function locale_translation_batch_status_check($project, $langcode, $options = a
'finish_feedback' => TRUE,
'use_remote' => TRUE,
);
- $source = locale_translation_get_status(array($project), array($langcode));
- $source = $source[$project][$langcode];
+ // @todo Class ProjectTranslationState must be overridable.
+ $project_state = new ProjectTranslationState($project);
+ $project_state->setLangcode($langcode);
// Check the status of local translation files.
- if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) {
- if ($file = locale_translation_source_check_file($source)) {
- locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
+ if ($source = $project_state->getLocalSource()) {
+ if ($local_file = locale_translation_source_check_file($source)) {
+ $project_state->updateLocalSource($local_file);
}
$checked = TRUE;
}
// Check the status of remote translation files.
- if ($options['use_remote'] && isset($source->files[LOCALE_TRANSLATION_REMOTE])) {
- $remote_file = $source->files[LOCALE_TRANSLATION_REMOTE];
+ if ($options['use_remote'] && $project_state->remoteIsValid() && $remote_file = $project_state->getRemoteSource()) {
if ($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 (isset($result['last_modified'])) {
$remote_file->uri = isset($result['location']) ? $result['location'] : $remote_file->uri;
$remote_file->timestamp = $result['last_modified'];
- locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_REMOTE, $remote_file);
+ $project_state->updateRemoteSource($remote_file);
}
// @todo What to do with when the file is not found (404)? To prevent
// re-checking within the TTL (1day, 1week) we can set a last_checked
@@ -75,12 +76,12 @@ function locale_translation_batch_status_check($project, $langcode, $options = a
// Provide user feedback and record success or failure for reporting at the
// end of the batch.
if ($options['finish_feedback'] && $checked) {
- $context['results']['files'][] = $source->name;
+ $context['results']['files'][] = $project_state->getName();
}
if ($failure && !$checked) {
- $context['results']['failed_files'][] = $source->name;
+ $context['results']['failed_files'][] = $project_state->getName();
}
- $context['message'] = t('Checked translation for %project.', array('%project' => $source->project));
+ $context['message'] = t('Checked translation for %project.', array('%project' => $project_state->getHumanName()));
}
/**
@@ -135,17 +136,17 @@ function locale_translation_batch_status_finished($success, $results) {
* @see locale_translation_batch_fetch_import()
*/
function locale_translation_batch_fetch_download($project, $langcode, &$context) {
- $sources = locale_translation_get_status(array($project), array($langcode));
- if (isset($sources[$project][$langcode])) {
- $source = $sources[$project][$langcode];
- if (isset($source->type) && $source->type == LOCALE_TRANSLATION_REMOTE) {
- if ($file = locale_translation_download_source($source->files[LOCALE_TRANSLATION_REMOTE], 'translations://')) {
- $context['message'] = t('Downloaded translation for %project.', array('%project' => $source->project));
- locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
- }
- else {
- $context['results']['failed_files'][] = $source->files[LOCALE_TRANSLATION_REMOTE];
- }
+ $project_state = new ProjectTranslationState($project);
+ $project_state->setLangcode($langcode);
+
+ if ($project_state->remoteIsAvailable() && $remote = $project_state->getRemoteSource()) {
+ $t = get_t();
+ if ($result = locale_translation_download_source($remote, 'translations://')) {
+ $context['message'] = $t('Downloaded translation for %project.', array('%project' => $project_state->getHumanName()));
+ $project_state->updateLocalSource($result);
+ }
+ else {
+ $context['results']['failed_files'][] = $remote;
}
}
}
@@ -169,31 +170,28 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context)
* @see locale_translation_batch_fetch_download()
*/
function locale_translation_batch_fetch_import($project, $langcode, $options, &$context) {
- $sources = locale_translation_get_status(array($project), array($langcode));
- if (isset($sources[$project][$langcode])) {
- $source = $sources[$project][$langcode];
- if (isset($source->type)) {
- if ($source->type == LOCALE_TRANSLATION_REMOTE || $source->type == LOCALE_TRANSLATION_LOCAL) {
- $file = $source->files[LOCALE_TRANSLATION_LOCAL];
- module_load_include('bulk.inc', 'locale');
- $options += array(
- 'message' => t('Importing translation for %project.', array('%project' => $source->project)),
- );
- // Import the translation file. For large files the batch operations is
- // progressive and will be called repeatedly until finished.
- locale_translate_batch_import($file, $options, $context);
+ $project_state = new ProjectTranslationState($project);
+ $project_state->setLangcode($langcode);
- // The import is finished.
- if (isset($context['finished']) && $context['finished'] == 1) {
- // The import is successful.
- if (isset($context['results']['files'][$file->uri])) {
- $context['message'] = t('Imported translation for %project.', array('%project' => $source->project));
+ if ($project_state->localIsAvailable() && $local = $project_state->getLocalSource()) {
+ $local->langcode = $langcode;
+ $t = get_t();
+ module_load_include('bulk.inc', 'locale');
+ $options += array(
+ 'message' => $t('Importing translation for %project.', array('%project' => $project_state->getHumanName())),
+ );
+ // Import the translation file. For large files the batch operations is
+ // progressive and will be called repeatedly until finished.
+ locale_translate_batch_import($local, $options, $context);
- // Save the data of imported source into the {locale_file} table and
- // update the current translation status.
- locale_translation_status_save($project, $langcode, LOCALE_TRANSLATION_CURRENT, $source->files[LOCALE_TRANSLATION_LOCAL]);
- }
- }
+ // The import is finished.
+ if (isset($context['finished']) && $context['finished'] == 1) {
+ // The import is successfull.
+ if (isset($context['results']['files'][$local->uri])) {
+ $context['message'] = $t('Imported translation for %project.', array('%project' => $project_state->getHumanName()));
+
+ // Update the current translation timestamp.
+ $project_state->updateTimestamp($local->timestamp);
}
}
}
@@ -284,9 +282,8 @@ function locale_translation_http_check($uri) {
* File object if download was successful. FALSE on failure.
*/
function locale_translation_download_source($source_file, $directory = 'temporary://') {
- if ($uri = system_retrieve_file($source_file->uri, $directory)) {
+ if ($uri = system_retrieve_file($source_file->uri, $directory, FALSE, FILE_EXISTS_REPLACE)) {
$file = clone($source_file);
- $file->type = LOCALE_TRANSLATION_LOCAL;
$file->uri = $uri;
$file->directory = $directory;
$file->timestamp = filemtime($uri);
diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc
index dad67a8..a89f417 100644
--- a/core/modules/locale/locale.compare.inc
+++ b/core/modules/locale/locale.compare.inc
@@ -335,10 +335,13 @@ function locale_translation_check_projects_local($projects = array(), $langcodes
// available. When found the source object is updated with the appropriate
// type and timestamp of the po file.
foreach ($projects as $name => $project) {
+ // @todo ProjectTranslationState must be overridable.
+ $project_state = new ProjectTranslationState($project);
foreach ($langcodes as $langcode) {
- $source = locale_translation_source_build($project, $langcode);
- $file = locale_translation_source_check_file($source);
- locale_translation_status_save($name, $langcode, LOCALE_TRANSLATION_LOCAL, $file);
+ $project_state->setLangcode($langcode);
+ $local = $project_state->getLocal();
+ $file = locale_translation_source_check_file($local);
+ $project_state->updateLocalSource($file);
}
}
}
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 7c3f570..b32ed1f 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -14,6 +14,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\Language;
use Drupal\Component\Utility\Crypt;
+use Drupal\locale\ProjectTranslationState;
/**
* Regular expression pattern used to localize JavaScript strings.
@@ -114,21 +115,6 @@
const LOCALE_TRANSLATION_OVERWRITE_NONE = 'none';
/**
- * Translation source is a remote file.
- */
-const LOCALE_TRANSLATION_REMOTE = 'remote';
-
-/**
- * Translation source is a local file.
- */
-const LOCALE_TRANSLATION_LOCAL = 'local';
-
-/**
- * Translation source is the current translation.
- */
-const LOCALE_TRANSLATION_CURRENT = 'current';
-
-/**
* Implements hook_help().
*/
function locale_help($path, $arg) {
@@ -911,7 +897,6 @@ function locale_translation_file_history_delete($projects = array(), $langcodes
*/
function locale_translation_get_status($projects = NULL, $langcodes = NULL) {
$result = array();
- $status = \Drupal::state()->get('locale.translation_status');
module_load_include('translation.inc', 'locale');
$projects = $projects ? $projects : array_keys(locale_translation_get_projects());
$langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
@@ -920,14 +905,9 @@ function locale_translation_get_status($projects = NULL, $langcodes = NULL) {
// status was stored, a new translation source is created.
foreach ($projects as $project) {
foreach ($langcodes as $langcode) {
- if (isset($status[$project][$langcode])) {
- $result[$project][$langcode] = $status[$project][$langcode];
- }
- else {
- $sources = locale_translation_build_sources(array($project), array($langcode));
- if (isset($sources[$project][$langcode])) {
- $result[$project][$langcode] = $sources[$project][$langcode];
- }
+ // @todo ProjectTranslationState object needs a method to clear its own data.
+ if ($status = Drupal::state()->get('locale.translation_status.' . $project . '.' . $langcode, NULL)) {
+ $result[$project][$langcode] = $status;
}
}
}
@@ -935,80 +915,19 @@ function locale_translation_get_status($projects = NULL, $langcodes = NULL) {
}
/**
- * Saves the status of translation sources in static cache.
- *
- * @param string $project
- * Machine readable project name.
- * @param string $langcode
- * Language code.
- * @param string $type
- * Type of data to be stored.
- * @param array $data
- * File object also containing timestamp when the translation is last updated.
- */
-function locale_translation_status_save($project, $langcode, $type, $data) {
- // Followup issue: http://drupal.org/node/1842362
- // Split status storage per module/language and expire individually. This will
- // improve performance for large sites.
-
- // Load the translation status or build it if not already available.
- module_load_include('translation.inc', 'locale');
- $status = locale_translation_get_status();
- if (empty($status)) {
- $projects = locale_translation_get_projects(array($project));
- if (isset($projects[$project])) {
- $status[$project][$langcode] = locale_translation_source_build($projects[$project], $langcode);
- }
- }
-
- // Merge the new status data with the existing status.
- if (isset($status[$project][$langcode])) {
- switch ($type) {
- case LOCALE_TRANSLATION_REMOTE:
- case LOCALE_TRANSLATION_LOCAL:
- // Add the source data to the status array.
- $status[$project][$langcode]->files[$type] = $data;
-
- // Check if this translation is the most recent one. Set timestamp and
- // data type of the most recent translation source.
- if (isset($data->timestamp) && $data->timestamp) {
- if ($data->timestamp > $status[$project][$langcode]->timestamp) {
- $status[$project][$langcode]->timestamp = $data->timestamp;
- $status[$project][$langcode]->last_checked = REQUEST_TIME;
- $status[$project][$langcode]->type = $type;
- }
- }
- break;
- case LOCALE_TRANSLATION_CURRENT:
- $data->last_checked = REQUEST_TIME;
- $status[$project][$langcode]->timestamp = $data->timestamp;
- $status[$project][$langcode]->last_checked = $data->last_checked;
- $status[$project][$langcode]->type = $type;
- locale_translation_update_file_history($data);
- break;
- }
-
- \Drupal::state()->set('locale.translation_status', $status);
- \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
- }
-}
-
-/**
* Delete language entries from the status cache.
*
* @param array $langcodes
* Language code(s) to be deleted from the cache.
*/
function locale_translation_status_delete_languages($langcodes) {
- if ($status = locale_translation_get_status()) {
- foreach ($status as $project => $languages) {
- foreach ($languages as $langcode => $source) {
- if (in_array($langcode, $langcodes)) {
- unset($status[$project][$langcode]);
- }
- }
+ $projects = locale_translation_get_projects();
+ foreach (array_keys($projects) as $project) {
+ foreach ($langcodes as $langcode) {
+ // @todo ProjectTranslationState object needs a method to clear its own data.
+ Drupal::state()
+ ->delete('locale.translation_status.' . $project . '.' . $langcode);
}
- \Drupal::state()->set('locale.translation_status', $status);
}
}
@@ -1033,7 +952,14 @@ function locale_translation_status_delete_projects($projects) {
* Clear the translation status cache.
*/
function locale_translation_clear_status() {
- \Drupal::state()->delete('locale.translation_status');
+ $projects = locale_translation_get_projects();
+ $langcodes = locale_translatable_language_list();
+ foreach (array_keys($projects) as $project) {
+ foreach (array_keys($langcodes) as $langcode) {
+ Drupal::state()
+ ->delete('locale.translation_status.' . $project . '.' . $langcode);
+ }
+ }
\Drupal::state()->delete('locale.translation_last_checked');
}
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 98f4db9..e4588d9 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -6,11 +6,8 @@
*/
use Drupal\Component\Utility\String;
-use Drupal\Core\Language\Language;
-use Drupal\locale\SourceString;
-use Drupal\locale\TranslationString;
use Symfony\Component\HttpFoundation\RedirectResponse;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Drupal\locale\ProjectTranslationState;
/**
* Page callback: Checks for translation updates and displays the status.
@@ -48,7 +45,7 @@ function locale_translation_status_form($form, &$form_state) {
module_load_include('translation.inc', 'locale');
module_load_include('compare.inc', 'locale');
$updates = $options = array();
- $languages_update = $languages_not_found = array();
+ $languages_update = $languages_not_found = $languages_translated = array();
$projects_update = array();
// @todo Calling locale_translation_build_projects() is an expensive way to
@@ -56,41 +53,39 @@ function locale_translation_status_form($form, &$form_state) {
// the project name will be stored to display use, like here.
$project_data = locale_translation_build_projects();
$languages = locale_translatable_language_list();
- $status = locale_translation_get_status();
+ $projects = locale_translation_get_projects();
// Prepare information about projects which have available translation
// updates.
- if ($languages && $status) {
- foreach ($status as $project) {
- foreach ($project as $langcode => $project_info) {
- // No translation file found for this project-language combination.
- if (empty($project_info->type)) {
- $updates[$langcode]['not_found'][] = array(
- 'name' => $project_info->name == 'drupal' ? t('Drupal core') : $project_data[$project_info->name]->info['name'],
- 'version' => $project_info->version,
- 'info' => _locale_translation_status_debug_info($project_info),
- );
- $languages_not_found[$langcode] = $langcode;
- }
- // Translation update found for this project-language combination.
- elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE ) {
- $local = isset($project_info->files[LOCALE_TRANSLATION_LOCAL]) ? $project_info->files[LOCALE_TRANSLATION_LOCAL] : NULL;
- $remote = isset($project_info->files[LOCALE_TRANSLATION_REMOTE]) ? $project_info->files[LOCALE_TRANSLATION_REMOTE] : NULL;
- $recent = _locale_translation_source_compare($local, $remote) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $remote : $local;
+ if ($languages) {
+ foreach (array_keys($projects) as $project) {
+ $project_status = new ProjectTranslationState($project);
+ foreach (array_keys($languages) as $langcode) {
+ $project_status->setLangcode($langcode);
+
+ if ($project_status->hasUpdates()) {
$updates[$langcode]['updates'][] = array(
- 'name' => $project_data[$project_info->name]->info['name'],
- 'version' => $project_info->version,
- 'timestamp' => $recent->timestamp,
+ 'name' => $project == 'drupal' ? t('Drupal core') : $project_data[$project]->info['name'],
+ 'version' => $project_status->getVersion(),
+ 'timestamp' => $project_status->getLatestSourceTimestamp(),
);
$languages_update[$langcode] = $langcode;
- $projects_update[$project_info->name] = $project_info->name;
+ $projects_update[$project] = $project;
+ }
+ elseif (!$project_status->isTranslated()) {
+ $updates[$langcode]['not_found'][] = array(
+ 'name' => $project == 'drupal' ? t('Drupal core') : $project_data[$project]->info['name'],
+ 'version' => $project_status->getVersion(),
+ 'info' => _locale_translation_status_debug_info($project_status),
+ );
+ $languages_not_found[$langcode] = $langcode;
}
}
}
$languages_not_found = array_diff($languages_not_found, $languages_update);
// Build data options for the select table.
- foreach($updates as $langcode => $update) {
+ foreach ($updates as $langcode => $update) {
$title = String::checkPlain($languages[$langcode]->name);
$locale_translation_update_info = array('#theme' => 'locale_translation_update_info');
foreach (array('updates', 'not_found') as $update_status) {
@@ -106,7 +101,14 @@ function locale_translation_status_form($form, &$form_state) {
'#markup' => $title
),
),
- 'status' => array('class' => array('description', 'expand', 'priority-low'), 'data' => drupal_render($locale_translation_update_info)),
+ 'status' => array(
+ 'class' => array(
+ 'description',
+ 'expand',
+ 'priority-low'
+ ),
+ 'data' => drupal_render($locale_translation_update_info)
+ ),
);
}
// Sort the table data on language name.
@@ -135,12 +137,12 @@ function locale_translation_status_form($form, &$form_state) {
if (!$languages) {
$empty = t('No translatable languages available. Add a language first.', array('@add_language' => url('admin/config/regional/language')));
}
- elseif ($status) {
- $empty = t('All translations up to date.');
- }
- else {
+ elseif (empty($updates)) {
$empty = t('No translation status available. Check manually.', array('@check' => url('admin/reports/translations/check')));
}
+ else {
+ $empty = t('All translations up to date.');
+ }
// The projects which require an update. Used by the _submit callback.
$form['projects_update'] = array(
@@ -246,11 +248,13 @@ function locale_translation_language_table($form_element) {
* @return string
* The string which contains debug information.
*/
-function _locale_translation_status_debug_info($source) {
- $remote_path = isset($source->files['remote']->uri) ? $source->files['remote']->uri : '';
- $local_path = isset($source->files['local']->uri) ? $source->files['local']->uri : '';
+function _locale_translation_status_debug_info(ProjectTranslationState $project_status) {
+ $local_source = $project_status->getLocalSource();
+ $remote_source = $project_status->getRemoteSource();
+ $remote_path = isset($remote_source->uri) ? $remote_source->uri : '';
+ $local_path = isset($local_source->uri) ? $local_source->uri : '';
- if (strpos($source->version, 'dev') !== FALSE) {
+ if (strpos($project_status->getVersion(), 'dev') !== FALSE) {
return t('No translation files are provided for development releases.');
}
if (locale_translation_use_remote_source() && $remote_path && $local_path) {
diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc
index fab2a21..713ce46 100644
--- a/core/modules/locale/locale.translation.inc
+++ b/core/modules/locale/locale.translation.inc
@@ -6,30 +6,6 @@
*/
/**
- * Comparison result of source files timestamps.
- *
- * Timestamp of source 1 is less than the timestamp of source 2.
- * @see _locale_translation_source_compare()
- */
-const LOCALE_TRANSLATION_SOURCE_COMPARE_LT = -1;
-
-/**
- * Comparison result of source files timestamps.
- *
- * Timestamp of source 1 is equal to the timestamp of source 2.
- * @see _locale_translation_source_compare()
- */
-const LOCALE_TRANSLATION_SOURCE_COMPARE_EQ = 0;
-
-/**
- * Comparison result of source files timestamps.
- *
- * Timestamp of source 1 is greater than the timestamp of source 2.
- * @see _locale_translation_source_compare()
- */
-const LOCALE_TRANSLATION_SOURCE_COMPARE_GT = 1;
-
-/**
* Get array of projects which are available for interface translation.
*
* This project data contains all projects which will be checked for available
@@ -86,63 +62,6 @@ function locale_translation_clear_cache_projects() {
}
/**
- * Loads cached translation sources containing current translation status.
- *
- * @param array $projects
- * Array of project names. Defaults to all translatable projects.
- * @param array $langcodes
- * Array of language codes. Defaults to all translatable languages.
- *
- * @return array
- * Array of source objects. Keyed with :.
- *
- * @see locale_translation_source_build()
- */
-function locale_translation_load_sources($projects = NULL, $langcodes = NULL) {
- $sources = array();
- $projects = $projects ? $projects : array_keys(locale_translation_get_projects());
- $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
-
- // Load source data from locale_translation_status cache.
- $status = locale_translation_get_status();
-
- // Use only the selected projects and languages for update.
- foreach($projects as $project) {
- foreach ($langcodes as $langcode) {
- $sources[$project][$langcode] = isset($status[$project][$langcode]) ? $status[$project][$langcode] : NULL;
- }
- }
- return $sources;
-}
-
-/**
- * Build translation sources.
- *
- * @param array $projects
- * Array of project names. Defaults to all translatable projects.
- * @param array $langcodes
- * Array of language codes. Defaults to all translatable languages.
- *
- * @return array
- * Array of source objects. Keyed by project name and language code.
- *
- * @see locale_translation_source_build()
- */
-function locale_translation_build_sources($projects = array(), $langcodes = array()) {
- $sources = array();
- $projects = locale_translation_get_projects($projects);
- $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
-
- foreach ($projects as $project) {
- foreach ($langcodes as $langcode) {
- $source = locale_translation_source_build($project, $langcode);
- $sources[$source->name][$source->langcode] = $source;
- }
- }
- return $sources;
-}
-
-/**
* Checks whether a po file exists in the local filesystem.
*
* It will search in the directory set in the translation source. Which defaults
@@ -163,136 +82,21 @@ function locale_translation_build_sources($projects = array(), $langcodes = arra
* - "uri": File name and path.
* - "timestamp": Last updated time of the po file.
* FALSE if the file is not found.
- *
- * @see locale_translation_source_build()
*/
-function locale_translation_source_check_file($source) {
- if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) {
- $source_file = $source->files[LOCALE_TRANSLATION_LOCAL];
- $directory = $source_file->directory;
- $filename = '/' . preg_quote($source_file->filename) . '$/';
+function locale_translation_source_check_file($source_file) {
+ $directory = $source_file->directory;
+ $filename = '/' . preg_quote($source_file->filename) . '$/';
- if ($files = file_scan_directory($directory, $filename, array('key' => 'name', 'recurse' => FALSE))) {
- $file = current($files);
- $source_file->uri = $file->uri;
- $source_file->timestamp = filemtime($file->uri);
- return $source_file;
- }
+ if ($files = file_scan_directory($directory, $filename, array('key' => 'name', 'recurse' => FALSE))) {
+ $file = current($files);
+ $source_file->uri = $file->uri;
+ $source_file->timestamp = filemtime($file->uri);
+ return $source_file;
}
return FALSE;
}
/**
- * Builds abstract translation source.
- *
- * @param object $project
- * Project object.
- * @param string $langcode
- * Language code.
- * @param string $filename
- * File name of translation file. May contain placeholders.
- *
- * @return object
- * Source object:
- * - "project": Project name.
- * - "name": Project name (inherited from project).
- * - "language": Language code.
- * - "core": Core version (inherited from project).
- * - "version": Project version (inherited from project).
- * - "project_type": Project type (inherited from project).
- * - "files": Array of file objects containing properties of local and remote
- * translation files.
- * Other processes can add the following properties:
- * - "type": Most recent translation source found. LOCALE_TRANSLATION_REMOTE and
- * LOCALE_TRANSLATION_LOCAL indicate available new translations,
- * LOCALE_TRANSLATION_CURRENT indicate that the current translation is them
- * most recent. "type" sorresponds with a key of the "files" array.
- * - "timestamp": The creation time of the "type" translation (file).
- * - "last_checked": The time when the "type" translation was last checked.
- * The "files" array can hold file objects of type:
- * LOCALE_TRANSLATION_LOCAL, LOCALE_TRANSLATION_REMOTE and
- * LOCALE_TRANSLATION_CURRENT. Each contains following properties:
- * - "type": The object type (LOCALE_TRANSLATION_LOCAL,
- * LOCALE_TRANSLATION_REMOTE, etc. see above).
- * - "project": Project name.
- * - "langcode": Language code.
- * - "version": Project version.
- * - "uri": Local or remote file path.
- * - "directory": Directory of the local po file.
- * - "filename": File name.
- * - "timestamp": Timestamp of the file.
- * - "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;
- $source->langcode = $langcode;
- $source->type = '';
- $source->timestamp = 0;
- $source->last_checked = 0;
-
- $filename = $filename ? $filename : \Drupal::config('locale.settings')->get('translation.default_filename');
-
- // If the server_pattern contains a remote file path we will check for a
- // remote file. The local version of this file will only be checked if a
- // translations directory has been defined. If the server_pattern is a local
- // file path we will only check for a file in the local file system.
- $files = array();
- if (_locale_translation_file_is_remote($source->server_pattern)) {
- $files[LOCALE_TRANSLATION_REMOTE] = (object) array(
- 'project' => $project->name,
- 'langcode' => $langcode,
- 'version' => $project->version,
- 'type' => LOCALE_TRANSLATION_REMOTE,
- 'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)),
- 'uri' => locale_translation_build_server_pattern($source, $source->server_pattern),
- );
- $files[LOCALE_TRANSLATION_LOCAL] = (object) array(
- 'project' => $project->name,
- 'langcode' => $langcode,
- 'version' => $project->version,
- 'type' => LOCALE_TRANSLATION_LOCAL,
- 'filename' => locale_translation_build_server_pattern($source, $filename),
- 'directory' => 'translations://',
- );
- $files[LOCALE_TRANSLATION_LOCAL]->uri = $files[LOCALE_TRANSLATION_LOCAL]->directory . $files[LOCALE_TRANSLATION_LOCAL]->filename;
- }
- else {
- $files[LOCALE_TRANSLATION_LOCAL] = (object) array(
- 'project' => $project->name,
- 'langcode' => $langcode,
- 'version' => $project->version,
- 'type' => LOCALE_TRANSLATION_LOCAL,
- 'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)),
- 'directory' => locale_translation_build_server_pattern($source, drupal_dirname($source->server_pattern)),
- );
- $files[LOCALE_TRANSLATION_LOCAL]->uri = $files[LOCALE_TRANSLATION_LOCAL]->directory . '/' . $files[LOCALE_TRANSLATION_LOCAL]->filename;
- }
- $source->files = $files;
-
- // If this project+language is already translated, we add its status and
- // update the current translation timestamp and last_updated time. If the
- // project+language is not translated before, create a new record.
- $history = locale_translation_get_file_history();
- if (isset($history[$project->name][$langcode]) && $history[$project->name][$langcode]->timestamp) {
- $source->files[LOCALE_TRANSLATION_CURRENT] = $history[$project->name][$langcode];
- $source->type = LOCALE_TRANSLATION_CURRENT;
- $source->timestamp = $history[$project->name][$langcode]->timestamp;
- $source->last_checked = $history[$project->name][$langcode]->last_checked;
- }
- else {
- locale_translation_update_file_history($source);
- }
-
- return $source;
-}
-
-/**
* Build path to translation source, out of a server path replacement pattern.
*
* @param object $project
@@ -379,45 +183,6 @@ function _locale_translation_file_is_remote($uri) {
}
/**
- * Compare two update sources, looking for the newer one.
- *
- * The timestamp property of the source objects are used to determine which is
- * the newer one.
- *
- * @param object $source1
- * Source object of the first translation source.
- * @param object $source2
- * Source object of available update.
- *
- * @return integer
- * - "LOCALE_TRANSLATION_SOURCE_COMPARE_LT": $source1 < $source2 OR $source1
- * is missing.
- * - "LOCALE_TRANSLATION_SOURCE_COMPARE_EQ": $source1 == $source2 OR both
- * $source1 and $source2 are missing.
- * - "LOCALE_TRANSLATION_SOURCE_COMPARE_GT": $source1 > $source2 OR $source2
- * is missing.
- */
-function _locale_translation_source_compare($source1, $source2) {
- if (isset($source1->timestamp) && isset($source2->timestamp)) {
- if ($source1->timestamp == $source2->timestamp) {
- return LOCALE_TRANSLATION_SOURCE_COMPARE_EQ;
- }
- else {
- return $source1->timestamp > $source2->timestamp ? LOCALE_TRANSLATION_SOURCE_COMPARE_GT : LOCALE_TRANSLATION_SOURCE_COMPARE_LT;
- }
- }
- elseif (isset($source1->timestamp) && !isset($source2->timestamp)) {
- return LOCALE_TRANSLATION_SOURCE_COMPARE_GT;
- }
- elseif (!isset($source1->timestamp) && isset($source2->timestamp)) {
- return LOCALE_TRANSLATION_SOURCE_COMPARE_LT;
- }
- else {
- return LOCALE_TRANSLATION_SOURCE_COMPARE_EQ;
- }
-}
-
-/**
* Returns default import options for translation update.
*
* @return array