diff --git a/core/includes/gettext.inc b/core/includes/gettext.inc
index 18c27ea..84c7f40 100644
--- a/core/includes/gettext.inc
+++ b/core/includes/gettext.inc
@@ -23,11 +23,15 @@
* Drupal file object corresponding to the PO file to import.
* @param $langcode
* Language code.
- * @param $mode
- * Should existing translations be replaced LOCALE_IMPORT_KEEP or
- * LOCALE_IMPORT_OVERWRITE.
+ * @param $overwrite_options
+ * An associative array indicating what data should be overwritten, if any.
+ * - not_customized: not customized strings should be overwritten.
+ * - customized: customized strings should be overwritten.
+ * @param $customized
+ * Whether the strings being imported should be saved as customized.
+ * Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
*/
-function _locale_import_po($file, $langcode, $mode) {
+function _locale_import_po($file, $langcode, $overwrite_options, $customized = LOCALE_NOT_CUSTOMIZED) {
// Try to allocate enough time to parse and import the data.
drupal_set_time_limit(240);
@@ -38,7 +42,7 @@ function _locale_import_po($file, $langcode, $mode) {
}
// Get strings from file (returns on failure after a partial import, or on success)
- $status = _locale_import_read_po('db-store', $file, $mode, $langcode);
+ $status = _locale_import_read_po('db-store', $file, $overwrite_options, $langcode, $customized);
if ($status === FALSE) {
// Error messages are set in _locale_import_read_po().
return FALSE;
@@ -80,13 +84,17 @@ function _locale_import_po($file, $langcode, $mode) {
* Storage operation type: db-store or mem-store.
* @param $file
* Drupal file object corresponding to the PO file to import.
- * @param $mode
- * Should existing translations be replaced LOCALE_IMPORT_KEEP or
- * LOCALE_IMPORT_OVERWRITE.
+ * @param $overwrite_options
+ * An associative array indicating what data should be overwritten, if any.
+ * - not_customized: not customized strings should be overwritten.
+ * - customized: customized strings should be overwritten.
* @param $lang
* Language code.
+ * @param $customized
+ * Whether the strings being imported should be saved as customized.
+ * Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
*/
-function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
+function _locale_import_read_po($op, $file, $overwrite_options = NULL, $lang = NULL, $customized = LOCALE_NOT_CUSTOMIZED) {
// The file will get closed by PHP on returning from this function.
$fd = fopen($file->uri, 'rb');
@@ -138,7 +146,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
}
elseif (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
// We are currently in string token, close it out.
- _locale_import_one_string($op, $current, $mode, $lang, $file);
+ _locale_import_one_string($op, $current, $overwrite_options, $lang, $file, $customized);
// Start a new entry for the comment.
$current = array();
@@ -182,7 +190,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
// We are currently in a message string, close it out.
- _locale_import_one_string($op, $current, $mode, $lang, $file);
+ _locale_import_one_string($op, $current, $overwrite_options, $lang, $file, $customized);
// Start a new context for the id.
$current = array();
@@ -212,7 +220,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
// We are currently in a message, start a new one.
- _locale_import_one_string($op, $current, $mode, $lang, $file);
+ _locale_import_one_string($op, $current, $overwrite_options, $lang, $file, $customized);
$current = array();
}
elseif (!empty($current['msgctxt'])) {
@@ -326,7 +334,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
// End of PO file, closed out the last entry.
if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
- _locale_import_one_string($op, $current, $mode, $lang, $file);
+ _locale_import_one_string($op, $current, $overwrite_options, $lang, $file, $customized);
}
elseif ($context != 'COMMENT') {
_locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
@@ -360,16 +368,20 @@ function _locale_import_message($message, $file, $lineno = NULL) {
* Operation to perform: 'db-store', 'db-report', 'mem-store' or 'mem-report'.
* @param $value
* Details of the string stored.
- * @param $mode
- * Should existing translations be replaced LOCALE_IMPORT_KEEP or
- * LOCALE_IMPORT_OVERWRITE.
+ * @param $overwrite_options
+ * An associative array indicating what data should be overwritten, if any.
+ * - not_customized: not customized strings should be overwritten.
+ * - customized: customized strings should be overwritten.
* @param $lang
* Language to store the string in.
* @param $file
* Object representation of file being imported, only required when op is
* 'db-store'.
+ * @param $customized
+ * Whether the strings being imported should be saved as customized.
+ * Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
*/
-function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL) {
+function _locale_import_one_string($op, $value = NULL, $overwrite_options = NULL, $lang = NULL, $file = NULL, $customized = LOCALE_NOT_CUSTOMIZED) {
$report = &drupal_static(__FUNCTION__, array('additions' => 0, 'updates' => 0, 'deletes' => 0, 'skips' => 0));
$header_done = &drupal_static(__FUNCTION__ . ':header_done', FALSE);
$strings = &drupal_static(__FUNCTION__ . ':strings', array());
@@ -395,7 +407,7 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
// If 'msgid' is empty, it means we got values for the header of the
// file as per the structure of the Gettext format.
$locale_plurals = variable_get('locale_translation_plurals', array());
- if (($mode != LOCALE_IMPORT_KEEP) || empty($locale_plurals[$lang]['plurals'])) {
+ if ((array_sum($overwrite_options) > 0) || empty($locale_plurals[$lang]['plurals'])) {
// Since we only need to parse the header if we ought to update the
// plural formula, only run this if we don't need to keep existing
// data untouched or if we don't have an existing plural formula.
@@ -432,7 +444,8 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
$value['msgid'],
$value['msgstr'],
$comments,
- $mode
+ $overwrite_options,
+ $customized
);
}
} // end of db-store operation
@@ -454,15 +467,34 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
* Translation to language specified in $langcode.
* @param $location
* Location value to save with source string.
- * @param $mode
- * Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
+ * @param $overwrite_options
+ * An associative array indicating what data should be overwritten, if any.
+ * - not_customized: not customized strings should be overwritten.
+ * - customized: customized strings should be overwritten.
+ * @param $customized
+ * (optional) Whether the strings being imported should be saved as customized.
+ * Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
*
* @return
* The string ID of the existing string modified or the new string added.
*/
-function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $location, $mode) {
+function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $location, $overwrite_options, $customized = LOCALE_NOT_CUSTOMIZED) {
+
+ // Initialize overwrite options if not set.
+ $overwrite_options += array(
+ 'not_customized' => FALSE,
+ 'customized' => FALSE,
+ );
+
$lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context", array(':source' => $source, ':context' => $context))->fetchField();
+ // See whether a translation exists already and is customized;
+ // we'll consider it customized if at least one of the plural forms is.
+ $existing_customized = NULL;
+ if ($lid) {
+ $existing_customized = db_query("SELECT MAX(customized) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
+ }
+
if (!empty($translation)) {
// Skip this string unless it passes a check for dangerous code.
if (!locale_string_is_safe($translation)) {
@@ -479,25 +511,25 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
->condition('lid', $lid)
->execute();
- $exists = db_query("SELECT COUNT(lid) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
-
- if (!$exists) {
+ if (!isset($existing_customized)) {
// No translation in this language.
db_insert('locales_target')
->fields(array(
'lid' => $lid,
'language' => $langcode,
'translation' => $translation,
+ 'customized' => $customized,
))
->execute();
$report['additions']++;
}
- elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
- // Translation exists, only overwrite if instructed.
+ elseif ($overwrite_options[$existing_customized ? 'customized' : 'not_customized']) {
+ // Translation exists, only overwrite if instructed.
db_update('locales_target')
->fields(array(
'translation' => $translation,
+ 'customized' => $customized,
))
->condition('language', $langcode)
->condition('lid', $lid)
@@ -521,13 +553,14 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
'lid' => $lid,
'language' => $langcode,
'translation' => $translation,
+ 'customized' => $customized,
))
->execute();
$report['additions']++;
}
}
- elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+ elseif (isset($existing_customized) && $overwrite_options[$existing_customized ? 'customized' : 'not_customized']) {
// Empty translation, remove existing if instructed.
db_delete('locales_target')
->condition('language', $langcode)
@@ -828,17 +861,67 @@ function _locale_import_parse_quoted($string) {
* @param $language
* Language object to generate the output for, or NULL if generating
* translation template.
+ * @param $options
+ * (optional) An associative array specifying what to include in the output:
+ * - customized: include customized strings (if TRUE)
+ * - uncustomized: include non-customized string (if TRUE)
+ * - untranslated: include untranslated source strings (if TRUE)
+ * Ignored if $language is NULL.
*
* @return
* An array of translated strings that can be used to generate an export.
*/
-function _locale_export_get_strings($language = NULL) {
- if (isset($language)) {
- $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language", array(':language' => $language->langcode));
+function _locale_export_get_strings($language = NULL, $options = array()) {
+
+ // Assume FALSE for all options if not provided by the API.
+ $options += array(
+ 'customized' => FALSE,
+ 'not_customized' => FALSE,
+ 'not_translated' => FALSE,
+ );
+ if (array_sum($options) == 0) {
+ // If user asked to not include anything in the translation files,
+ // that would not make sense, so just fall back on providing a template.
+ $language = NULL;
+ }
+
+ // Build and execute query to collect source strings and translations.
+ $query = db_select('locales_source', 's');
+ if (!empty($language)) {
+ if ($options['not_translated']) {
+ // Left join to keep untranslated strings in.
+ $query->leftJoin('locales_target', 't', 's.lid = t.lid AND t.language = :language', array(':language' => $language->langcode));
+ }
+ else {
+ // Inner join to filter for only translations.
+ $query->innerJoin('locales_target', 't', 's.lid = t.lid AND t.language = :language', array(':language' => $language->langcode));
+ }
+ if ($options['customized']) {
+ if (!$options['not_customized']) {
+ // Filter for customized strings only.
+ $query->condition('t.customized', LOCALE_CUSTOMIZED);
+ }
+ // Else no filtering needed in this case.
+ }
+ else {
+ if ($options['not_customized']) {
+ // Filter for non-customized strings only.
+ $query->condition('t.customized', LOCALE_NOT_CUSTOMIZED);
+ }
+ else {
+ // Filter for strings without translation.
+ $query->isNull('t.translation');
+ }
+ }
+ $query->fields('t', array('translation'));
}
else {
- $result = db_query("SELECT s.lid, s.source, s.context, s.location FROM {locales_source} s");
+ $query->leftJoin('locales_target', 't', 's.lid = t.lid');
}
+ $query->fields('s', array('lid', 'source', 'context', 'location'));
+ $result = $query->execute();
+
+ // Structure results in an array with metainformation on the strings.
$strings = array();
foreach ($result as $child) {
$strings[$child->lid] = array(
diff --git a/core/includes/locale.inc b/core/includes/locale.inc
index 1f9567b..45e4a95 100644
--- a/core/includes/locale.inc
+++ b/core/includes/locale.inc
@@ -73,26 +73,28 @@ define('LOCALE_JS_OBJECT_CONTEXT', '
');
/**
- * Translation import mode overwriting all existing translations
- * if new translated version available.
+ * Flag for locally not customized interface translation.
+ *
+ * Such translations are imported from .po files downloaded from
+ * localize.drupal.org for example.
*/
-const LOCALE_IMPORT_OVERWRITE = 0;
+const LOCALE_NOT_CUSTOMIZED = 0;
/**
- * Translation import mode keeping existing translations and only
- * inserting new strings.
+ * Flag for locally customized interface translation.
+ *
+ * Such translations are edited from their imported originals on the user
+ * interface or are imported as customized.
*/
-const LOCALE_IMPORT_KEEP = 1;
+const LOCALE_CUSTOMIZED = 1;
/**
- * URL language negotiation: use the path prefix as URL language
- * indicator.
+ * URL language negotiation: use the path prefix as URL language indicator.
*/
const LANGUAGE_NEGOTIATION_URL_PREFIX = 0;
/**
- * URL language negotiation: use the domain as URL language
- * indicator.
+ * URL language negotiation: use the domain as URL language indicator.
*/
const LANGUAGE_NEGOTIATION_URL_DOMAIN = 1;
diff --git a/core/modules/block/block.js b/core/modules/block/block.js
index 72b5673..67cbb31 100644
--- a/core/modules/block/block.js
+++ b/core/modules/block/block.js
@@ -32,6 +32,17 @@ Drupal.behaviors.blockSettingsSummary = {
return vals.join(', ');
});
+ $('fieldset#edit-langcode', context).drupalSetSummary(function (context) {
+ var vals = [];
+ $('input[type="checkbox"]:checked', context).each(function () {
+ vals.push($.trim($(this).next('label').text()));
+ });
+ if (!vals.length) {
+ vals.push(Drupal.t('Not restricted'));
+ }
+ return vals.join(', ');
+ });
+
$('fieldset#edit-role', context).drupalSetSummary(function (context) {
var vals = [];
$('input[type="checkbox"]:checked', context).each(function () {
diff --git a/core/modules/language/language.install b/core/modules/language/language.install
index ecf637d..87a1606 100644
--- a/core/modules/language/language.install
+++ b/core/modules/language/language.install
@@ -71,6 +71,33 @@ function language_schema() {
'list' => array('weight', 'name'),
),
);
+ $schema['block_langcode'] = array(
+ 'description' => 'Sets up display criteria for blocks based on langcode',
+ 'fields' => array(
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'description' => "The block's origin module, from {block}.module.",
+ ),
+ 'delta' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'description' => "The block's unique delta within module, from {block}.delta.",
+ ),
+ 'langcode' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'description' => "The machine-readable name of this language from {language}.langcode.",
+ ),
+ ),
+ 'primary key' => array('module', 'delta', 'langcode'),
+ 'indexes' => array(
+ 'langcode' => array('langcode'),
+ ),
+ );
return $schema;
}
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index 20f7c62..962b771 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -186,6 +186,17 @@ function language_delete($langcode) {
}
/**
+ * Implements hook_modules_uninstalled().
+ *
+ * Cleans up {block_langcode} table from modules' blocks.
+ */
+function language_modules_uninstalled($modules) {
+ db_delete('block_langcode')
+ ->condition('module', $modules, 'IN')
+ ->execute();
+}
+
+/**
* Implements hook_css_alter().
*
* This function checks all CSS files currently added via drupal_add_css() and
@@ -211,3 +222,95 @@ function language_css_alter(&$css) {
}
}
}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Adds per language block visibility options to block configuration form.
+ *
+ * @see language_form_block_admin_configure_submit()
+ * @see block_admin_configure()
+ */
+function language_form_block_admin_configure_alter(&$form, &$form_state) {
+ $default_langcode_options = db_query("SELECT langcode FROM {block_langcode} WHERE module = :module AND delta = :delta", array(
+ ':module' => $form['module']['#value'],
+ ':delta' => $form['delta']['#value'],
+ ))->fetchCol();
+
+ // Fetch the enabled languages.
+ $enabled_languages = language_list(TRUE);
+ foreach ($enabled_languages as $language) {
+ $langcodes_options[$language->langcode] = t($language->name);
+ }
+ $form['visibility']['langcode'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Languages'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ '#group' => 'visibility',
+ '#weight' => 5,
+ );
+ $form['visibility']['langcode']['langcodes'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Show block for specific languages'),
+ '#default_value' => $default_langcode_options,
+ '#options' => $langcodes_options,
+ '#description' => t('Show this block only if the current language is of the given language(s). If you select no language, there will be no language-specific limitation.'),
+ );
+ $form['#submit'][] = 'language_form_block_admin_configure_submit';
+}
+
+/**
+ * Form submission handler for locale_form_block_admin_configure_alter().
+ */
+function language_form_block_admin_configure_submit($form, &$form_state) {
+ db_delete('block_langcode')
+ ->condition('module', $form_state['values']['module'])
+ ->condition('delta', $form_state['values']['delta'])
+ ->execute();
+ $query = db_insert('block_langcode')->fields(array(
+ 'langcode', 'module', 'delta'
+ ));
+ foreach (array_filter($form_state['values']['langcodes']) as $langcode) {
+ $query->values(array(
+ 'langcode' => $langcode,
+ 'module' => $form_state['values']['module'],
+ 'delta' => $form_state['values']['delta'],
+ ));
+ }
+ $query->execute();
+}
+
+/**
+ * Implements hook_block_list_alter().
+ *
+ * Hide the blocks that have been disabled for langcodes visibility setting.
+ */
+function language_block_list_alter(&$blocks) {
+ global $language_interface, $theme_key;
+
+ $result = db_query('SELECT module, delta, langcode FROM {block_langcode}');
+ $block_langcodes = array();
+ foreach ($result as $record) {
+ $block_langcodes[$record->module][$record->delta][$record->langcode] = TRUE;
+ }
+ foreach ($blocks as $key => $block) {
+ // Any module using this alter should inspect the data before changing it,
+ // to ensure it is what they expect.
+ if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) {
+ // This block was added by a contrib module, leave it in the list.
+ continue;
+ }
+
+ if (!isset($block_langcodes[$block->module][$block->delta])) {
+ // No language setting for this block, leave it in the list.
+ continue;
+ }
+
+ if (!isset($block_langcodes[$block->module][$block->delta][$language_interface->langcode])) {
+ // This block should not be displayed with the active language, remove
+ // from the list.
+ unset($blocks[$key]);
+ }
+ }
+}
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index a4982e1..3c5612a 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -34,62 +34,79 @@ function locale_translate_import_form($form, &$form_state) {
else {
$default = key($existing_languages);
$language_options = array(
- t('Already added languages') => $existing_languages,
+ t('Existing languages') => $existing_languages,
t('Languages not yet added') => language_admin_predefined_list()
);
}
- $form['import'] = array('#type' => 'fieldset',
- '#title' => t('Import translation'),
- );
- $form['import']['file'] = array('#type' => 'file',
- '#title' => t('Language file'),
+ $form['file'] = array(
+ '#type' => 'file',
+ '#title' => t('Translation file'),
'#size' => 50,
'#description' => t('A Gettext Portable Object (.po) file.'),
);
- $form['import']['langcode'] = array('#type' => 'select',
- '#title' => t('Import into'),
+ $form['langcode'] = array(
+ '#type' => 'select',
+ '#title' => t('Language'),
'#options' => $language_options,
'#default_value' => $default,
- '#description' => t('Choose the language you want to add strings into. If you choose a language which is not yet set up, it will be added.'),
);
- $form['import']['mode'] = array('#type' => 'radios',
- '#title' => t('Mode'),
- '#default_value' => LOCALE_IMPORT_KEEP,
- '#options' => array(
- LOCALE_IMPORT_OVERWRITE => t('Strings in the uploaded file replace existing ones, new ones are added. The plural format is updated.'),
- LOCALE_IMPORT_KEEP => t('Existing strings and the plural format are kept, only new strings are added.')
+ $form['customized'] = array(
+ '#title' => t('Import contains custom translations'),
+ '#type' => 'checkbox',
+ );
+ $form['overwrite_options'] = array(
+ '#type' => 'container',
+ '#tree' => TRUE,
+ );
+ $form['overwrite_options']['not_customized'] = array(
+ '#title' => t('Overwrite existing non-customized translations'),
+ '#type' => 'checkbox',
+ '#states' => array(
+ 'checked' => array(
+ ':input[name=customized]' => array('checked' => TRUE),
+ ),
),
);
- $form['import']['submit'] = array('#type' => 'submit', '#value' => t('Import'));
+ $form['overwrite_options']['customized'] = array(
+ '#title' => t('Overwrite existing customized translations'),
+ '#type' => 'checkbox',
+ );
+ $form['actions'] = array(
+ '#type' => 'actions'
+ );
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import')
+ );
return $form;
}
/**
- * Process the locale import form submission.
+ * Processes the locale import form submission.
*/
function locale_translate_import_form_submit($form, &$form_state) {
$validators = array('file_validate_extensions' => array('po'));
- // Ensure we have the file uploaded
+
+ // Ensure we have the file uploaded.
if ($file = file_save_upload('file', $validators)) {
- // Add language, if not yet supported
- drupal_static_reset('language_list');
- $languages = language_list();
- $langcode = $form_state['values']['langcode'];
- if (!isset($languages[$langcode])) {
+ // Add language, if not yet supported.
+ $language = language_load($form_state['values']['langcode']);
+ if (empty($language)) {
include_once DRUPAL_ROOT . '/core/includes/standard.inc';
$predefined = standard_language_list();
$language = (object) array(
- 'langcode' => $langcode,
+ 'langcode' => $form_state['values']['langcode'],
);
- language_save($language);
- drupal_set_message(t('The language %language has been created.', array('%language' => t($predefined[$langcode][0]))));
+ $language = language_save($language);
+ 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
- if ($return = _locale_import_po($file, $langcode, $form_state['values']['mode']) == FALSE) {
+ if ($return = _locale_import_po($file, $language->langcode, $form_state['values']['overwrite_options'], $customized) == FALSE) {
$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);
@@ -106,11 +123,9 @@ function locale_translate_import_form_submit($form, &$form_state) {
}
/**
- * User interface for the translation export screen.
+ * Builds form to export Gettext translation files.
*/
-function locale_translate_export_screen() {
- // Get all enabled languages, except English, if we should not translate that.
- drupal_static_reset('language_list');
+function locale_translate_export_form($form, &$form_state) {
$languages = language_list(TRUE);
$language_options = array();
foreach ($languages as $langcode => $language) {
@@ -119,64 +134,55 @@ function locale_translate_export_screen() {
}
}
- $output = '';
- // Offer translation export if any language is set up.
- if (!empty($language_options)) {
- $elements = drupal_get_form('locale_translate_export_po_form', $language_options);
- $output = drupal_render($elements);
- }
- $elements = drupal_get_form('locale_translate_export_pot_form');
- $output .= drupal_render($elements);
- return $output;
-}
-
-/**
- * Form to export PO files for the languages provided.
- *
- * @param $names
- * An associate array with localized language names
- */
-function locale_translate_export_po_form($form, &$form_state, $names) {
- $form['export_title'] = array('#type' => 'item',
- '#title' => t('Export translation'),
+ $form['langcode'] = array(
+ '#type' => 'select',
+ '#title' => t('Language'),
+ '#options' => $language_options,
+ '#empty_option' => t('Source text only'),
+ '#empty_value' => LANGUAGE_SYSTEM,
+ );
+ $form['content_options'] = array(
+ '#type' => 'container',
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name=langcode]' => array('value' => LANGUAGE_SYSTEM),
+ )
+ ),
+ '#tree' => TRUE,
);
- $form['langcode'] = array('#type' => 'select',
- '#title' => t('Language name'),
- '#options' => $names,
- '#description' => t('Select the language to export in Gettext Portable Object (.po) format.'),
+ $form['content_options']['customized'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include customized translations'),
+ '#default_value' => TRUE,
+ );
+ $form['content_options']['not_customized'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include non-customized translations'),
+ '#default_value' => TRUE,
+ );
+ $form['content_options']['not_translated'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Include untranslated text'),
+ '#default_value' => TRUE,
);
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
- return $form;
-}
-/**
- * Translation template export form.
- */
-function locale_translate_export_pot_form() {
- // Complete template export of the strings
- $form['export_title'] = array('#type' => 'item',
- '#title' => t('Export template'),
- '#description' => t('Generate a Gettext Portable Object Template (.pot) file with all strings from the Drupal locale database.'),
+ $form['actions'] = array(
+ '#type' => 'actions'
+ );
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Export')
);
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
- // Reuse PO export submission callback.
- $form['#submit'][] = 'locale_translate_export_po_form_submit';
return $form;
}
/**
- * Process a translation (or template) export form submission.
+ * Processes a translation (or template) export form submission.
*/
-function locale_translate_export_po_form_submit($form, &$form_state) {
+function locale_translate_export_form_submit($form, &$form_state) {
// If template is required, language code is not given.
- $language = NULL;
- if (isset($form_state['values']['langcode'])) {
- $languages = language_list();
- $language = $languages[$form_state['values']['langcode']];
- }
- _locale_export_po($language, _locale_export_po_generate($language, _locale_export_get_strings($language)));
+ $language = ($form_state['values']['langcode'] != LANGUAGE_SYSTEM ? language_load($form_state['values']['langcode']) : NULL);
+ _locale_export_po($language, _locale_export_po_generate($language, _locale_export_get_strings($language, $form_state['values']['content_options'])));
}
/**
@@ -267,7 +273,7 @@ function locale_translate_batch_import($filepath, &$context) {
// we can extract the language code to use for the import from the end.
if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
$file = (object) array('filename' => drupal_basename($filepath), 'uri' => $filepath);
- _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]);
+ _locale_import_read_po('db-store', $file, array(), $langcode[2]);
$context['results'][] = $filepath;
}
}
diff --git a/core/modules/locale/locale.css b/core/modules/locale/locale.css
index 66de82b..96f08b9 100644
--- a/core/modules/locale/locale.css
+++ b/core/modules/locale/locale.css
@@ -6,7 +6,7 @@
#locale-translation-filter-form .form-item-language,
#locale-translation-filter-form .form-item-translation,
-#locale-translation-filter-form .form-item-group {
+#locale-translation-filter-form .form-item-customized {
float: left; /* LTR */
padding-right: .8em; /* LTR */
margin: 0.1em;
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index acf3a80..8ee3332 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -176,6 +176,12 @@ function locale_schema() {
'default' => '',
'description' => 'Language code. References {language}.langcode.',
),
+ 'customized' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0, // LOCALE_NOT_CUSTOMIZED
+ 'description' => 'Boolean indicating whether the translation is modified.',
+ ),
),
'primary key' => array('language', 'lid'),
'foreign keys' => array(
@@ -485,6 +491,27 @@ function locale_update_8005() {
}
/**
+ * Add column to track customized string status to locales_target.
+ */
+function locale_update_8006() {
+ $spec = array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0, // LOCALE_NOT_CUSTOMIZED
+ );
+ db_add_field('locales_target', 'customized', $spec);
+
+ // If l10n_update module was installed retain its status data, but drop the
+ // old field from the table.
+ if (db_field_exists('locales_target', 'l10n_status')) {
+ db_update('locales_target')
+ ->expression('customized', 'l10n_status')
+ ->execute();
+ db_drop_field('locales_target', 'l10n_status');
+ }
+}
+
+/**
* @} End of "addtogroup updates-7.x-to-8.x"
* The next series of updates should start at 9000.
*/
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 6d3ded0..4b00e6c 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -124,7 +124,8 @@ function locale_menu() {
);
$items['admin/config/regional/translate/export'] = array(
'title' => 'Export',
- 'page callback' => 'locale_translate_export_screen', // possibly multiple forms concatenated
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('locale_translate_export_form'),
'access arguments' => array('translate interface'),
'weight' => 30,
'type' => MENU_LOCAL_TASK,
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 0d59f42..595d4e5 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -29,6 +29,7 @@ function _locale_translate_seek() {
$query = array(
'translation' => 'all',
'language' => 'all',
+ 'customized' => 'all',
'string' => '',
);
}
@@ -36,13 +37,16 @@ function _locale_translate_seek() {
$sql_query = db_select('locales_source', 's');
$sql_query->leftJoin('locales_target', 't', 't.lid = s.lid');
$sql_query->fields('s', array('source', 'location', 'context', 'lid'));
- $sql_query->fields('t', array('translation', 'language'));
+ $sql_query->fields('t', array('translation', 'language', 'customized'));
// Compute LIKE section.
switch ($query['translation']) {
case 'translated':
$sql_query->condition('t.translation', '%' . db_like($query['string']) . '%', 'LIKE');
$sql_query->orderBy('t.translation', 'DESC');
+ if ($query['customized'] != 'all') {
+ $sql_query->condition('t.customized', $query['customized'], '=');
+ }
break;
case 'untranslated':
$sql_query->condition(db_and()
@@ -134,7 +138,7 @@ function _locale_translate_seek_query() {
$query = &drupal_static(__FUNCTION__);
if (!isset($query)) {
$query = array();
- $fields = array('string', 'language', 'translation');
+ $fields = array('string', 'language', 'translation', 'customized');
foreach ($fields as $field) {
if (isset($_SESSION['locale_translation_filter'][$field])) {
$query[$field] = $_SESSION['locale_translation_filter'][$field];
@@ -172,7 +176,25 @@ function locale_translation_filters() {
$filters['translation'] = array(
'title' => t('Search in'),
- 'options' => array('all' => t('Both translated and untranslated strings'), 'translated' => t('Only translated strings'), 'untranslated' => t('Only untranslated strings')),
+ 'options' => array(
+ 'all' => t('Both translated and untranslated strings'),
+ 'translated' => t('Only translated strings'),
+ 'untranslated' => t('Untranslated strings')
+ ),
+ );
+
+ $filters['customized'] = array(
+ 'title' => t('Customized'),
+ 'options' => array(
+ 'all' => t('All'),
+ LOCALE_NOT_CUSTOMIZED => t('Non-customized only'),
+ LOCALE_CUSTOMIZED => t('Customized only'),
+ ),
+ 'states' => array(
+ 'visible' => array(
+ ':input[name=translation]' => array('value' => 'translated'),
+ )
+ ),
);
return $filters;
@@ -210,6 +232,9 @@ function locale_translation_filter_form() {
'#size' => 0,
'#options' => $filter['options'],
);
+ if (isset($filter['states'])) {
+ $form['filters']['status'][$key]['#states'] = $filter['states'];
+ }
}
if (!empty($_SESSION['locale_translation_filter'][$key])) {
$form['filters']['status'][$key]['#default_value'] = $_SESSION['locale_translation_filter'][$key];
@@ -435,21 +460,23 @@ function locale_translate_edit_form_submit($form, &$form_state) {
}
if ($has_translation) {
// Only update or insert if we have a value to use.
- if (!empty($translation)) {
+ if (!empty($translation) && ($translation != $value)) {
db_update('locales_target')
->fields(array(
'translation' => $value,
+ 'customized' => LOCALE_CUSTOMIZED,
))
->condition('lid', $lid)
->condition('language', $langcode)
->execute();
}
- else {
+ if (empty($translation)) {
db_insert('locales_target')
->fields(array(
'lid' => $lid,
'translation' => $value,
'language' => $langcode,
+ 'customized' => LOCALE_CUSTOMIZED,
))
->execute();
}
diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test
index f2395ad..0b43a7a 100644
--- a/core/modules/locale/locale.test
+++ b/core/modules/locale/locale.test
@@ -621,9 +621,11 @@ class LocalePluralFormatTest extends DrupalWebTestCase {
// will not overwrite the proper plural formula imported above.
$this->importPoFile($this->getPoFileWithMissingPlural(), array(
'langcode' => 'fr',
+ 'overwrite_options[not_customized]' => 1,
));
$this->importPoFile($this->getPoFileWithBrokenPlural(), array(
'langcode' => 'hr',
+ 'overwrite_options[not_customized]' => 1,
));
// Reset static caches from locale_get_plural() to ensure we get fresh data.
@@ -809,7 +811,6 @@ class LocalePluralFormatTest extends DrupalWebTestCase {
$name = tempnam('temporary://', "po_") . '.po';
file_put_contents($name, $contents);
$options['files[file]'] = $name;
- $options['mode'] = LOCALE_IMPORT_OVERWRITE;
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
drupal_unlink($name);
}
@@ -972,7 +973,6 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase {
// strings are kept.
$this->importPoFile($this->getOverwritePoFile(), array(
'langcode' => 'fr',
- 'mode' => 1, // Existing strings are kept, only new strings are added.
));
// The import should have created 1 string.
@@ -994,7 +994,7 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase {
// strings are overwritten.
$this->importPoFile($this->getOverwritePoFile(), array(
'langcode' => 'fr',
- 'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
+ 'overwrite_options[not_customized]' => 1,
));
// The import should have updated 2 strings.
@@ -1074,7 +1074,7 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase {
// Try importing a .po file.
$this->importPoFile($this->getPoFileWithEmptyMsgstr(), array(
'langcode' => $langcode,
- 'mode' => 0,
+ 'overwrite_options[not_customized]' => 1,
));
$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 is the language indicator on the translation search screen for
@@ -1102,7 +1102,9 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase {
* Additional options to pass to the translation import form.
*/
function importPoFile($contents, array $options = array()) {
- $name = tempnam('temporary://', "po_") . '.po';
+ //$name = tempnam('temporary://', "po_") . '.po';
+ //debug($name);
+ $name = 'sites/default/files/tmp/po_' . rand() . '.po';
file_put_contents($name, $contents);
$options['files[file]'] = $name;
$this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
@@ -1316,9 +1318,6 @@ class LocaleExportFunctionalTest extends DrupalWebTestCase {
*/
function testExportTranslationTemplateFile() {
// Get the translation template file.
- // There are two 'Export' buttons on this page, but it somehow works. It'd
- // be better if we could use the submit button id like documented but that
- // doesn't work.
$this->drupalPost('admin/config/regional/translate/export', array(), t('Export'));
// Ensure we have a translation file.
$this->assertRaw('# LANGUAGE translation of PROJECT', t('Exported translation template file.'));
diff --git a/core/modules/simpletest/tests/upgrade/drupal-7.language.database.php b/core/modules/simpletest/tests/upgrade/drupal-7.language.database.php
index 99dc318..4ddf0f8 100644
--- a/core/modules/simpletest/tests/upgrade/drupal-7.language.database.php
+++ b/core/modules/simpletest/tests/upgrade/drupal-7.language.database.php
@@ -553,6 +553,39 @@ db_insert('locales_target')->fields(array(
))
->execute();
+ // Add block_langcode table from language.install schema.
+ db_create_table('block_langcode', array(
+ 'fields' => array(
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ ),
+ 'delta' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ ),
+ 'langcode' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ ),
+ ),
+ 'primary key' => array(
+ 'module',
+ 'delta',
+ 'langcode'
+ ),
+ 'indexes' => array(
+ 'langcode' => array(
+ 'langcode',
+ ),
+ ),
+ 'module' => 'language',
+ 'name' => 'block_langcode',
+ ));
+
// Set up variables needed for language support.
db_insert('variable')->fields(array(
'name',