diff --git a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php index 33f05d9..1e93e7e 100644 --- a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php +++ b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php @@ -189,7 +189,12 @@ class PoDatabaseWriter implements PoWriterInterface { $item->setSource(join(LOCALE_PLURAL_DELIMITER, $item->getSource())); $item->setTranslation(join(LOCALE_PLURAL_DELIMITER, $item->getTranslation())); } - $this->importString($item); + $lid = $this->importString($item); + // If saved and it is a configuration string, add to the report list. + if ($lid && $item->getContext() == LOCALE_CONFIG_CONTEXT) { + $location = $item->getComment(); + $this->_report['configuration'][] = $location; + } } /** @@ -223,15 +228,28 @@ class PoDatabaseWriter implements PoWriterInterface { $context = $item->getContext(); $source = $item->getSource(); $translation = $item->getTranslation(); - + $location = $item->getComment(); // Look up the source string and any existing translation. - $string = db_query("SELECT s.lid, t.customized FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.source = :source AND s.context = :context", array( - ':source' => $source, - ':context' => $context, - ':language' => $this->_langcode, - )) - ->fetchObject(); + // For configuration strings we do a different search. + if ($context === LOCALE_CONFIG_CONTEXT) { + // For configuration strings we search by context and location. + $string = db_query("SELECT s.lid, s.source, t.customized FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.context = :context AND s.location = :location", array( + ':context' => $context, + ':language' => $this->_langcode, + ':location' => $location, + )) + ->fetchObject(); + } + else { + // for other strings + $string = db_query("SELECT s.lid, t.customized FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.source = :source AND s.context = :context", array( + ':source' => $source, + ':context' => $context, + ':language' => $this->_langcode, + )) + ->fetchObject(); + } if (!empty($translation)) { // Skip this string unless it passes a check for dangerous code. @@ -267,6 +285,7 @@ class PoDatabaseWriter implements PoWriterInterface { $this->_report['updates']++; } + return $string->lid; } else { @@ -275,6 +294,7 @@ class PoDatabaseWriter implements PoWriterInterface { ->fields(array( 'source' => $source, 'context' => $context, + 'location' => $location, )) ->execute(); @@ -288,6 +308,7 @@ class PoDatabaseWriter implements PoWriterInterface { ->execute(); $this->_report['additions']++; + return $lid; } } diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index fa400b5..4f12b26 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -459,6 +459,11 @@ function locale_translate_batch_import($filepath, $options, &$context) { $options['seek'] = $context['sandbox']['parse_state']['seek']; $options['items'] = $context['sandbox']['parse_state']['chunk_size']; $report = GetText::fileToDatabase($file, $options); + // If there are configuration strings in the batch, update them + if (isset($report['configuration'])) { + module_load_include('config.inc', 'locale'); + locale_config_imported_translations($report['configuration'], $options['langcode']); + } // If not yet finished with reading, mark progress based on size and // position. if ($report['seek'] < filesize($file->uri)) { diff --git a/core/modules/locale/locale.config.inc b/core/modules/locale/locale.config.inc new file mode 100644 index 0000000..9d30070 --- /dev/null +++ b/core/modules/locale/locale.config.inc @@ -0,0 +1,256 @@ + $key_list) { + $updated += locale_config_object_update_translation($name, $langcode); + } + if ($updated) { + watchdog('locale', '@count configuration strings have been updated.', array('@count' => $updated)); + } +} + +/** + * Update configuration object after strings or configuration has been updated. + * + * @param $name + * Configuration object name. + * @param $langcode + * Language code to update. + */ +function locale_config_object_update_translation($name, $langcode = NULL) { + // Update all languages if no language code provided. + $langcode_list = $langcode ? array($langcode) : array_keys(language_list()); + // @todo Get default configuration object here, without translation. + $default_config = locale_config($name); + $updated = 0; + // Update / create a localized configuration object for each language. + foreach ($langcode_list as $lang) { + $locale_config = locale_config($name, $lang); + + $changed = FALSE; + foreach (locale_config_object_get_translation($name, $lang) as $key => $translation) { + // Check string source matches default value and if so update config object. + if (!empty($translation->translation) && $translation->source == $default_config->get($key)) { + $locale_config->set($key, $translation->translation); + $changed = TRUE; + } + } + // Save localized configuration if any string has changed. + if ($changed) { + $updated++; + $locale_config->save(); + } + } + return $updated; +} + +/** + * Add strings from configuration object. + * + * @param Drupal\Core\Config\Config $config + * Configuration object. + * @param $string_keys + * Array of keys that are translatable strings. + */ +function locale_config_add_strings(Drupal\Core\Config\Config $config, $string_keys) { + $name = $config->getName(); + $strings = array(); + foreach ($string_keys as $key) { + if ($source_string = $config->get($key)) { + $strings[$key] = $source = locale_config_add_source($name, $key, $source_string); + } + } + if ($strings) { + locale_config_object_update_translation($name); + } + return $strings; +} + +/** + * Add source string for configuration. + * + * @param $name + * Configuration name. + * @param $key + * Configuration key. + * @param $source_string + * The source string stored by the configuration system. + */ +function locale_config_add_source($name, $key, $source_string) { + $config_property = $name . LOCALE_CONFIG_CONCAT . $key; + if ($source = locale_config_get_source($config_property)) { + $update = FALSE; + if ($source->source != $source_string) { + $source->source = $source_string; + $update = TRUE; + } + if ($source->version != VERSION) { + $source->version = VERSION; + $update = TRUE; + } + if ($update) { + drupal_write_record('locales_source', $source, 'lid'); + } + } + else { + $source = (object)array( + 'source' => $source_string, + 'version' => VERSION, + 'context' => LOCALE_CONFIG_CONTEXT, + 'location' => $config_property, + ); + drupal_write_record('locales_source', $source); + } + return $source; +} + +/** + * Get string by configuration name. + */ +function locale_config_get_source($config_property) { + return db_select('locales_source', 's') + ->fields('s') + ->condition('s.context', LOCALE_CONFIG_CONTEXT) + ->condition('s.location', $config_property) + ->execute() + ->fetchObject(); +} + +/** + * Gets all existing translations for a configuration object's strings. + * + * @param $name + * The name of this configuration object. + * @param $langcode + * Language code to use for the lookup. + */ +function locale_config_object_get_translation($name, $langcode) { + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['locale'] = &drupal_static(__FUNCTION__); + } + $locale_t = &$drupal_static_fast['locale']; + + // Strings are cached by langcode and configuration name. + if (!isset($locale_t[$langcode])) { + $locale_t[$langcode] = array(); + } + if (!isset($locale_t[$langcode][$name])) { + $locale_t[$langcode][$name] = _locale_config_object_get_translation($name, $langcode); + } + return $locale_t[$langcode][$name]; +} + +/** + * Load all existing translations from db. + */ +function _locale_config_object_get_translation($name, $langcode) { + $translations = db_query("SELECT s.*, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.context = :context AND s.location LIKE :location", array( + ':language' => $langcode, + ':context' => LOCALE_CONFIG_CONTEXT, + ':location' => $name . LOCALE_CONFIG_CONCAT . '%', + ))->fetchAll(); + if ($translations) { + foreach ($translations as $translation) { + list($name, $key) = explode(LOCALE_CONFIG_CONCAT, $translation->location); + $value[$key] = $translation; + if ($translation->version != VERSION) { + // This is the first use of this string under current Drupal version. + // Update the {locales_source} table to indicate the string is current. + db_update('locales_source') + ->fields(array('version' => VERSION)) + ->condition('lid', $translation->lid) + ->execute(); + } + } + } + else { + $value = array(); + } + return $value; +} + +/** + * Get string by configuration name. + */ +function locale_config_property_get_translation($config_property, $langcode) { + list($name, $key) = _locale_config_name($config_property); + $strings = locale_config_object_get_translation($name, $langcode); + if (isset($strings[$key]) && isset($strings[$key]->translation)) { + return $strings[$key]->translation; + } + else { + return NULL; + } +} + +/** + * Get configuration name and key from full property name. + */ +function _locale_config_name($config_property) { + return explode(LOCALE_CONFIG_CONCAT, $config_property); +} + +/** + * Get configuration object for localization purposes. + * + * @todo Get default configuration object here, without translation nor overrides. + * For now we create a foo event dispatcher so we get raw configuration objects. + * Hope some day the config system allows easier ways. + */ +function locale_config($name, $langcode = NULL) { + // Get storage. + $storage = drupal_container()->get('config.storage'); + // Get dispatcher. + try { + $dispatcher = drupal_container()->get('locale.config.dispatcher'); + } + catch (Exception $e) { + drupal_container()->register('locale.config.dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher'); + $dispatcher = drupal_container()->get('locale.config.dispatcher'); + } + if ($langcode) { + $name = 'locale.config.' . $langcode . '.' . $name; + } + $config = new Config($name, $storage, $dispatcher); + return $config + ->init() + ->load(); +} \ No newline at end of file diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 228bf32..d9f682a 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -65,6 +65,11 @@ const LOCALE_NOT_CUSTOMIZED = 0; */ const LOCALE_CUSTOMIZED = 1; +/** + * Context for configuration strings. + */ +const LOCALE_CONFIG_CONTEXT = 'configuration'; + // --------------------------------------------------------------------------------- // Hook implementations @@ -924,3 +929,28 @@ function locale_language_init() { // Add locale helper to configuration subscribers. drupal_container()->get('dispatcher')->addSubscriber(new LocaleConfigSubscriber()); } + +/** + * Update configuration object after strings or configuration has been updated. + * + * @param $name + * Configuration object name. + */ +function locale_config_update_object($name) { + module_load_include('config.inc', 'locale'); + return locale_config_object_update_translation($name); +} + +/** + * Update configuration object/key after updating a translation. + * + * @param $config_property + * Configuration property full name. + */ +function locale_config_update_property($config_property, $langcode) { + module_load_include('config.inc', 'locale'); + list($name, $key) =_locale_config_name($config_property); + // @todo Update only the requested property, we are updating full object atm. + $updated = locale_config_object_update_translation($name, $langcode); + drupal_set_message(format_plural($updated, 'A configuration string has been updated.', '@count configuration strings have been updated.')); +} diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index 5fdba76..b47e00a 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -423,6 +423,10 @@ function locale_translate_edit_form_submit($form, &$form_state) { )) ->execute(); } + // Update configuration string. + if (isset($form_state['values']['strings'][$lid]['context']) && $form_state['values']['strings'][$lid]['context'] == LOCALE_CONFIG_CONTEXT) { + locale_config_update_property($form_state['values']['strings'][$lid]['location'], $langcode); + } } elseif (!empty($translation_old)) { // Empty translation entered: remove existing entry from database.