Index: modules/contact/contact.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/contact/contact.module,v
retrieving revision 1.86
diff -u -r1.86 contact.module
--- modules/contact/contact.module	31 May 2007 12:03:18 -0000	1.86
+++ modules/contact/contact.module	2 Jun 2007 16:44:06 -0000
@@ -363,41 +363,40 @@
   global $user;
 
   $account = user_load(array('uid' => arg(1), 'status' => 1));
-  // Compose the body:
-  $message[] = "$account->name,";
-  $message[] = 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)), '!form-url' => url($_GET['q'], array('absolute' => TRUE)), '!site' => variable_get('site_name', 'Drupal')));
-  $message[] = 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))));
-  $message[] = t('Message:');
-  $message[] = $form_values['message'];
+  
+  // Format the subject
+  $message['subject'] = text_t('[!site_name] !subject');
+  // Compose the body
+  $message['body'][] = text("!name-to,");
+  $message['body'][] = text_t("!name-from (!name-from-url) has sent you a message via your contact form (!form-url) at !site_name.");
+  $message['body'][] = text_t("If you don't want to receive such e-mails, you can change your settings at !url.");
+  $message['body'][] = text_t('Message:');
+  $message['body'][] = $form_values['message'];
+
+  // Prepare variables for replacement. User mail tokens will be
+  // added automatically in user_mail, so they're not needed here.
+  $message['variables'] = array(
+    '!name-from'     => $user->name,
+    '!name-to'       => $account->name,
+    '!name-from-url' => array('#type' => 'url', 'url' => "user/$user->uid", 'options' => array('absolute' => TRUE)),
+    '!form-url'      => array('#type' => 'url', 'url' => $_GET['q'], 'options' => array('absolute' => TRUE)),
+    '!url'           => array('#type' => 'url', 'url' => "user/$account->uid", 'options' => array('absolute' => TRUE)),
+    '!subject'       => $form_values['subject'],
+  );
+  
+  // Send the e-mail
+  $message['#mail_id'] = 'user-contact-mail';
+  $message['#from'] = $user->mail;
+  user_mail($account, $message);
 
-  // Tidy up the body:
-  foreach ($message as $key => $value) {
-    $message[$key] = wordwrap($value);
-  }
-
-  // Prepare all fields:
-  $to = $account->mail;
-  $from = $user->mail;
-
-  // Format the subject:
-  $subject = '['. variable_get('site_name', 'Drupal') .'] '. $form_values['subject'];
-
-  // Prepare the body:
-  $body = implode("\n\n", $message);
-
-  // Send the e-mail:
-  drupal_mail('contact-user-mail', $to, $subject, $body, $from);
-
-  // Send a copy if requested:
+  // Send a copy to the user, if requested
   if ($form_values['copy']) {
-    drupal_mail('contact-user-copy', $from, $subject, $body, $from);
+    $message['#mail_id'] = 'user-contact-copy';
+    user_mail($user, $message);
   }
 
-  // Log the operation:
   flood_register_event('contact');
   watchdog('mail', '%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name));
-
-  // Set a status message:
   drupal_set_message(t('The message has been sent.'));
 
   // Jump to the user's profile page:
@@ -509,40 +508,54 @@
  * Process the site-wide contact page form submission.
  */
 function contact_mail_page_submit($form, &$form_state, $form_values) {
-
+  global $language;
+  
   // E-mail address of the sender: as the form field is a text field,
   // all instances of \r and \n have been automatically stripped from it.
   $from = $form_values['mail'];
 
-  // Compose the body:
-  $message[] = t("!name sent a message using the contact form at !form.", array('!name' => $form_values['name'], '!form' => url($_GET['q'], array('absolute' => TRUE))));
-  $message[] = $form_values['message'];
-
-  // Tidy up the body:
-  foreach ($message as $key => $value) {
-    $message[$key] = wordwrap($value);
-  }
-
   // Load the category information:
   $contact = db_fetch_object(db_query("SELECT * FROM {contact} WHERE cid = %d", $form_values['cid']));
 
-  // Format the category:
-  $subject = t('[!category] !subject', array('!category' => $contact->category, '!subject' => $form_values['subject']));
-
-  // Prepare the body:
-  $body = implode("\n\n", $message);
-
-  // Send the e-mail to the recipients:
-  drupal_mail('contact-page-mail', $contact->recipients, $subject, $body, $from);
+  // Compose the mail message:
+  $message['subject'] = text_t('[!category] !subject');
+  $message['body'][] = text_t("!name sent a message using the contact form at !form.");
+  $message['body'][] = $form_values['message'];
+
+  // Collect variables for later replacement
+  $message['variables'] = array(
+    '!category' => $contact->category,
+    '!subject' => $form_values['subject'],
+    '!name' => $form_values['name'],
+    '!form' => array('#type' => 'url', 'url' => $_GET['q'], 'options' => array('absolute' => TRUE)),
+  );
+
+  // Send the e-mail to the recipients using the default site language:
+  $message['#mail_id'] = 'contact-page-mail';
+  $message['#to'] = $contact->recipients;
+  $message['#from'] = $from;
+  $message['#langcode'] = language_default('language');
+  
+  drupal_mail($message);
 
-  // If the user requests it, send a copy.
+  // If the user requests it, send a copy using current language.
   if ($form_values['copy']) {
-    drupal_mail('contact-page-copy', $from, $subject, $body, $from);
+    $message['#mail_id'] = 'contact-page-copy';
+    $message['#to'] = $from;
+    $message['#langcode'] = $language->language;
+     
+    drupal_mail($message);
   }
 
   // Send an auto-reply if necessary:
   if ($contact->reply) {
-    drupal_mail('contact-page-autoreply', $from, $subject, wordwrap($contact->reply), $contact->recipients);
+    $message['#mail_id'] = 'contact-page-autoreply';
+    $message['#to'] = $from;
+    $message['body'][] = $contact->reply;
+    $message['#from'] = $contact->recipients;
+    $message['#langcode'] = $language->language;
+    
+    drupal_mail($message);
   }
 
   // Log the operation:
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.650
diff -u -r1.650 common.inc
--- includes/common.inc	1 Jun 2007 09:49:11 -0000	1.650
+++ includes/common.inc	2 Jun 2007 16:44:06 -0000
@@ -2276,30 +2276,57 @@
  * Send an e-mail message, using Drupal variables and default settings.
  * More information in the <a href="http://php.net/manual/en/function.mail.php">
  * PHP function reference for mail()</a>
- * @param $mailkey
+ * 
+ * This function automatically renders the 'subject' and 'body' elements
+ * if present, using text rendering functions.
+ * 
+ * @see drupal_render_text
+ * 
+ * @see drupal_render_mail
+ * 
+ * The mail data is an array that should have the following elements:
+ * 
+ * #mail_id
  *   A key to identify the mail sent, for altering.
- * @param $to
+ * #to
  *   The mail address or addresses where the message will be send to. The
  *   formatting of this string must comply with RFC 2822. Some examples are:
  *    user@example.com
  *    user@example.com, anotheruser@example.com
  *    User <user@example.com>
  *    User <user@example.com>, Another User <anotheruser@example.com>
- * @param $subject
+ * #subject or 'subject'
  *   Subject of the e-mail to be sent. This must not contain any newline
  *   characters, or the mail may not be sent properly.
- * @param $body
+ *   If the element 'subject' exists it will be rendered before sending 
+ *   the email and '#subject' will be populated from that
+ * #body or 'body'
  *   Message to be sent. Drupal will format the correct line endings for you.
- * @param $from
+ *   If the element 'body' exists it will be rendered before sending the email
+ *   and '#body' will be populated from that
+ * 
+ * Optional elements
+ * 
+ * #from
  *   Sets From, Reply-To, Return-Path and Error-To to this value, if given.
- * @param $headers
+ * #headers
  *   Associative array containing the headers to add. This is typically
  *   used to add extra headers (From, Cc, and Bcc).
- *   <em>When sending mail, the mail must contain a From header.</em>
+ *   <em>When sending mail, the mail must contain a From header.</em> 
+ * #langcode
+ *   Language code to localize some parts of the e-mail
+ * 'variables'
+ *   Tokens for replacement when rendering the body or the header
+ * 
+ * @param $message
+ *   An array containing all the mail data. It supports text rendering for parts
+ *   of the e-mail.
+ *
  * @return Returns TRUE if the mail was successfully accepted for delivery,
  *   FALSE otherwise.
  */
-function drupal_mail($mailkey, $to, $subject, $body, $from = NULL, $headers = array()) {
+function drupal_mail(&$mail) {
+  // Add default headers
   $defaults = array(
     'MIME-Version' => '1.0',
     'Content-Type' => 'text/plain; charset=UTF-8; format=flowed',
@@ -2310,17 +2337,30 @@
   // Return-Path headers should have a domain authorized to use the originating
   // SMTP server.  Errors-To is redundant, but shouldn't hurt.
   $default_from = variable_get('site_mail', ini_get('sendmail_from'));
+  // Add default properties
+  $mail += array(
+    '#from' => $default_from,
+    '#headers' => array(),
+  );
+  
   if ($default_from) {
-    $defaults['From'] = $defaults['Reply-To'] = $defaults['Sender'] = $defaults['Return-Path'] = $defaults['Errors-To'] = $default_from;
-  }
-  if ($from) {
-    $defaults['From'] = $defaults['Reply-To'] = $from;
+    $defaults['Sender'] = $defaults['Return-Path'] = $defaults['Errors-To'] = $default_from;
   }
-  $headers = array_merge($defaults, $headers);
+  $defaults['From'] = $defaults['Reply-To'] = $mail['#from'];
 
-  // Bundle up the variables into a structured array for altering.
-  $message = array('#mail_id' => $mailkey, '#to' => $to, '#subject' => $subject, '#body' => $body, '#from' => $from, '#headers' => $headers);
+  $mail['#headers'] += $defaults;
+
+  // Alter before text rendering
+  drupal_alter('mail_text', $mail);
+  
+  // Render different parts of the mail. From here on, whe don't use the by-reference $mail 
+  // variable anymore but the resulting array with just known mail properties.
+  // However, the last header and body rendered will be in $mail['#header'] and $mail['#body'] 
+  $message = drupal_render_mail($mail);
+  
+  // Alter after text rendering
   drupal_alter('mail', $message);
+  
   $mailkey = $message['#mail_id'];
   $to = $message['#to'];
   $subject = $message['#subject'];
@@ -2599,6 +2639,300 @@
 }
 
 /**
+ * Returns text lines ready for later rendering.
+ * 
+ * This function outputs arrays containing text information
+ * ready for rendering with drupal_text_render()
+ * 
+ * @see drupal_text_render()
+ * 
+ * @param $value
+ *   String containing the text to be localized or replaced and rendered
+ * @param $replace
+ *   Whether to do text replacement with provided tokens
+ * @param $localize
+ *   Whether to run the string through localization system
+ * @param $type
+ *   Optional type of element, will default to 'string'
+ * @prefix
+ *   Prefix for the string after rendering. Useful to compose html text.
+ * @sufix
+ *   Sufix for the string after rendering. Useful to compose html text.
+ */
+function text($value, $replace = TRUE, $localize = FALSE, $type = 'string', $prefix = '', $sufix = '') {
+  return array(
+    '#type' => $type,
+    '#value' => $value,
+    '#replace' => $replace,
+    '#localize' => $localize,
+    '#prefix' => $prefix,
+    '#sufix' => $sufix
+  );
+}
+
+/**
+ * Shorthand function for translatable text lines
+ * 
+ * This is only a wrapper for text() function for quick writing
+ * and for marking the strings to be localized for the locale extractor.
+ * 
+ * @see text()
+ * 
+ * @param $value
+ *   String to be localized and then replaced with tokens
+ */
+function text_t($value) {
+  return text($value, TRUE, TRUE);
+}
+
+/**
+ * Render different types of text arrays into strings.
+ * 
+ * This function should be used for composing complex texts or email texts. Texts are
+ * composed using arrays, similar to forms, with some parameters, and then rendered using
+ * this function. It supports localization of single text lines and also
+ * has some internal caching allowing quick localization of a single text to multiple
+ * languages.
+ *
+ * Example to print the same message in all available languages:
+ * 
+ * @code
+ *   // Delayed localization with parameter substitution
+ *   
+ *   // There are other types of arrays that may be rendered so we have to state the type.
+ *   // Type 'text' arrays will be rendered as an array of strings glued together at the end.
+ *   $text['#type'] = 'text';
+ * 
+ *   $text[] = text_t('Hello !username,');
+ *  
+ *   // This text will be localized and replaced with variables
+ *   $text[] = text_t('A new !content_type has been posted to !site_name:');
+ *   // Simple string, won't be localized nor replaced with variables
+ *   $text[] = $node->title.'.';
+ *   // This text will be replaced but not localized
+ *   $text[] = text('!site_slogan'); 
+ *   
+ *   // Special formats for the text
+ *   $text['#glue'] = '<br />'; // Will be used for gluing the lines together
+ *   
+ *   // Variables for replacement
+ *   $args['!site_name'] => variable_get('site_name', 'Drupal'); // Simple text, won't be localized
+ *   $args['!content_type'] => text_t($node->type); // The variable will be localized 
+ *   $args['!username'] = $user->name;
+ * 
+ *   // This one is more tricky. A variable whose default will be localized when rendering
+ *   $args['!site_slogan'] => array(
+ *     '#type' => 'variable', 
+ *     'name' => 'site_slogan',
+ *     'default'=> text_t('Drupal rocks!'), // We can nest arguments and text arrays !!
+ *   );
+ * 
+ *   // Localize for all languages
+ *   $output = '';
+ *   foreach (language_list() as $language) {
+ *     $output .= $language->name .':'. drupal_render_text($text, $args, $language->language);
+ *   }
+ * 
+ * @endcode
+ * 
+ * @see text()
+ * 
+ * @see text_t()
+ * 
+ * @param $text
+ *   Text to be rendered in array form
+ * @param $langcode
+ *   Optional language code, for language different to current one
+ * @param $variables
+ *   Arguments ready for replacement
+ * @param $defaults
+ *   Internal use only
+ * @param $debug
+ *   Developer facility, for debugging
+ * @return
+ *   Plain text or array of plain texts, depending on text type.
+ */
+function drupal_render_text(&$text, $langcode = NULL, $variables = array(), $defaults = array(), $debug = FALSE) {
+  global $language;  
+  static $text_defaults = array( // Defaults for each type of content, 'all' for all types
+    'all' => array('#value' => '', '#replace' => FALSE, '#localize' => FALSE, '#wordwrap' => 0, '#glue' => '',
+      '#param' => array(), '#rendered' => array(), '#prefix' => '', '#sufix' => '', '#cache' => TRUE
+    ),
+    'variable' => array('#callback' => 'variable_get'),
+    'date' => array('#callback' => 'format_date'),
+    'interval' => array('#callback' => 'format_interval'),
+    'plural' => array('#callback' => 'format_plural'),
+    'url' => array('#callback' => 'url', '#language' => 'options'), // Language in array 'options'
+    'link' => array('#callback' => 'l', '#language' => 'options'),  // Language in array 'options'
+  );
+  static $callback_parameters = array( // Parameters for known callbacks
+    'url' => array('url' => NULL, 'options' => array()),
+    'l' => array('text' => NULL, 'path' => NULL, 'options' => array()),
+    'variable_get' => array('name' => NULL, 'default' => NULL),
+    'format_date' => array('timestamp' => NULL, 'type' => 'medium', 'format' => '', 'timezone' => NULL, 'langcode' => array('#type' => 'langcode')),
+    'format_interval' => array('timestamp' => NULL, 'granularity' => 2, 'langcode' => array('#type' => 'langcode')),
+    'format_plural' => array('count' => NULL, 'singular' => NULL, 'plural' => NULL, 'args' => array(), 'langcode' => array('#type' => 'langcode')),  
+  );
+  static $language_list; // We cache the language list too
+  if (empty($language_list)) {
+    $language_list = language_list();
+  }
+  $langcode = $langcode ? $langcode : $language->language;
+  
+  // Add defaults. If plain text, convert to string array
+  if (!is_array($text)) {
+    $text = array('#value' => $text, '#type' => 'string');
+  }
+  // If no type, it is a value. May be a parameter for callbacks
+  if (empty($text['#type'])) {
+    $text = array('#type' => 'value', '#value' => $text);
+  }
+
+  // If already rendered for this language, just return value
+  if (isset($text['#rendered'][$langcode])) {
+    return $text['#rendered'][$langcode];
+  }
+    
+  // Add defaults before processing, in the right order for overriding
+  $text += $defaults;
+  $text += $text_defaults['all'];
+  // We should already have a type property. Set defaults for this type
+  $type = $text['#type'];
+  if (isset($text_defaults[$type])) {
+    $text += $text_defaults[$type];
+  }
+
+  if($debug) drupal_set_message(print_r($text, TRUE));
+  // So we need some rendering, first we process variables for replacement
+  
+  // Get value depending on type
+  switch ($text['#type']) {
+    case 'langcode':
+      // This is a langcode parameter of a callback
+      $value = $langcode;
+      break;
+    case 'text':
+      // Array of text lines, render each line and merge
+      foreach (element_children($text) as $index) {
+        $value[] = drupal_render_text($text[$index], $langcode, $variables, $defaults, $debug);
+      }
+      // Now we glue the text together
+      $value = implode($text['#glue'], $value);
+      break;
+    case 'arguments':
+      // Render variables for replacement
+      // We pass the full variables array, to the next
+      foreach (element_children($text) as $name) {
+        $value[$name] = drupal_render_text($text[$name], $langcode, $variables);
+        switch ($name[0]) {
+          case '@':
+            $value[$name] = check_plain($value[$name]); 
+            break;
+          case '%':
+            $value[$name] = theme('placeholder', $value[$name]);
+            break;
+          case '!':
+            // Do nothing     
+        }
+        // Add to variables to pass over to the next
+        $variables[$name] = $value[$name];
+      }
+      break;
+    case 'value':
+    case 'string':
+    default:
+      // Prepare callbacks and parameters
+      if (isset($text['#callback']) && ($function = $text['#callback']) && function_exists($function)) {
+        // Prepare parameters
+        if(isset($text['#parameters'])) {
+          // It's the first one of this type, should bring information about parameters
+          // We get it and store it in the static variable
+          $parameters = $callback_parameters[$function] = $text['#parameters'];
+        } elseif (isset($callback_parameters[$function])) {
+          $parameters = $callback_parameters[$function];
+        }
+        // Collect parameters. We render them with drupal_render_text to support complex parameters
+        foreach ($parameters as $name => $value) {
+          $params[$name] = isset($text[$name]) ? drupal_render_text($text[$name], $langcode, $variables, $defaults) : drupal_render_text($parameters[$name]); 
+        }
+        // Replace language for functions needing this parameter
+        if (isset($text['#language'])) {
+          $params[$text['#language']]['language'] = $language_list[$langcode];
+        }
+        $value = call_user_func_array($function, $params);
+      
+      } else {   
+        $value = $text['#value'];
+      }
+      break;
+  }
+  
+  // Process optional parameters. Localize, replace, wordwrap....
+  // Make sure we are not screwing with arrays, just text
+  if (!is_array($value)) {
+    if ($text['#localize']) {
+      $value = t($value, array(), $langcode);
+    }
+    if ($text['#replace'] && $variables) {
+      $value = strtr($value, $variables);
+    }
+    if ($text['#wordwrap']) {
+      $value = wordwrap($value, $text['#wordwrap']);
+    }
+    $value = $text['#prefix'] . $value . $text['#sufix'];
+    $output = $value;
+  } else {
+    $output = print_r($value, TRUE);
+  }
+  // For now, this function supports debugging
+  
+  if ($debug) drupal_set_message("Text rendering: type=".$text['#type']."value=".$text['#value']." return= $output");
+  
+  // We cache this text for this language unless specified otherwise
+  if ($text['#cache']) {
+    $text['#rendered'][$langcode] = $value;
+  }
+  return $value;
+}
+
+/**
+ * Render a mail array doing text rendering for known parts of the mail
+ * 
+ * @see drupal_mail()
+ */
+function drupal_render_mail(&$mail) {
+  global $language;
+  
+  $langcode = isset($mail['#langcode']) ? $mail['#langcode'] : $language->language;
+
+  // This is an e-mail. Has some known parts like
+  // $mail['body'] and $mail['subject'] which are the texts before rendering
+
+  // First, compute variables for substitution
+  $variables = isset($mail['variables']) ? drupal_render_text($mail['variables'] += array('#type' => 'arguments'), $langcode, array(), array('#type' => 'value')) : array();
+
+  // Subject and body mail be properties for rendering or they may be fixed values
+  // passed in '#subject' and '#body' elements  
+  if (isset($mail['subject'])) {
+    $mail['subject'] = is_array($mail['subject']) ? $mail['subject'] + array('#type' => 'text') : array('#type' => 'string', '#value' => $mail['subject']);
+    $mail['#subject'] = drupal_render_text($mail['subject'], $langcode, $variables);
+  }
+  
+  if (isset($mail['body'])) {
+    $mail['body'] = is_array($mail['body']) ? $mail['body'] + array('#type' => 'text', '#glue' => "\n\n") : array('#type' => 'string', '#value' => $mail['body']);
+    $mail['#body'] = drupal_render_text($mail['body'], $langcode, $variables, array('#wordwrap' => 75));
+  }
+  
+  // Bundle up the variables into a structured array for altering removing non-mail ones.
+  // This will produce at least some notice if any property is missing.
+  foreach (array('#mail_id', '#to', '#subject', '#body', '#from', '#headers') as $key) {
+    $message[$key] = $mail[$key];
+  }
+  // The return value is the whole array
+  return $message;  
+}
+
+/**
  * Function used by uasort in drupal_render() to sort structured arrays
  * by weight.
  */
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.177
diff -u -r1.177 locale.module
--- modules/locale/locale.module	30 May 2007 08:08:58 -0000	1.177
+++ modules/locale/locale.module	2 Jun 2007 16:44:07 -0000
@@ -186,10 +186,9 @@
  * Implementation of hook_user().
  */
 function locale_user($type, $edit, &$user, $category = NULL) {
-  if ($type == 'form' && $category == 'account' && variable_get('language_count', 1) > 1 && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) == LANGUAGE_NEGOTIATION_PATH) {
-    $languages = language_list('enabled');
+  if (variable_get('language_count', 1) > 1 && ($type == 'register' && user_access('administer users') || $type == 'form' && $category == 'account' )) {    $languages = language_list('enabled');
     $languages = $languages['1'];
-    if ($user->language == '') {
+    if ($user && empty($user->language)) {
       $user->language = language_default('language');
     }
     $names = array();
@@ -202,9 +201,9 @@
     );
     $form['locale']['language'] = array('#type' => 'radios',
       '#title' => t('Language'),
-      '#default_value' => $user->language,
+      '#default_value' => user_language($user),
       '#options' => $names,
-      '#description' => t('Selecting a different locale will change the interface language of the site.'),
+      '#description' => t('Selecting a locale will set the default site interface and mail language for this account.'),
     );
     return $form;
   }
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.788
diff -u -r1.788 user.module
--- modules/user/user.module	30 May 2007 08:08:59 -0000	1.788
+++ modules/user/user.module	2 Jun 2007 16:44:11 -0000
@@ -1205,6 +1205,67 @@
 }
 
 /**
+ * Send an e-mail message to a user account or a destination mail address.
+ * 
+ * This is basically a wrapper for drupal_mail(), but some predefined variables
+ * are added and when the destination mail address belongs to a user, some properties
+ * are pulled from the user account.
+ *
+ * @see drupal_mail()
+ * 
+ * @param $destination
+ *   User account or e-mail address to which the e-mail will be sent.
+ * @param $message
+ *   Message array.
+ * @return
+ *   Returns TRUE if the mail was successfully accepted for
+ *   delivery, FALSE otherwise.
+ */
+function user_mail($destination, &$message) {
+
+  // Now we get the destination mail or other properties
+  if ($account = is_object($destination) ? $destination : user_load(array('mail' => $destination))) {
+    // Destination was a user account or is resolved to a user account.
+    $mail = $account->mail;
+    $langcode = user_language($account);
+    $variables = user_mail_tokens($account);
+  }
+  else {
+    // Destination is an e-mail address and it doesn't belong to a known user.
+    $mail = $destination;
+    $variables = array('!mailto' => $mail) + user_mail_tokens();  
+  }
+  
+  // Add parameters to $message array. These may need to be overridden from one email
+  // to the next when sending an e-mail to different users.
+  // We add some extra properties that may be useful for mail_text_alter()
+  $message = array_merge($message, array(
+    '#to' => $mail,
+    '#langcode' => $account ? user_language($account) : language_default('language'),
+    '#account' => $account,
+    'variables' => isset($message['variables']) ? $variables + $message['variables'] : $variables,
+  ));
+  
+  return drupal_mail($message);
+}
+
+/**
+ * Returns the code of the language preferred by the
+ * user if set or the default language code.
+ * 
+ * @param $account
+ *   Optional user account. Falls back to the current $user.
+ */
+function user_language($account = NULL) {
+  global $user;
+
+  $account = isset($account) ? $account : $user;
+  
+  // The user language will be the site default if no language is set for the account.
+  return (isset($account->language) && $account->language) ? $account->language : language_default('language');  
+}
+
+/**
  * Menu callback; process one time login link and redirects to the user page on success.
  */
 function user_pass_reset($uid, $timestamp, $hashed_pass, $action = NULL) {
@@ -1386,7 +1447,7 @@
     }
     else if (!variable_get('user_email_verification', TRUE) && $account->status && !$admin) {
       // No e-mail verification is required, create new user account, and login user immediately.
-      _user_mail_notify('register_no_approval_required', $account, $pass);
+      _user_mail_notify('register_no_approval_required', $account, array('!password' => $pass));
       user_authenticate($account->name, trim($pass));
       $form_state['redirect'] = '';
       return;
@@ -1394,7 +1455,7 @@
     else if ($account->status || $notify) {
       // Create new user account, no administrator approval required.
       $op = $notify ? 'register_admin_created' : 'register_no_approval_required';
-      _user_mail_notify($op, $account, $pass);
+      _user_mail_notify($op, $account, array('!password' => $pass));
       if ($notify) {
         drupal_set_message(t('Password and further instructions have been e-mailed to the new user %user.', array('%user' => $name)));
       }
@@ -1406,9 +1467,8 @@
     }
     else {
       // Create new user account, administrator approval required.
-      _user_mail_notify('register_pending_approval', $account, $pass);
+      _user_mail_notify('register_pending_approval', $account, array('!password' => $pass));
       drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.<br />In the meantime, your password and further instructions have been sent to your e-mail address.'));
-
     }
   }
 }
@@ -1658,45 +1718,63 @@
 
 /*** Administrative features ***********************************************/
 
-function _user_mail_text($messageid, $variables = array()) {
-
-  // Check if an admin setting overrides the default string.
-  if ($admin_setting = variable_get('user_mail_'. $messageid, FALSE)) {
-    return strtr($admin_setting, $variables);
-  }
-  // No override, return with default strings.
-  else {
-    switch ($messageid) {
-      case 'register_no_approval_required_subject':
-        return t('Account details for !username at !site', $variables);
-      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);
-      case 'register_admin_created_subject':
-        return t('An administrator created an account for you at !site', $variables);
-      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);
-      case 'register_pending_approval_subject':
-        return t('Account details for !username at !site (pending admin approval)', $variables);
-      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);
-      case 'password_reset_subject':
-        return t('Replacement login information for !username at !site', $variables);
-      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 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);
-      case 'status_activated_subject':
-        return t('Account details for !username at !site (approved)', $variables);
-      case 'status_activated_body':
-        return "!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 the following username:\n\nusername: !username\n";
-      case 'status_blocked_subject':
-        return t('Account details for !username at !site (blocked)', $variables);
-      case 'status_blocked_body':
-        return "!username,\n\nYour account on !site has been blocked.";
-      case 'status_deleted_subject':
-        return t('Account details for !username at !site (deleted)', $variables);
-      case 'status_deleted_body':
-        return "!username,\n\nYour account on !site has been deleted.";
-    }
+/**
+ * Generate text for the built in emails sent in user.module.
+ *
+ * @param $messageid
+ *   Message identifier as used in user_mail().
+ * @param $variables
+ *   Values to replace placeholders in the text with.
+ */
+function _user_mail_text($messageid) {
+  // Get default text. We use text_t() to mark text for the locale extractor and because
+  // the text will be localized later using drupal_render_text().
+
+  switch ($messageid) {
+    case 'register_no_approval_required_subject':
+      $default = text_t('Account details for !username at !site_name');
+      break;
+    case 'register_no_approval_required_body':
+      $default = text_t("!username,\n\nThank you for registering at !site_name. 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_name team");
+      break;
+    case 'register_admin_created_subject':
+      $default = text_t('An administrator created an account for you at !site_name');
+      break;
+    case 'register_admin_created_body':
+      $default = text_t("!username,\n\nA site administrator at !site_name 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_name team");
+      break;
+    case 'register_pending_approval_subject':
+      $default = text_t('Account details for !username at !site_name (pending admin approval)');
+      break;
+    case 'register_pending_approval_body':
+      $default = text_t("!username,\n\nThank you for registering at !site_name. 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_name team");
+      break;
+    case 'password_reset_subject':
+      $default = text_t('Replacement login information for !username at !site_name');
+      break;
+    case 'password_reset_body':
+      $default = text_t("!username,\n\nA request to reset the password for your account has been made at !site_name.\n\nYou may now log in to !uri_brief 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.");
+      break;
+    case 'status_activated_subject':
+      $default = text_t('Account details for !username at !site_name (approved)');
+      break;
+    case 'status_activated_body':
+      $default = text_t("!username,\n\nYour account at !site_name 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 the following username:\n\nusername: !username\n");
+      break;
+    case 'status_blocked_subject':
+      $default = text_t('Account details for !username at !site_name (blocked)');
+      break;
+    case 'status_blocked_body':
+      $default = text_t("!username,\n\nYour account on !site_name has been blocked.");
+      break;
+    case 'status_deleted_subject':
+      $default = text_t('Account details for !username at !site_name (deleted)');
+      break;
+    case 'status_deleted_body':
+      $default = text_t("!username,\n\nYour account on !site_name has been deleted.");
+      break;
   }
+  return array('#type' => 'variable', 'name' => 'user_mail_'. $messageid, 'default' => $default);
 }
 
 function user_admin_check_user() {
@@ -2450,7 +2528,7 @@
   );
   // These email tokens are shared for all settings, so just define
   // the list once to help ensure they stay in sync.
-  $email_token_help = t('Available variables are:') .' !username, !site, !password, !uri, !uri_brief, !mailto, !date, !login_uri, !edit_uri, !login_url.';
+  $email_token_help = t('Available variables are:') .' !username, !site_name, !password, !uri_full, !uri_brief, !mailto, !date, !login_uri, !edit_uri, !login_url.';
 
   $form['email']['admin_created'] = array(
     '#type' => 'fieldset',
@@ -3074,28 +3152,31 @@
  * Return an array of token to value mappings for user e-mail messages.
  *
  * @param $account
- *  The user object of the account being notified.  Must contain at
- *  least the fields 'uid', 'name', and 'mail'.
- * @param $password
- *  Optional string containing the user's current password (if known).
+ *   Optional user object of the account being notified. Must contain
+ *   at least the 'uid', 'name', and 'mail' fields. If not provided,
+ *   the function will only return site-wide mappings.
  * @return
- *  Array of mappings from token names to values (for use with strtr()).
+ *   Array of mappings from token names to values.
  */
-function user_mail_tokens($account, $password = NULL) {
+function user_mail_tokens($account = NULL) {
   global $base_url;
+
+  // Site-wide tokens
   $tokens = array(
-    '!username' => $account->name,
-    '!site' => variable_get('site_name', 'Drupal'),
-    '!login_url' => user_pass_reset_url($account),
-    '!uri' => $base_url,
+    '!site_name' => variable_get('site_name', 'Drupal'),
+    '!uri_full'  => $base_url,
     '!uri_brief' => substr($base_url, strlen('http://')),
-    '!mailto' => $account->mail,
-    '!date' => format_date(time()),
-    '!login_uri' => url('user', array('absolute' => TRUE)),
-    '!edit_uri' => url('user/'. $account->uid .'/edit', array('absolute' => TRUE)),
+    '!date'      => array('#type' => 'date', 'timestamp' =>time()),
   );
-  if (!empty($password)) {
-    $tokens['!password'] = $password;
+
+  // User dependent values
+  if ($account) {
+    $tokens += array(
+      '!username'  => $account->name,
+      '!login_url' => user_pass_reset_url($account),
+      '!mailto'    => $account->mail,
+      '!edit_uri'  => array('#type' => 'url', 'url' => 'user/'. $account->uid .'/edit', 'options' => array('absolute' => TRUE)),
+    );
   }
   return $tokens;
 }
@@ -3104,50 +3185,57 @@
  * Conditionally create and send a notification email when a certain
  * operation happens on the given user account.
  *
+ * @see user_mail()
  * @see user_mail_tokens()
  * @see drupal_mail()
  *
  * @param $op
- *  The operation being performed on the account.  Possible values:
- *  'register_admin_created': Welcome message for user created by the admin
- *  'register_no_approval_required': Welcome message when user self-registers
- *  'register_pending_approval': Welcome message, user pending admin approval
- *  'password_reset': Password recovery request
- *  'status_activated': Account activated
- *  'status_blocked': Account blocked
- *  'status_deleted': Account deleted
+ *   The operation being performed on the account.  Possible values:
+ *   'register_admin_created': Welcome message for user created by the admin
+ *   'register_no_approval_required': Welcome message when user self-registers
+ *   'register_pending_approval': Welcome message, user pending admin approval
+ *   'password_reset': Password recovery request
+ *   'status_activated': Account activated
+ *   'status_blocked': Account blocked
+ *   'status_deleted': Account deleted
  *
  * @param $account
- *  The user object of the account being notified.  Must contain at
- *  least the fields 'uid', 'name', and 'mail'.
+ *   The user object of the account being notified.  Must contain at
+ *   least the fields 'uid', 'name', and 'mail'.
  *
- * @param $password
- *  Optional string containing the user's current password (if known).
+ * @param $variables
+ *   Optional variables for text replacement
  *
  * @return
- *  The return value from drupal_mail(), if ends up being called.
+ *   The return value from drupal_mail(), if ends up being called.
  */
-function _user_mail_notify($op, $account, $password = NULL) {
+function _user_mail_notify($op, $account, $variables) {
   $mail_id = 'user-'. strtr($op, '_', '-');
+ 
   if ($op == 'register_pending_approval') {
     // Special case, since we need to distinguish what we send to the
     // user and what we send to the administrator, handled below.
     $mail_id .= '-user';
   }
+  
   // By default, we always notify except for deleted and blocked.
   $default_notify = ($op != 'status_deleted' && $op != 'status_blocked');
   $notify = variable_get('user_mail_'. $op .'_notify', $default_notify);
   $result = NULL;
+  
   if ($notify) {
-    $from = variable_get('site_mail', ini_get('sendmail_from'));
-    $variables = user_mail_tokens($account, $password);
-    $subject = _user_mail_text($op .'_subject', $variables);
-    $body = _user_mail_text($op .'_body', $variables);
-    $result = drupal_mail($mail_id, $account->mail, $subject, $body, $from);
+    $message['#mail_id'] = $mail_id;
+    $message['subject'] = _user_mail_text($op .'_subject');
+    $message['body'][] = _user_mail_text($op .'_body');
+    $message['variables'] = $variables;
+    $result = user_mail($account, $message);
     if ($op == 'register_pending_approval') {
       // If a user registered requiring admin approval, notify the admin, too.
-      drupal_mail('user-register-approval-admin', $from, $subject, t("!username has applied for an account.\n\n!edit_uri", $variables), $from);
+      $message['#mail_id'] = 'user-register-approval-admin';
+      $message['body'][] = text_t("!username has applied for an account.\n\n!edit_uri");
+      user_mail(variable_get('site_mail', ini_get('sendmail_from')), $message);
     }
   }
+
   return $result;
 }
