Index: CHANGELOG.txt =================================================================== RCS file: /cvs/drupal/drupal/CHANGELOG.txt,v retrieving revision 1.310 diff -u -p -r1.310 CHANGELOG.txt --- CHANGELOG.txt 21 May 2009 21:12:22 -0000 1.310 +++ CHANGELOG.txt 31 May 2009 07:12:19 -0000 @@ -119,6 +119,9 @@ Drupal 7.0, xxxx-xx-xx (development vers with other blocks in the same region. * Blocks can now return structured arrays for later rendering just like page callbacks. + * Made the help text area a full featured region with blocks. +- Translation system + * The translation system now supports message context (msgctxt). - Upgraded the core JavaScript library to jQuery version 1.3.2. - Upgraded the jQuery Forms library to 2.21. Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.913 diff -u -p -r1.913 common.inc --- includes/common.inc 27 May 2009 14:33:41 -0000 1.913 +++ includes/common.inc 31 May 2009 07:12:21 -0000 @@ -1095,37 +1095,44 @@ function fix_gpc_magic() { * - @variable: escape plain text to HTML (check_plain) * - %variable: escape text and theme as a placeholder for user-submitted * content (check_plain + theme_placeholder) - * @param $langcode - * Optional language code to translate to a language other than what is used - * to display the page. + * @param $options + * An associative array of additional options, with the following keys: + * - 'langcode' (default to the current language) The language code to + * translate to a language other than what is used to display the page. + * - 'context' (default to the empty context) The context the source string + * belongs to. * @return * The translated string. */ -function t($string, $args = array(), $langcode = NULL) { +function t($string, array $args = array(), array $options = array()) { global $language; static $custom_strings; - if (!isset($langcode)) { - $langcode = isset($language->language) ? $language->language : 'en'; + // Merge in default. + if (empty($options['langcode'])) { + $options['langcode'] = isset($language->language) ? $language->language : 'en'; + } + if (empty($options['context'])) { + $options['context'] = ''; } // First, check for an array of customized strings. If present, use the array // *instead of* database lookups. This is a high performance way to provide a // handful of string replacements. See settings.php for examples. // Cache the $custom_strings variable to improve performance. - if (!isset($custom_strings[$langcode])) { - $custom_strings[$langcode] = variable_get('locale_custom_strings_' . $langcode, array()); + if (!isset($custom_strings[$options['langcode']])) { + $custom_strings[$options['langcode']] = variable_get('locale_custom_strings_' . $options['langcode'], array()); } // Custom strings work for English too, even if locale module is disabled. - if (isset($custom_strings[$langcode][$string])) { - $string = $custom_strings[$langcode][$string]; + if (isset($custom_strings[$options['langcode']][$options['context']][$string])) { + $string = $custom_strings[$options['langcode']][$options['context']][$string]; } // Translate with locale module if enabled. // We don't use drupal_function_exists() here, because it breaks the testing // framework if the locale module is enabled in the parent site (we cannot // unload functions in PHP). - elseif (module_exists('locale') && $langcode != 'en') { - $string = locale($string, $langcode); + elseif (module_exists('locale') && $options['langcode'] != 'en') { + $string = locale($string, $options['context'], $options['langcode']); } if (empty($args)) { return $string; @@ -1680,34 +1687,37 @@ function format_xml_elements($array) { * content (check_plain + theme_placeholder) * Note that you do not need to include @count in this array. * This replacement is done automatically for the plural case. - * @param $langcode - * Optional language code to translate to a language other than - * what is used to display the page. + * @param $options + * An associative array of additional options, with the following keys: + * - 'langcode' (default to the current language) The language code to + * translate to a language other than what is used to display the page. + * - 'context' (default to the empty context) The context the source string + * belongs to. * @return * A translated string. */ -function format_plural($count, $singular, $plural, $args = array(), $langcode = NULL) { +function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) { $args['@count'] = $count; if ($count == 1) { - return t($singular, $args, $langcode); + return t($singular, $args, $options); } // Get the plural index through the gettext formula. - $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, $langcode) : -1; + $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1; // Backwards compatibility. if ($index < 0) { - return t($plural, $args, $langcode); + return t($plural, $args, $options); } else { switch ($index) { case "0": - return t($singular, $args, $langcode); + return t($singular, $args, $options); case "1": - return t($plural, $args, $langcode); + return t($plural, $args, $options); default: unset($args['@count']); $args['@count[' . $index . ']'] = $count; - return t(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $langcode); + return t(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $options); } } } @@ -1746,19 +1756,19 @@ function parse_size($size) { */ function format_size($size, $langcode = NULL) { if ($size < DRUPAL_KILOBYTE) { - return format_plural($size, '1 byte', '@count bytes', array(), $langcode); + return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode)); } else { $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes. $units = array( - t('@size KB', array(), $langcode), - t('@size MB', array(), $langcode), - t('@size GB', array(), $langcode), - t('@size TB', array(), $langcode), - t('@size PB', array(), $langcode), - t('@size EB', array(), $langcode), - t('@size ZB', array(), $langcode), - t('@size YB', array(), $langcode), + t('@size KB', array(), array('langcode' => $langcode)), + t('@size MB', array(), array('langcode' => $langcode)), + t('@size GB', array(), array('langcode' => $langcode)), + t('@size TB', array(), array('langcode' => $langcode)), + t('@size PB', array(), array('langcode' => $langcode)), + t('@size EB', array(), array('langcode' => $langcode)), + t('@size ZB', array(), array('langcode' => $langcode)), + t('@size YB', array(), array('langcode' => $langcode)), ); foreach ($units as $unit) { if (round($size, 2) >= DRUPAL_KILOBYTE) { @@ -1799,7 +1809,7 @@ function format_interval($timestamp, $gr foreach ($units as $key => $value) { $key = explode('|', $key); if ($timestamp >= $value) { - $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), $langcode); + $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), array('langcode' => $langcode)); $timestamp %= $value; $granularity--; } @@ -1808,7 +1818,7 @@ function format_interval($timestamp, $gr break; } } - return $output ? $output : t('0 sec', array(), $langcode); + return $output ? $output : t('0 sec', array(), array('langcode' => $langcode)); } /** @@ -1877,13 +1887,13 @@ function format_date($timestamp, $type = for ($i = 0; $i < $max; $i++) { $c = $format[$i]; if (strpos('AaeDlMT', $c) !== FALSE) { - $date .= t(date_format($date_time, $c), array(), $langcode); + $date .= t(date_format($date_time, $c), array(), array('langcode' => $langcode)); } elseif ($c == 'F') { // Special treatment for long month names: May is both an abbreviation // and a full month name in English, but other languages have // different abbreviations. - $date .= trim(t('!long-month-name ' . date_format($date_time, $c), array('!long-month-name' => ''), $langcode)); + $date .= t(date_format($date_time, $c), array(), array('context' => 'Long month name', 'langcode' => $langcode)); } elseif (strpos('BcdGgHhIijLmNnOoPSstUuWwYyZz', $c) !== FALSE) { $date .= date_format($date_time, $c); Index: includes/locale.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/locale.inc,v retrieving revision 1.217 diff -u -p -r1.217 locale.inc --- includes/locale.inc 25 May 2009 18:22:30 -0000 1.217 +++ includes/locale.inc 31 May 2009 07:12:23 -0000 @@ -885,31 +885,36 @@ function locale_translate_export_po_form */ function locale_translate_edit_form(&$form_state, $lid) { // Fetch source string, if possible. - $source = db_query('SELECT source, textgroup, location FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject(); + $source = db_query('SELECT source, context, textgroup, location FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject(); if (!$source) { drupal_set_message(t('String not found.'), 'error'); drupal_goto('admin/international/translate/translate'); } // Add original text to the top and some values for form altering. - $form = array( - 'original' => array( - '#type' => 'item', - '#title' => t('Original text'), - '#markup' => check_plain(wordwrap($source->source, 0)), - ), - 'lid' => array( - '#type' => 'value', - '#value' => $lid - ), - 'textgroup' => array( - '#type' => 'value', - '#value' => $source->textgroup, - ), - 'location' => array( - '#type' => 'value', - '#value' => $source->location - ), + $form['original'] = array( + '#type' => 'item', + '#title' => t('Original text'), + '#markup' => check_plain(wordwrap($source->source, 0)), + ); + if (!empty($source->context)) { + $form['context'] = array( + '#type' => 'item', + '#title' => t('Context'), + '#markup' => check_plain($source->context), + ); + } + $form['lid'] = array( + '#type' => 'value', + '#value' => $lid + ); + $form['textgroup'] = array( + '#type' => 'value', + '#value' => $source->textgroup, + ); + $form['location'] = array( + '#type' => 'value', + '#value' => $source->location ); // Include default form controls with empty values for all languages. @@ -1037,7 +1042,7 @@ function locale_translate_edit_form_subm * String deletion confirmation page. */ function locale_translate_delete_page($lid) { - if ($source = db_query('SELECT * FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject()) { + if ($source = db_query('SELECT lid, source FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject()) { return drupal_get_form('locale_translate_delete_form', $source); } else { @@ -1289,8 +1294,26 @@ function _locale_import_read_po($op, $fi $current["msgid"] = $quoted; $context = "MSGID"; } + elseif (!strncmp("msgctxt", $line, 7)) { + if ($context == "MSGSTR") { // End current entry, start a new one + _locale_import_one_string($op, $current, $mode, $lang, $file, $group); + $current = array(); + } + elseif (!empty($current["msgctxt"])) { // Already in this context? Parse error + _locale_import_message('The translation file %filename contains an error: "msgctxt" is unexpected on line %line.', $file, lineno); + return FALSE; + } + $line = trim(substr($line, 7)); + $quoted = _locale_import_parse_quoted($line); + if ($quoted === FALSE) { + _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); + return FALSE; + } + $current["msgctxt"] = $quoted; + $context = "MSGCTXT"; + } elseif (!strncmp("msgstr[", $line, 7)) { - if (($context != "MSGID") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgid_plural, or msgstr[] + if (($context != "MSGID") && ($context != "MSGCTXT") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgxtxt, msgid_plural, or msgstr[] _locale_import_message('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $file, $lineno); return FALSE; } @@ -1310,7 +1333,7 @@ function _locale_import_read_po($op, $fi $context = "MSGSTR_ARR"; } elseif (!strncmp("msgstr", $line, 6)) { - if ($context != "MSGID") { // Should come just after a msgid block + if (($context != "MSGID") && ($context != "MSGCTXT")) { // Should come just after a msgid or msgctxt block _locale_import_message('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $file, $lineno); return FALSE; } @@ -1332,6 +1355,9 @@ function _locale_import_read_po($op, $fi if (($context == "MSGID") || ($context == "MSGID_PLURAL")) { $current["msgid"] .= $quoted; } + elseif ($context == "MSGCTXT") { + $current["msgctxt"] .= $quoted; + } elseif ($context == "MSGSTR") { $current["msgstr"] .= $quoted; } @@ -1346,7 +1372,7 @@ function _locale_import_read_po($op, $fi } // End of PO file, flush last entry - if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { + if (!empty($current) && !empty($current['msgstr'])) { _locale_import_one_string($op, $current, $mode, $lang, $file, $group); } elseif ($context != "COMMENT") { @@ -1403,7 +1429,7 @@ function _locale_import_one_string($op, // Store string in memory (only supports single strings) case 'mem-store': - $strings[$value['msgid']] = $value['msgstr']; + $strings[isset($value['msgctxt']) ? $value['msgctxt'] : ''][$value['msgid']] = $value['msgstr']; return; // Called at end of import to inform the user @@ -1456,7 +1482,7 @@ function _locale_import_one_string($op, if ($key == 0) { $plid = 0; } - $plid = _locale_import_one_string_db($report, $lang, $english[$key], $trans, $group, $comments, $mode, $plid, $key); + $plid = _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english[$key], $trans, $group, $comments, $mode, $plid, $key); } } @@ -1464,7 +1490,7 @@ function _locale_import_one_string($op, // A simple string to import. $english = $value['msgid']; $translation = $value['msgstr']; - _locale_import_one_string_db($report, $lang, $english, $translation, $group, $comments, $mode); + _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english, $translation, $group, $comments, $mode); } } } // end of db-store operation @@ -1478,6 +1504,8 @@ function _locale_import_one_string($op, * array(inserts, updates, deletes). * @param $langcode * Language code to import string into. + * @param $context + * The context of this string. * @param $source * Source string. * @param $translation @@ -1495,8 +1523,8 @@ function _locale_import_one_string($op, * @return * The string ID of the existing string modified or the new string added. */ -function _locale_import_one_string_db(&$report, $langcode, $source, $translation, $textgroup, $location, $mode, $plid = 0, $plural = 0) { - $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND textgroup = :textgroup", array(':source' => $source, ':textgroup' => $textgroup))->fetchField(); +function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $textgroup, $location, $mode, $plid = 0, $plural = 0) { + $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(':source' => $source, ':context' => $context, ':textgroup' => $textgroup))->fetchField(); if (!empty($translation)) { // Skip this string unless it passes a check for dangerous code. @@ -1549,7 +1577,12 @@ function _locale_import_one_string_db(&$ else { // No such source string in the database yet. $lid = db_insert('locales_source') - ->fields(array('location' => $location, 'source' => $source, 'textgroup' => $textgroup)) + ->fields(array( + 'location' => $location, + 'source' => $source, + 'context' => (string) $context, + 'textgroup' => $textgroup, + )) ->execute(); db_insert('locales_target') @@ -1934,6 +1967,7 @@ function _locale_parse_js_file($filepath ->fields(array( 'location' => $filepath, 'source' => $string, + 'context' => '', 'textgroup' => 'default', )) ->execute(); @@ -1960,17 +1994,18 @@ function _locale_parse_js_file($filepath */ function _locale_export_get_strings($language = NULL, $group = 'default') { if (isset($language)) { - $result = db_query("SELECT s.lid, s.source, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':language' => $language->language, ':textgroup' => $group)); + $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':language' => $language->language, ':textgroup' => $group)); } else { - $result = db_query("SELECT s.lid, s.source, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':textgroup' => $group)); + $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':textgroup' => $group)); } $strings = array(); foreach ($result as $child) { $string = array( 'comment' => $child->location, 'source' => $child->source, - 'translation' => isset($child->translation) ? $child->translation : '' + 'context' => $child->context, + 'translation' => isset($child->translation) ? $child->translation : '', ); if ($child->plid) { // Has a parent lid. Since we process in the order of plids, @@ -2047,6 +2082,9 @@ function _locale_export_po_generate($lan $output .= '#: ' . $string['comment'] . "\n"; } $output .= 'msgid ' . _locale_export_string($string['source']); + if (!empty($string['context'])) { + $output .= 'msgctxt ' . _locale_export_string($string['context']); + } if (!empty($string['plural'])) { $plural = $string['plural']; $output .= 'msgid_plural ' . _locale_export_string($strings[$plural]['source']); @@ -2201,7 +2239,7 @@ 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', 'lid', 'textgroup')); + $sql_query->fields('s', array('source', 'location', 'context', 'lid', 'textgroup')); $sql_query->fields('t', array('translation', 'language')); // Compute LIKE section. @@ -2244,7 +2282,7 @@ function _locale_translate_seek() { $locales = $sql_query->execute(); $groups = module_invoke_all('locale', 'groups'); - $header = array(t('Text group'), t('String'), ($limit_language) ? t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan' => '2')); + $header = array(t('Text group'), t('String'), t('Context'), ($limit_language) ? t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan' => '2')); $strings = array(); foreach ($locales as $locale) { @@ -2254,6 +2292,7 @@ function _locale_translate_seek() { 'languages' => array(), 'location' => $locale->location, 'source' => $locale->source, + 'context' => $locale->context, ); } if (isset($locale->language)) { @@ -2266,6 +2305,7 @@ function _locale_translate_seek() { $rows[] = array( $groups[$string['group']], array('data' => check_plain(truncate_utf8($string['source'], 150, FALSE, TRUE)) . '
' . $string['location'] . ''), + $string['context'], array('data' => _locale_translate_language_list($string['languages'], $limit_language), 'align' => 'center'), array('data' => l(t('edit'), "admin/international/translate/edit/$lid", array('query' => drupal_get_destination())), 'class' => 'nowrap'), array('data' => l(t('delete'), "admin/international/translate/delete/$lid", array('query' => drupal_get_destination())), 'class' => 'nowrap'), Index: modules/contact/contact.module =================================================================== RCS file: /cvs/drupal/drupal/modules/contact/contact.module,v retrieving revision 1.115 diff -u -p -r1.115 contact.module --- modules/contact/contact.module 27 May 2009 18:33:56 -0000 1.115 +++ modules/contact/contact.module 31 May 2009 07:12:23 -0000 @@ -178,13 +178,13 @@ function contact_mail($key, &$message, $ case 'page_mail': case 'page_copy': $contact = $params['contact']; - $message['subject'] .= t('[!category] !subject', array('!category' => $contact['category'], '!subject' => $params['subject']), $language->language); - $message['body'][] = t("!name sent a message using the contact form at !form.", array('!name' => $params['name'], '!form' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language))), $language->language); + $message['subject'] .= t('[!category] !subject', array('!category' => $contact['category'], '!subject' => $params['subject']), array('langcode' => $language->language)); + $message['body'][] = t("!name sent a message using the contact form at !form.", array('!name' => $params['name'], '!form' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language))), array('langcode' => $language->language)); $message['body'][] = $params['message']; break; case 'page_autoreply': $contact = $params['contact']; - $message['subject'] .= t('[!category] !subject', array('!category' => $contact['category'], '!subject' => $params['subject']), $language->language); + $message['subject'] .= t('[!category] !subject', array('!category' => $contact['category'], '!subject' => $params['subject']), array('langcode' => $language->language)); $message['body'][] = $contact['reply']; break; case 'user_mail': @@ -193,9 +193,9 @@ function contact_mail($key, &$message, $ $account = $params['account']; $message['subject'] .= '[' . variable_get('site_name', 'Drupal') . '] ' . $params['subject']; $message['body'][] = "$account->name,"; - $message['body'][] = t("!name (!name-url) has sent you a message via your contact form (!form-url) at !site.", array('!name' => $user->name, '!name-url' => url("user/$user->uid", array('absolute' => TRUE, 'language' => $language)), '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)), '!site' => variable_get('site_name', 'Drupal')), $language->language); - $message['body'][] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE, 'language' => $language))), $language->language); - $message['body'][] = t('Message:', NULL, $language->language); + $message['body'][] = t("!name (!name-url) has sent you a message via your contact form (!form-url) at !site.", array('!name' => $user->name, '!name-url' => url("user/$user->uid", array('absolute' => TRUE, 'language' => $language)), '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)), '!site' => variable_get('site_name', 'Drupal')), array('langcode' => $language->language)); + $message['body'][] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE, 'language' => $language))), array('langcode' => $language->language)); + $message['body'][] = t('Message:', array(), array('langcode' => $language->language)); $message['body'][] = $params['message']; break; } Index: modules/locale/locale.install =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.install,v retrieving revision 1.41 diff -u -p -r1.41 locale.install --- modules/locale/locale.install 27 May 2009 19:54:21 -0000 1.41 +++ modules/locale/locale.install 31 May 2009 07:12:23 -0000 @@ -230,6 +230,26 @@ function locale_update_6006() { */ /** + * @defgroup updates-6.x-to-7.x Locale updates from 6.x to 7.x + * @{ + */ + +/** + * Allow longer location. + */ +function locale_update_7000() { + $ret = array(); + db_add_field($ret, 'locales_source', 'context', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'description' => 'The context this string applies to.')); + db_drop_index($ret, 'locales_source', 'source'); + db_add_index($ret, 'locales_source', 'source_context', array(array('source', 30), 'context')); + return $ret; +} + +/** + * @} End of "defgroup updates-6.x-to-7.x" + */ + +/** * Implement hook_uninstall(). */ function locale_uninstall() { @@ -381,6 +401,12 @@ function locale_schema() { 'not null' => TRUE, 'description' => 'The original string in English.', ), + 'context' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'The context this string applies to.', + ), 'version' => array( 'type' => 'varchar', 'length' => 20, @@ -391,7 +417,7 @@ function locale_schema() { ), 'primary key' => array('lid'), 'indexes' => array( - 'source' => array(array('source', 30)), + 'source_context' => array(array('source', 30), 'context'), ), ); Index: modules/locale/locale.module =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v retrieving revision 1.241 diff -u -p -r1.241 locale.module --- modules/locale/locale.module 27 May 2009 18:33:58 -0000 1.241 +++ modules/locale/locale.module 31 May 2009 07:12:24 -0000 @@ -341,12 +341,14 @@ function locale_theme() { * A string to look up translation for. If omitted, all the * cached strings will be returned in all languages already * used on the page. + * @param $context + * The context of this string. * @param $langcode * Language code to use for the lookup. * @param $reset * Set to TRUE to reset the in-memory cache. */ -function locale($string = NULL, $langcode = NULL, $reset = FALSE) { +function locale($string = NULL, $context = NULL, $langcode = NULL, $reset = FALSE) { global $language; static $locale_t; @@ -377,9 +379,9 @@ function locale($string = NULL, $langcod // Refresh database stored cache of translations for given language. // We only store short strings used in current version, to improve // performance and consume less memory. - $result = db_query("SELECT s.source, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.textgroup = 'default' AND s.version = :version AND LENGTH(s.source) < 75", array(':language' => $langcode, ':version' => VERSION)); + $result = db_query("SELECT s.source, s.context, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.textgroup = 'default' AND s.version = :version AND LENGTH(s.source) < 75", array(':language' => $langcode, ':version' => VERSION)); foreach ($result as $data) { - $locale_t[$langcode][$data->source] = (empty($data->translation) ? TRUE : $data->translation); + $locale_t[$langcode][$data->context][$data->source] = (empty($data->translation) ? TRUE : $data->translation); } cache_set('locale:' . $langcode, $locale_t[$langcode]); } @@ -387,17 +389,18 @@ function locale($string = NULL, $langcod } // If we have the translation cached, skip checking the database - if (!isset($locale_t[$langcode][$string])) { + if (!isset($locale_t[$langcode][$context][$string])) { // We do not have this translation cached, so get it from the DB. - $translation = db_query("SELECT s.lid, t.translation, s.version FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.source = :source AND s.textgroup = 'default'", array( + $translation = db_query("SELECT s.lid, t.translation, s.version 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 AND s.textgroup = 'default'", array( ':language' => $langcode, ':source' => $string, + ':context' => (string) $context, ))->fetchObject(); if ($translation) { // We have the source string at least. // Cache translation string or TRUE if no translation exists. - $locale_t[$langcode][$string] = (empty($translation->translation) ? TRUE : $translation->translation); + $locale_t[$langcode][$context][$string] = (empty($translation->translation) ? TRUE : $translation->translation); if ($translation->version != VERSION) { // This is the first use of this string under current Drupal version. Save version @@ -416,17 +419,18 @@ function locale($string = NULL, $langcod ->fields(array( 'location' => request_uri(), 'source' => $string, + 'context' => (string) $context, 'textgroup' => 'default', 'version' => VERSION, )) ->execute(); - $locale_t[$langcode][$string] = TRUE; + $locale_t[$langcode][$context][$string] = TRUE; // Clear locale cache so this string can be added in a later request. cache_clear_all('locale:', 'cache', TRUE); } } - return ($locale_t[$langcode][$string] === TRUE ? $string : $locale_t[$langcode][$string]); + return ($locale_t[$langcode][$context][$string] === TRUE ? $string : $locale_t[$langcode][$context][$string]); } /** Index: modules/locale/locale.test =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v retrieving revision 1.24 diff -u -p -r1.24 locale.test --- modules/locale/locale.test 27 May 2009 11:45:00 -0000 1.24 +++ modules/locale/locale.test 31 May 2009 07:12:25 -0000 @@ -212,9 +212,9 @@ class LocaleTranslationFunctionalTest ex ); $this->drupalPost('admin/international/language/add', $edit, t('Add custom language')); // Add string. - t($name, array(), $langcode); + t($name, array(), array('langcode' => $langcode)); // Reset locale cache. - locale(NULL, NULL, TRUE); + locale(NULL, NULL, NULL, TRUE); $this->assertText($langcode, t('Language code found.')); $this->assertText($name, t('Name found.')); $this->assertText($native, t('Native found.')); @@ -251,7 +251,7 @@ class LocaleTranslationFunctionalTest ex $this->drupalPost(NULL, $edit, t('Save translations')); $this->assertText(t('The string has been saved.'), t('The string has been saved.')); $this->assertEqual($this->getUrl(), url('admin/international/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.')); - $this->assertTrue($name != $translation && t($name, array(), $langcode) == $translation, t('t() works.')); + $this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, t('t() works.')); $this->drupalPost('admin/international/translate/translate', $search, t('Filter')); // The indicator should not be here. $this->assertNoRaw($language_indicator, t('String is translated.')); @@ -344,7 +344,7 @@ class LocaleTranslationFunctionalTest ex ); $this->drupalPost('admin/international/language/add', $edit, t('Add custom language')); // Add string. - t($name, array(), $langcode); + t($name, array(), array('langcode' => $langcode)); // Reset locale cache. $search = array( 'string' => $name, @@ -405,9 +405,9 @@ class LocaleTranslationFunctionalTest ex ); $this->drupalPost('admin/international/language/add', $edit, t('Add custom language')); // Add string. - t($name, array(), $langcode); + t($name, array(), array('langcode' => $langcode)); // Reset locale cache. - locale(NULL, NULL, TRUE); + locale(NULL, NULL, NULL, TRUE); $this->drupalLogout(); // Search for the name. @@ -553,13 +553,9 @@ class LocaleImportFunctionalTest extends */ function testStandalonePoFile() { // Try importing a .po file. - $name = tempnam(file_directory_temp(), "po_"); - file_put_contents($name, $this->getPoFile()); - $this->drupalPost('admin/international/translate/import', array( + $this->importPoFile($this->getPoFile(), array( 'langcode' => 'fr', - 'files[file]' => $name, - ), t('Import')); - unlink($name); + )); // The import should automatically create the corresponding language. $this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), t('The language has been automatically created.')); @@ -570,31 +566,28 @@ class LocaleImportFunctionalTest extends // Ensure we were redirected correctly. $this->assertEqual($this->getUrl(), url('admin/international/translate', array('absolute' => TRUE)), t('Correct page redirection.')); + // Try importing a .po file with invalid tags in the default text group. - $name = tempnam(file_directory_temp(), "po_"); - file_put_contents($name, $this->getBadPoFile()); - $this->drupalPost('admin/international/translate/import', array( + $this->importPoFile($this->getBadPoFile(), array( 'langcode' => 'fr', - 'files[file]' => $name, - ), t('Import')); - unlink($name); + )); + // The import should have created 1 string and rejected 2. $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); $skip_message = format_plural(2, 'One translation string was skipped because it contains disallowed HTML.', '@count translation strings were skipped because they contain disallowed HTML.'); $this->assertRaw($skip_message, t('Unsafe strings were skipped.')); + // Try importing a .po file with invalid tags in a non default text group. - $name = tempnam(file_directory_temp(), "po_"); - file_put_contents($name, $this->getBadPoFile()); - $this->drupalPost('admin/international/translate/import', array( + $this->importPoFile($this->getBadPoFile(), array( 'langcode' => 'fr', - 'files[file]' => $name, 'group' => 'custom', - ), t('Import')); - unlink($name); + )); + // The import should have created 3 strings. $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 3, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); + // Try importing a .po file which doesn't exist. $name = $this->randomName(16); $this->drupalPost('admin/international/translate/import', array( @@ -605,16 +598,14 @@ class LocaleImportFunctionalTest extends $this->assertEqual($this->getUrl(), url('admin/international/translate/import', array('absolute' => TRUE)), t('Correct page redirection.')); $this->assertText(t('File to import not found.'), t('File to import not found message.')); + // Try importing a .po file with overriding strings, and ensure existing // strings are kept. - $name = tempnam(file_directory_temp(), "po_"); - file_put_contents($name, $this->getOverwritePoFile()); - $this->drupalPost('admin/international/translate/import', array( + $this->importPoFile($this->getOverwritePoFile(), array( 'langcode' => 'fr', - 'files[file]' => $name, 'mode' => 1, // Existing strings are kept, only new strings are added. - ), t('Import')); - unlink($name); + )); + // The import should have created 1 string. $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.')); // Ensure string wasn't overwritten. @@ -627,16 +618,14 @@ class LocaleImportFunctionalTest extends $this->drupalPost('admin/international/translate/translate', $search, t('Filter')); $this->assertText(t('No strings found for your search.'), t('String not overwritten by imported string.')); + // Try importing a .po file with overriding strings, and ensure existing // strings are overwritten. - $name = tempnam(file_directory_temp(), "po_"); - file_put_contents($name, $this->getOverwritePoFile()); - $this->drupalPost('admin/international/translate/import', array( + $this->importPoFile($this->getOverwritePoFile(), array( 'langcode' => 'fr', - 'files[file]' => $name, 'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added. - ), t('Import')); - unlink($name); + )); + // The import should have updated 2 strings. $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), t('The translation file was successfully imported.')); // Ensure string was overwritten. @@ -690,6 +679,36 @@ class LocaleImportFunctionalTest extends } /** + * Test automatic importation of a module's translation files when a language + * is enabled. + */ + function testLanguageContext() { + // Try importing a .po file. + $this->importPoFile($this->getPoFileWithContext(), array( + 'langcode' => 'hr', + )); + + $this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', t('Long month name context is working.')); + $this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Short month name')), 'Svi.', t('Short month name context is working.')); + } + + /** + * Helper function: import a standalone .po file in a given language. + * + * @param $contents + * Contents of the .po file to import. + * @param $options + * Additional options to pass to the translation import form. + */ + function importPoFile($contents, array $options = array()) { + $name = tempnam(file_directory_temp(), "po_"); + file_put_contents($name, $contents); + $options['files[file]'] = $name; + $this->drupalPost('admin/international/translate/import', $options, t('Import')); + unlink($name); + } + + /** * Helper function that returns a proper .po file. */ function getPoFile() { @@ -772,6 +791,32 @@ msgstr "Jour" EOF; } + /** + * Helper function that returns a .po file with context. + */ + function getPoFileWithContext() { + // Croatian (code hr) is one the the languages that have a different + // form for the full name and the abbreviated name for the month May. + return <<< EOF +msgid "" +msgstr "" +"Project-Id-Version: Drupal 6\\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=UTF-8\\n" +"Content-Transfer-Encoding: 8bit\\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n" + +msgctxt "Long month name" +msgid "May" +msgstr "Svibanj" + +msgctxt "Short month name" +msgid "May" +msgstr "Svi." +EOF; + } + + } /** Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.475 diff -u -p -r1.475 taxonomy.module --- modules/taxonomy/taxonomy.module 27 May 2009 18:34:01 -0000 1.475 +++ modules/taxonomy/taxonomy.module 31 May 2009 07:12:25 -0000 @@ -327,7 +327,7 @@ function taxonomy_check_vocabulary_hiera $hierarchy = 2; break; } - elseif (count($term->parents) == 1 && 0 !== array_shift($term->parents)) { + elseif (count($term->parents) == 1 && current($term->parents) != 0) { $hierarchy = 1; } } Index: modules/update/update.module =================================================================== RCS file: /cvs/drupal/drupal/modules/update/update.module,v retrieving revision 1.35 diff -u -p -r1.35 update.module --- modules/update/update.module 27 May 2009 18:34:02 -0000 1.35 +++ modules/update/update.module 31 May 2009 07:12:26 -0000 @@ -404,11 +404,11 @@ function update_refresh() { function update_mail($key, &$message, $params) { $language = $message['language']; $langcode = $language->language; - $message['subject'] .= t('New release(s) available for !site_name', array('!site_name' => variable_get('site_name', 'Drupal')), $langcode); + $message['subject'] .= t('New release(s) available for !site_name', array('!site_name' => variable_get('site_name', 'Drupal')), array('langcode' => $langcode)); foreach ($params as $msg_type => $msg_reason) { $message['body'][] = _update_message_text($msg_type, $msg_reason, FALSE, $language); } - $message['body'][] = t('See the available updates page for more information:', array(), $langcode) . "\n" . url('admin/reports/updates', array('absolute' => TRUE, 'language' => $language)); + $message['body'][] = t('See the available updates page for more information:', array(), array('langcode' => $langcode)) . "\n" . url('admin/reports/updates', array('absolute' => TRUE, 'language' => $language)); } /** @@ -437,53 +437,53 @@ function _update_message_text($msg_type, switch ($msg_reason) { case UPDATE_NOT_SECURE: if ($msg_type == 'core') { - $text = t('There is a security update available for your version of Drupal. To ensure the security of your server, you should update immediately!', array(), $langcode); + $text = t('There is a security update available for your version of Drupal. To ensure the security of your server, you should update immediately!', array(), array('langcode' => $langcode)); } else { - $text = t('There are security updates available for one or more of your modules or themes. To ensure the security of your server, you should update immediately!', array(), $langcode); + $text = t('There are security updates available for one or more of your modules or themes. To ensure the security of your server, you should update immediately!', array(), array('langcode' => $langcode)); } break; case UPDATE_REVOKED: if ($msg_type == 'core') { - $text = t('Your version of Drupal has been revoked and is no longer available for download. Upgrading is strongly recommended!', array(), $langcode); + $text = t('Your version of Drupal has been revoked and is no longer available for download. Upgrading is strongly recommended!', array(), array('langcode' => $langcode)); } else { - $text = t('The installed version of at least one of your modules or themes has been revoked and is no longer available for download. Upgrading or disabling is strongly recommended!', array(), $langcode); + $text = t('The installed version of at least one of your modules or themes has been revoked and is no longer available for download. Upgrading or disabling is strongly recommended!', array(), array('langcode' => $langcode)); } break; case UPDATE_NOT_SUPPORTED: if ($msg_type == 'core') { - $text = t('Your version of Drupal is no longer supported. Upgrading is strongly recommended!', array(), $langcode); + $text = t('Your version of Drupal is no longer supported. Upgrading is strongly recommended!', array(), array('langcode' => $langcode)); } else { - $text = t('The installed version of at least one of your modules or themes is no longer supported. Upgrading or disabling is strongly recommended! Please see the project homepage for more details.', array(), $langcode); + $text = t('The installed version of at least one of your modules or themes is no longer supported. Upgrading or disabling is strongly recommended! Please see the project homepage for more details.', array(), array('langcode' => $langcode)); } break; case UPDATE_NOT_CURRENT: if ($msg_type == 'core') { - $text = t('There are updates available for your version of Drupal. To ensure the proper functioning of your site, you should update as soon as possible.', array(), $langcode); + $text = t('There are updates available for your version of Drupal. To ensure the proper functioning of your site, you should update as soon as possible.', array(), array('langcode' => $langcode)); } else { - $text = t('There are updates available for one or more of your modules or themes. To ensure the proper functioning of your site, you should update as soon as possible.', array(), $langcode); + $text = t('There are updates available for one or more of your modules or themes. To ensure the proper functioning of your site, you should update as soon as possible.', array(), array('langcode' => $langcode)); } break; case UPDATE_UNKNOWN: case UPDATE_NOT_CHECKED: if ($msg_type == 'core') { - $text = t('There was a problem determining the status of available updates for your version of Drupal.', array(), $langcode); + $text = t('There was a problem determining the status of available updates for your version of Drupal.', array(), array('langcode' => $langcode)); } else { - $text = t('There was a problem determining the status of available updates for one or more of your modules or themes.', array(), $langcode); + $text = t('There was a problem determining the status of available updates for one or more of your modules or themes.', array(), array('langcode' => $langcode)); } break; } if ($report_link) { - $text .= ' ' . t('See the available updates page for more information.', array('@available_updates' => url('admin/reports/updates', array('language' => $language))), $langcode); + $text .= ' ' . t('See the available updates page for more information.', array('@available_updates' => url('admin/reports/updates', array('language' => $language))), array('langcode' => $langcode)); } return $text; Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.996 diff -u -p -r1.996 user.module --- modules/user/user.module 30 May 2009 12:14:27 -0000 1.996 +++ modules/user/user.module 31 May 2009 07:12:27 -0000 @@ -2080,35 +2080,35 @@ function _user_mail_text($key, $language // No override, return default string. switch ($key) { case 'register_no_approval_required_subject': - return t('Account details for !username at !site', $variables, $langcode); + return t('Account details for !username at !site', $variables, array('langcode' => $langcode)); case 'register_no_approval_required_body': - return t("!username,\n\nThank you for registering at !site. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n-- !site team", $variables, $langcode); + return t("!username,\n\nThank you for registering at !site. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n-- !site team", $variables, array('langcode' => $langcode)); case 'register_admin_created_subject': - return t('An administrator created an account for you at !site', $variables, $langcode); + return t('An administrator created an account for you at !site', $variables, array('langcode' => $langcode)); case 'register_admin_created_body': - return t("!username,\n\nA site administrator at !site has created an account for you. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n-- !site team", $variables, $langcode); + return t("!username,\n\nA site administrator at !site has created an account for you. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n-- !site team", $variables, array('langcode' => $langcode)); case 'register_pending_approval_subject': case 'register_pending_approval_admin_subject': - return t('Account details for !username at !site (pending admin approval)', $variables, $langcode); + return t('Account details for !username at !site (pending admin approval)', $variables, array('langcode' => $langcode)); case 'register_pending_approval_body': - return t("!username,\n\nThank you for registering at !site. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details.\n\n\n-- !site team", $variables, $langcode); + return t("!username,\n\nThank you for registering at !site. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details.\n\n\n-- !site team", $variables, array('langcode' => $langcode)); case 'register_pending_approval_admin_body': - return t("!username has applied for an account.\n\n!edit_uri", $variables, $langcode); + return t("!username has applied for an account.\n\n!edit_uri", $variables, array('langcode' => $langcode)); case 'password_reset_subject': - return t('Replacement login information for !username at !site', $variables, $langcode); + return t('Replacement login information for !username at !site', $variables, array('langcode' => $langcode)); case 'password_reset_body': - return t("!username,\n\nA request to reset the password for your account has been made at !site.\n\nYou may now log in to !uri_brief by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once. It expires after one day and nothing will happen if it's not used.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.", $variables, $langcode); + return t("!username,\n\nA request to reset the password for your account has been made at !site.\n\nYou may now log in to !uri_brief by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once. It expires after one day and nothing will happen if it's not used.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.", $variables, array('langcode' => $langcode)); case 'status_activated_subject': - return t('Account details for !username at !site (approved)', $variables, $langcode); + return t('Account details for !username at !site (approved)', $variables, array('langcode' => $langcode)); case 'status_activated_body': - return t("!username,\n\nYour account at !site has been activated.\n\nYou may now log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\nOnce you have set your own password, you will be able to log in to !login_uri in the future using:\n\nusername: !username\n", $variables, $langcode); + return t("!username,\n\nYour account at !site has been activated.\n\nYou may now log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\nOnce you have set your own password, you will be able to log in to !login_uri in the future using:\n\nusername: !username\n", $variables, array('langcode' => $langcode)); case 'status_blocked_subject': - return t('Account details for !username at !site (blocked)', $variables, $langcode); + return t('Account details for !username at !site (blocked)', $variables, array('langcode' => $langcode)); case 'status_blocked_body': - return t("!username,\n\nYour account on !site has been blocked.", $variables, $langcode); + return t("!username,\n\nYour account on !site has been blocked.", $variables, array('langcode' => $langcode)); case 'cancel_confirm_subject': - return t('Account cancellation request for !username at !site', $variables, $langcode); + return t('Account cancellation request for !username at !site', $variables, array('langcode' => $langcode)); case 'cancel_confirm_body': return t("!username, @@ -2120,14 +2120,14 @@ You may now cancel your account on !uri_ NOTE: The cancellation of your account is not reversible. -This link expires in one day and nothing will happen if it is not used.", $variables, $langcode); +This link expires in one day and nothing will happen if it is not used.", $variables, array('langcode' => $langcode)); case 'status_canceled_subject': - return t('Account details for !username at !site (canceled)', $variables, $langcode); + return t('Account details for !username at !site (canceled)', $variables, array('langcode' => $langcode)); case 'status_canceled_body': return t("!username, -Your account on !site has been canceled.", $variables, $langcode); +Your account on !site has been canceled.", $variables, array('langcode' => $langcode)); } } }