Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.172
diff -u -p -r1.172 bootstrap.inc
--- includes/bootstrap.inc	15 Jun 2007 06:45:05 -0000	1.172
+++ includes/bootstrap.inc	29 Jun 2007 13:29:49 -0000
@@ -1030,9 +1030,17 @@ function language_list($field = 'languag
 
   // Init language list
   if (!isset($languages)) {
-    $result = db_query('SELECT * FROM {languages} ORDER BY weight ASC, name ASC');
-    while ($row = db_fetch_object($result)) {
-      $languages['language'][$row->language] = $row;
+    if (variable_get('language_count', 1) > 1) {
+      $result = db_query('SELECT * FROM {languages} ORDER BY weight ASC, name ASC');
+      while ($row = db_fetch_object($result)) {
+        $languages['language'][$row->language] = $row;
+      }
+    }
+    else {
+      // One language only, the locale tables might not even
+      // be in place, so use the default language only.
+      $default = language_default();
+      $languages['language'][$default->language] = $default;
     }
   }
 
Index: includes/mail.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/mail.inc,v
retrieving revision 1.2
diff -u -p -r1.2 mail.inc
--- includes/mail.inc	28 Jun 2007 07:48:40 -0000	1.2
+++ includes/mail.inc	29 Jun 2007 13:29:49 -0000
@@ -2,12 +2,38 @@
 // $Id: mail.inc,v 1.2 2007/06/28 07:48:40 dries Exp $
 
 /**
- * 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
- *   A key to identify the mail sent, for altering.
+ * Compose and optionally send an e-mail message, using Drupal
+ * variables and default settings. See drupal_mail_send() for the
+ * function actually sending the email.
+ *
+ * The e-mail workflow works the following way:
+ *  1. You build the $params, which contain all the variable parts
+ *     to use in the email subject, body or headers. These could be
+ *     from form input, or built into your module.
+ *  2. You decide on what language to use for the e-mail. This should
+ *     be decided on a case-by-case basis. If you are sending an e-mail
+ *     to a user, see user_language(). Otherwise you would probably
+ *     decide between the current page language (the global $language)
+ *     or language_default().
+ *  3. Implement hook_mail() to receive a mail key, $params and
+ *     $language to compose the mail with. If often happens that you
+ *     send mails with the same $params but with different $language to
+ *     different recipients when sending contact mails or when notifying
+ *     multiple users of a certain event on the site. Composing the
+ *     subject, body and optionally e-mail headers based on $params and
+ *     $language is abstracted to hook_mail().
+ *  4. You can either send email directly with drupal_mail() or send the
+ *     same composed email (in the same $language, with the same $params,
+ *     but to different recipients) with calling drupal_mail() with the
+ *     $message array.
+ *
+ * @param $module
+ *   A module name to invoke hook_mail() on. The {$module}_mail() hook will be
+ *   called to complete the $message structure built in the function with the
+ *   mail subject and body.
+ * @param $key
+ *   A key to identify the mail sent. The final e-amil id for altering will
+ *   be {$module}_{$key}.
  * @param $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:
@@ -15,68 +41,120 @@
  *    user@example.com, anotheruser@example.com
  *    User <user@example.com>
  *    User <user@example.com>, Another User <anotheruser@example.com>
- * @param $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
- *   Message to be sent. Accepts both CRLF and LF line-endings.
- *   E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
- *   smart plain text wrapping.
+ * @param $language
+ *   Language to compose the e-mail in.
+ * @param $params
+ *   Optional parameteres to build the e-mail.
  * @param $from
  *   Sets From, Reply-To, Return-Path and Error-To to this value, if given.
- * @param $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>
- * @return Returns TRUE if the mail was successfully accepted for delivery,
- *   FALSE otherwise.
+ * @param $send
+ *   Send the message directly, without calling drupal_mail_send() manually.
+ * @return
+ *   The $message array structure containing all details of the
+ *   message. If already sent ($send = TRUE), then the 'result' element
+ *   will contain the success indicator of the e-mail.
  */
-function drupal_mail($mailkey, $to, $subject, $body, $from = NULL, $headers = array()) {
-  $defaults = array(
-    'MIME-Version' => '1.0',
-    'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
+function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) {
+  $default_from = variable_get('site_mail', ini_get('sendmail_from'));
+  
+  // Bundle up the variables into a structured array for altering.
+  $message = array(
+    'id'       => $module .'_'. $key,
+    'to'       => $to, 
+    'from'     => isset($from) ? $from : $default_from,
+    'language' => $language,
+    'params'   => $params,
+    'subject'  => '',
+    'body'     => array()
+  );
+
+  // Build the default headers
+  $headers = array(
+    'MIME-Version'              => '1.0',
+    'Content-Type'              => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
     'Content-Transfer-Encoding' => '8Bit',
-    'X-Mailer' => 'Drupal'
+    'X-Mailer'                  => 'Drupal'
   );
-  // To prevent e-mail from looking like spam, the addresses in the Sender and
-  // 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'));
   if ($default_from) {
-    $defaults['From'] = $defaults['Reply-To'] = $defaults['Sender'] = $defaults['Return-Path'] = $defaults['Errors-To'] = $default_from;
+    // To prevent e-mail from looking like spam, the addresses in the Sender and
+    // Return-Path headers should have a domain authorized to use the originating
+    // SMTP server. Errors-To is redundant, but shouldn't hurt.
+    $headers['From'] = $headers['Reply-To'] = $headers['Sender'] = $headers['Return-Path'] = $headers['Errors-To'] = $default_from;
   }
   if ($from) {
-    $defaults['From'] = $defaults['Reply-To'] = $from;
+    $headers['From'] = $headers['Reply-To'] = $from;
   }
-  $headers = array_merge($defaults, $headers);
-
-  // 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);
+  $message['headers'] = $headers;
+    
+  // Build the e-mail (get subject and body, allow additional headers) by
+  // invoking hook_mail() on this module. We cannot use module_invoke() as
+  // we need to have $message by reference in hook_mail().
+  if (function_exists($function = $module .'_mail')) {
+    $function($key, $message, $params);
+  }
+  
+  // Invoke hook_mail_alter() to allow all modules to alter the resulting email.
   drupal_alter('mail', $message);
-  $mailkey = $message['#mail_id'];
-  $to = $message['#to'];
-  $subject = $message['#subject'];
-  $body = $message['#body'];
-  $from = $message['#from'];
-  $headers = $message['#headers'];
 
-  // Allow for custom mail backend
+  // Concatenate and wrap the e-mail body.
+  $message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']);
+
+  // Optionally send e-mail.
+  if ($send) {
+    $message['result'] = drupal_mail_send($message);
+  }
+
+  return $message;
+}
+
+/**
+ * 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>. See drupal_mail() for information on
+ * how $message is composed.
+ *
+ * @param $message
+ *  Message array with at least the following elements:
+ *   - id
+ *      A unique identifier of the e-mail type. Examples: 'contact_user_copy',
+ *      'user_password_reset'.
+ *   - to
+ *      The mail address or addresses where the message will be sent 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>
+ *   - subject
+ *      Subject of the e-mail to be sent. This must not contain any newline
+ *      characters, or the mail may not be sent properly.
+ *   - body
+ *      Message to be sent. Accepts both CRLF and LF line-endings.
+ *      E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
+ *      smart plain text wrapping.
+ *   - headers
+ *      Associative array containing all mail headers.
+ * @return
+ *   Returns TRUE if the mail was successfully accepted for delivery,
+ *   FALSE otherwise.
+ */
+function drupal_mail_send($message) {
+  // Allow for a custom mail backend.
   if (variable_get('smtp_library', '') && file_exists(variable_get('smtp_library', ''))) {
     include_once './' . variable_get('smtp_library', '');
-    return drupal_mail_wrapper($mailkey, $to, $subject, $body, $from, $headers);
+    return drupal_mail_wrapper($message);
   }
   else {
-    // Note: e-mail uses CRLF for line-endings, but PHP's API requires LF.
-    // They will appear correctly in the actual e-mail that is sent.
-
     $mimeheaders = array();
-    foreach ($headers as $name => $value) {
+    foreach ($message['headers'] as $name => $value) {
       $mimeheaders[] = $name .': '. mime_header_encode($value);
     }
     return mail(
-      $to,
-      mime_header_encode($subject),
-      str_replace("\r", '', $body),
+      $message['to'],
+      mime_header_encode($message['subject']),
+      // Note: e-mail uses CRLF for line-endings, but PHP's API requires LF.
+      // They will appear correctly in the actual e-mail that is sent.
+      str_replace("\r", '', $message['body']),
       join("\n", $mimeheaders)
     );
   }
Index: modules/contact/contact.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/contact/contact.module,v
retrieving revision 1.91
diff -u -p -r1.91 contact.module
--- modules/contact/contact.module	26 Jun 2007 22:21:08 -0000	1.91
+++ modules/contact/contact.module	29 Jun 2007 13:29:50 -0000
@@ -360,48 +360,37 @@ function contact_mail_user(&$form_state,
  * Process the personal contact page form submission.
  */
 function contact_mail_user_submit($form, &$form_state) {
-  global $user;
+  global $user, $language;
 
   $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_state['values']['message'];
 
-  // Prepare all fields:
+  // Send from the current user to the requested user.
   $to = $account->mail;
   $from = $user->mail;
 
-  // Format the subject:
-  $subject = '['. variable_get('site_name', 'Drupal') .'] '. $form_state['values']['subject'];
-
-  // Prepare the body:
-  $body = drupal_wrap_mail(implode("\n\n", $message));
+  // Save both users and all form values for email composition.
+  $values = $form_state['values'];
+  $values['account'] = $account;
+  $values['user'] = $user;
 
-  // Send the e-mail:
-  drupal_mail('contact-user-mail', $to, $subject, $body, $from);
+  // Send the e-mail in the requested user language.
+  drupal_mail('contact', 'user_mail', $to, user_language($account), $values, $from);
 
-  // Send a copy if requested:
+  // Send a copy if requested, using current page language.
   if ($form_state['values']['copy']) {
-    drupal_mail('contact-user-copy', $from, $subject, $body, $from);
+    drupal_mail('contact', 'user_copy', $from, $language, $values, $from);
   }
 
-  // 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:
+  
+  // Back to the requested users profile page.
   $form_state['redirect'] = "user/$account->uid";
-  return;
 }
 
 /**
- * Site-wide contact page
+ * Site-wide contact page.
  */
 function contact_site_page() {
   global $user;
@@ -504,45 +493,68 @@ function contact_mail_page_validate($for
  * Process the site-wide contact page form submission.
  */
 function contact_mail_page_submit($form, &$form_state) {
+  global $language;
+  
+  $values = $form_state['values'];
 
   // 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_state['values']['mail'];
-
-  // Compose the body:
-  $message[] = t("!name sent a message using the contact form at !form.", array('!name' => $form_state['values']['name'], '!form' => url($_GET['q'], array('absolute' => TRUE))));
-  $message[] = $form_state['values']['message'];
-
-  // Load the category information:
-  $contact = db_fetch_object(db_query("SELECT * FROM {contact} WHERE cid = %d", $form_state['values']['cid']));
-
-  // Format the category:
-  $subject = t('[!category] !subject', array('!category' => $contact->category, '!subject' => $form_state['values']['subject']));
-
-  // Prepare the body:
-  $body = drupal_wrap_mail(implode("\n\n", $message));
+  $from = $values['mail'];
 
-  // Send the e-mail to the recipients:
-  drupal_mail('contact-page-mail', $contact->recipients, $subject, $body, $from);
+  // Load category properties and save form values for email composition.
+  $contact = db_fetch_object(db_query("SELECT * FROM {contact} WHERE cid = %d", $values['cid']));
+  $values['contact'] = $contact;
+  
+  // Send the e-mail to the recipients using the site default language.
+  drupal_mail('contact', 'page_mail', $contact->recipients, language_default(), $values, $from); 
 
-  // If the user requests it, send a copy.
-  if ($form_state['values']['copy']) {
-    drupal_mail('contact-page-copy', $from, $subject, $body, $from);
+  // If the user requests it, send a copy using the current language.
+  if ($values['copy']) {
+    drupal_mail('contact', 'page_copy', $from, $language, $values, $from);
   }
 
-  // Send an auto-reply if necessary:
+  // Send an auto-reply if necessary using the current language.
   if ($contact->reply) {
-    drupal_mail('contact-page-autoreply', $from, $subject, drupal_wrap_mail($contact->reply), $contact->recipients);
+    drupal_mail('contact', 'page_autoreply', $from, $language, $values, $contact->recipients);
   }
 
-  // Log the operation:
   flood_register_event('contact');
-  watchdog('mail', '%name-from sent an e-mail regarding %category.', array('%name-from' => $form_state['values']['name'] ." [$from]", '%category' => $contact->category));
-
-  // Update user:
+  watchdog('mail', '%name-from sent an e-mail regarding %category.', array('%name-from' => $values['name'] ." [$from]", '%category' => $contact->category));
   drupal_set_message(t('Your message has been sent.'));
 
-  // Jump to home page rather than back to contact page to avoid contradictory messages if flood control has been activated.
+  // Jump to home page rather than back to contact page to avoid
+  // contradictory messages if flood control has been activated.
   $form_state['redirect'] = '';
-  return;
+}
+
+/**
+ * Implementation of hook_mail().
+ */
+function contact_mail($key, &$message, $params) {
+  $language = $message['language'];
+  switch ($key) {
+    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['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['body'][] = $contact->reply;
+      break;
+    case 'user_mail':
+    case 'user_copy':
+      $user = $params['user'];
+      $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'][] = $params['message'];
+      break;
+  }
 }
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.180
diff -u -p -r1.180 locale.module
--- modules/locale/locale.module	18 Jun 2007 16:09:38 -0000	1.180
+++ modules/locale/locale.module	29 Jun 2007 13:29:50 -0000
@@ -186,25 +186,33 @@ function locale_locale($op = 'groups') {
  * 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) {
+  global $language;
+
+  // If we have more then one language and either creating a user on the
+  // admin interface or edit the user, show the language selector.
+  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 == '') {
-      $user->language = language_default('language');
-    }
+
+    // If the user is being created, we set the user language to the page language.
+    $user_language = $user ? user_language($user) : $language;
+
     $names = array();
     foreach ($languages as $langcode => $language) {
       $names[$langcode] = t($language->name) .' ('. $language->native .')';
     }
-    $form['locale'] = array('#type' => 'fieldset',
-      '#title' => t('Interface language settings'),
+    $form['locale'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Language settings'),
       '#weight' => 1,
     );
-    $form['locale']['language'] = array('#type' => 'radios',
+    
+    $form['locale']['language'] = array(
+      '#type' => 'radios',
       '#title' => t('Language'),
-      '#default_value' => $user->language,
+      '#default_value' => $user_language->language,
       '#options' => $names,
-      '#description' => t('Selecting a different locale will change the interface language of the site.'),
+      '#description' => t('Sets the default site interface and e-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.809
diff -u -p -r1.809 user.module
--- modules/user/user.module	28 Jun 2007 07:48:41 -0000	1.809
+++ modules/user/user.module	29 Jun 2007 13:29:53 -0000
@@ -1232,9 +1232,11 @@ function user_pass_validate($form, &$for
 }
 
 function user_pass_submit($form, &$form_state) {
+  global $language;
+
   $account = $form_state['values']['account'];
-  // Mail one time login URL and instructions.
-  $mail_success = _user_mail_notify('password_reset', $account);
+  // Mail one time login URL and instructions using current language.
+  $mail_success = _user_mail_notify('password_reset', $account, $language);
   if ($mail_success) {
     watchdog('user', 'Password reset instructions mailed to %name at %email.', array('%name' => $account->name, '%email' => $account->mail));
     drupal_set_message(t('Further instructions have been sent to your e-mail address.'));
@@ -1420,12 +1422,14 @@ function user_register_submit($form, &$f
     return;
   }
   else {
+    // Add plain text password into user account to generate mail tokens.
+    $account->password = $pass;
     if ($admin && !$notify) {
       drupal_set_message(t('Created a new user account. No e-mail has been sent.'));
     }
     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);
       user_authenticate($account->name, trim($pass));
       $form_state['redirect'] = '';
       return;
@@ -1433,7 +1437,7 @@ function user_register_submit($form, &$f
     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);
       if ($notify) {
         drupal_set_message(t('Password and further instructions have been e-mailed to the new user %user.', array('%user' => $name)));
       }
@@ -1445,7 +1449,7 @@ function user_register_submit($form, &$f
     }
     else {
       // Create new user account, administrator approval required.
-      _user_mail_notify('register_pending_approval', $account, $pass);
+      _user_mail_notify('register_pending_approval', $account);
       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.'));
 
     }
@@ -1707,49 +1711,61 @@ function user_build_content($account) {
   return $account->content;
 }
 
-/*** 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.";
+/**
+ * Implementation of hook_mail().
+ */
+function user_mail($key, &$message, $params) {
+  // Get user account and language for variables.
+  $account = $params['account'];
+  $language = $message['language'];
+  $variables = user_mail_tokens($account, $language);
+  
+  // Get the text for subject and body.
+  foreach (array('subject', 'body') as $item) {
+    // Check if an admin setting overrides the default string.
+    if ($admin_setting = variable_get('user_mail_'. $key .'_'. $item, FALSE)) {
+      $message[$item] = strtr($admin_setting, $variables);
     }
+    else {
+      switch ($key .'_'. $item) {
+        case 'register_no_approval_required_subject':
+          $message[$item] = t('Account details for !username at !site', $variables, $language->language);
+        case 'register_no_approval_required_body':
+          $message[$item] = 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, $language->language);
+        case 'register_admin_created_subject':
+          $message[$item] = t('An administrator created an account for you at !site', $variables, $language->language);
+        case 'register_admin_created_body':
+          $message[$item] = 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, $language->language);
+        case 'register_pending_approval_subject':
+        case 'register_pending_approval_admin_subject':
+          $message[$item] = t('Account details for !username at !site (pending admin approval)', $variables, $language->language);
+        case 'register_pending_approval_body':
+          $message[$item] = 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, $language->language);
+        case 'register_pending_approval_admin_body':
+          $message[$item] = t("!username has applied for an account.\n\n!edit_uri", $variables, $language->language);
+        case 'password_reset_subject':
+          $message[$item] = t('Replacement login information for !username at !site', $variables, $language->language);
+        case 'password_reset_body':
+          $message[$item] = 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, $language->language);
+        case 'status_activated_subject':
+          $message[$item] = t('Account details for !username at !site (approved)', $variables, $language->language);
+        case 'status_activated_body':
+          $message[$item] = 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 the following username:\n\nusername: !username\n", $variables, $language->language);
+        case 'status_blocked_subject':
+          $message[$item] = t('Account details for !username at !site (blocked)', $variables, $language->language);
+        case 'status_blocked_body':
+          $message[$item] = t("!username,\n\nYour account on !site has been blocked.", $variables, $language->language);
+        case 'status_deleted_subject':
+          $message[$item] = t('Account details for !username at !site (deleted)', $variables, $language->language);
+        case 'status_deleted_body':
+          $message[$item] = t("!username,\n\nYour account on !site has been deleted.", $variables, $language->language);      
+      }
+    }    
   }
 }
 
+/*** Administrative features ***********************************************/
+
 function user_admin_check_user() {
   $form['user'] = array('#type' => 'fieldset', '#title' => t('Username'));
   $form['user']['test'] = array('#type' => 'textfield', '#title' => '', '#description' => t('Enter a username to check if it will be denied or allowed.'), '#size' => 30, '#maxlength' => USERNAME_MAX_LENGTH);
@@ -3091,12 +3107,12 @@ function theme_user_signature($signature
  * @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).
+ * @param $language
+ *  Language object to generate the tokens with.
  * @return
  *  Array of mappings from token names to values (for use with strtr()).
  */
-function user_mail_tokens($account, $password = NULL) {
+function user_mail_tokens($account, $language) {
   global $base_url;
   $tokens = array(
     '!username' => $account->name,
@@ -3105,17 +3121,37 @@ function user_mail_tokens($account, $pas
     '!uri' => $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' => format_date(time(), 'medium', '', NULL, $language->language),
+    '!login_uri' => url('user', array('absolute' => TRUE, 'language' => $language)),
+    '!edit_uri' => url('user/'. $account->uid .'/edit', array('absolute' => TRUE, 'language' => $language)),
   );
-  if (!empty($password)) {
-    $tokens['!password'] = $password;
+  if (!empty($account->password)) {
+    $tokens['!password'] = $account->password;
   }
   return $tokens;
 }
 
 /**
+ * Get the language object preferred by the user. Falls back on the the
+ * site default language if no language is set for the user, or if it is
+ * not enabled anymore.
+ * 
+ * @param $account
+ *   User account to look up language for.
+ * @param $default
+ *   Optional default language if account has no valid language.
+ */
+function user_language($account, $default = NULL) {
+  $language_list = language_list();
+  if ($account->language && isset($language_list[$account->language])) {
+    return $language_list[$account->language];
+  }
+  else {
+    return $default ? $default : language_default();
+  }
+}
+
+/**
  * Conditionally create and send a notification email when a certain
  * operation happens on the given user account.
  *
@@ -3135,36 +3171,27 @@ function user_mail_tokens($account, $pas
  * @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).
- *
+ * @param $language
+ *  Optional language to use for the notification, overriding account language.
  * @return
- *  The return value from drupal_mail(), if ends up being called.
+ *  The return value from drupal_mail_send(), if ends up being called.
  */
-function _user_mail_notify($op, $account, $password = NULL) {
-  $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';
-  }
+function _user_mail_notify($op, $account, $language = NULL) {
   // 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 = drupal_wrap_mail(_user_mail_text($op .'_body', $variables));
-    $result = drupal_mail($mail_id, $account->mail, $subject, $body, $from);
+    $params['account'] = $account;
+    $language = $language ? $language : user_language($account);
+    $mail = drupal_mail('user', $op, $account->mail, $language, $params);
     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);
+      // We use the site default language for this.
+      drupal_mail('user', 'register_pending_approval_admin', variable_get('site_mail', ini_get('sendmail_from')), language_default(), $params);
     }
   }
-  return $result;
+  return empty($mail) ? NULL : $mail['result'];
 }
 
 /**
