Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.641
diff -u -r1.641 common.inc
--- includes/common.inc	15 May 2007 20:19:47 -0000	1.641
+++ includes/common.inc	25 May 2007 13:45:23 -0000
@@ -722,12 +722,145 @@
   elseif (function_exists('locale') && $langcode != 'en') {
     $string = locale($string, $langcode);
   }
+  // We use $langcode as default parameter for callbacks with t function, if any.
+  return $args ? drupal_replace($string, $args, $langcode) : $string;
+}
+
+/**
+ * Text replacement with deferred localization.
+ *   
+ * Can be used to localize the same base text to multiple languages
+ * or when the language is not yet known when composing the text.
+ * 
+ * This function supports callback objects with arguments. These callbacks
+ * will be executed before text replacement with an aditional $langcode argument.
+ * 
+ * Security note: Avoid user supplied input into the main text for replacement,
+ * because it may allow a user to include predefined variables, which may be 
+ * sensitive data, into the resulting text, which may be an e-mail. 
+ * This user supplied input must be in a variable for replacement itself.
+ * 
+ * @code
+ *   // INCORRECT; someone may post 'email !destination' to
+ *   // expose this variable in the resulting text.
+ *   $text[] = $form_values['message']; // User input
+ *   $args = array('!destination' => 'mail@example.com');
+ *   drupal_format_text($text, $args);
+ *   
+ *   // CORRECT; when composing and email, HTML encoding the 
+ *   // input would be problematic, so !message is correct.
+ *   $text[] = '!message';
+ *   $args = array('!destination' => 'mail@example.com', '!message' => $form_values['message']);
+ *   drupal_format_text($text, $args);
+ * @endcode
+ * 
+ * Example to print the same message in all available languages:
+ * 
+ * @code
+ *   // Delayed localization with parameter substitution
+ *   $text[] = t_later('Hello !username,', array('!username' => $user->name)); 
+ *   // Delayed localization for later parameter substitution
+ *   $text[] = t_later('A new !content_type has been posted to !site_name:');
+ *   // Simple string, won't be localized
+ *   $text[] = $node->title 
+ * 
+ *   // Variables for replacement
+ *   $args['!site_name'] => variable_get('site_name', 'Drupal'); // Simple text, won't be localized
+ *   $args['!content_type'] => t_later($node->type); // The variable will be localized 
+ * 
+ *   // Localize for all languages
+ *   $output = '';
+ *   foreach (language_list() as $language) {
+ *     $output .= $language->name .':'. drupal_format_text($text, $args, $language->language);
+ *   }
+ * @endcode
+ *    
+ * Note that the first parameter may be a single callback object
+ * 
+ * @param $text
+ *   Plain text, callback, or array of text components. Each text component 
+ *   can be plain text or a callback array.
+ * @param $args
+ *   Optional associative array of replacements to perform after
+ *   localization. See drupal_replace() for more details.
+ * @param $langcode
+ *   Language code for localization.
+ * @param $wrap
+ *   Optional, TRUE for wrapping each text line. Can be used
+ *   to prepare text for email.
+ * @param $glue
+ *   Optional string to use to concatenate the string results of
+ *   text components at the end.
+ * @return
+ *   Text localized and formatted with variable replacement.
+ */
+function drupal_format_text($text, $args = array(), $langcode = NULL, $wrap = FALSE, $glue = "\n") {
+  // If plain text, convert to array
+  $text = is_array($text) ? $text : array($text);
+  // Each line can be a plain string or a callback object needing $langcode parameter
+  foreach ($text as $key => $value) {
+    // Vale may be a callback so running it through drupal_callback will execute it
+    // If not a callback, the value will be just returned
+    $value = drupal_callback($value, $langcode);
+    $text[$key] = drupal_replace($value, $args);
+    if ($wrap) {
+      // Wrapping to use to format email text.
+      $text[$key] = wordwrap($text[$key]);
+    }
+  }
+  return join($glue, $text);
+}
+
+/**
+ * Performs replacement of placeholders with values in the given string.
+ * 
+ * The value can be a simple string to use as replacement or a callback
+ * array. See drupal_callback() for more details.
+ *
+ * This function is used by t() to perform replacements. See the
+ * documentation of t() for more examples on the usage of placeholders.
+ * 
+ * Examples:
+ * 
+ * @code
+ *   // Simple string replacement
+ *   $string = drupal_replace('Welcome @username', array('@username' => $user-name));
+ *   // Replacement with callback array
+ *   $string = drupal_replace('Thank you for posting a !name', array('!name' => array('t', 'story')));
+ * @endcode
+ * 
+ * @param $string
+ *   Text with placeholders to replace.
+ * @param $args
+ *   An associative array of replacements to perform after localization.
+ *   Incidences of any key in this array are replaced with the corresponding value.
+ *   Based on the first character of the key, the value is escaped and/or themed:
+ *    - !variable: inserted as is
+ *    - @variable: escape plain text to HTML (with check_plain())
+ *    - %variable: escape text and theme as a placeholder for user-submitted
+ *      content (check_plain() + theme_placeholder())
+ *   The value can be a simple string for replacement or an array, which indicates 
+ *   a callback. See drupal_callback() for more details on callbacks.
+ * @param $defaults
+ *   Optional default parameters for known callback functions.
+ *   See drupal_callback() for more details.
+ */
+function drupal_replace($string, $args = NULL) {
   if (!$args) {
+    // Nothing to replace.
     return $string;
   }
   else {
-    // Transform arguments before inserting them
+    // Prepare parameters for callbacks
+    $params = func_get_args();
+    array_shift($params);
+    array_shift($params);
+    // Transform arguments before inserting them.    
     foreach ($args as $key => $value) {
+      if (is_object($value)) {
+        // Invoke callback with optional arguments to get string value.
+        $value = call_user_func_array('drupal_callback', array_merge(array($value), $params));
+      }      
       switch ($key[0]) {
         // Escaped only
         case '@':
@@ -740,13 +873,57 @@
           break;
         // Pass-through
         case '!':
+          $args[$key] = $value;
       }
     }
     return strtr($string, $args);
+  }  
+}
+
+/**
+ * Executes a Drupal callback or just returns the first parameter if it's not a callback.
+ * 
+ * A callback is an special Drupal object that is produced by certain functions
+ * like t_later(). It allows to pass arguments that are actually executable functions.
+ * 
+ * Example:
+ * 
+ * @code
+ *   // The t_later() function will produce a callback object.
+ *   $callback = t_later('Welcome to !site_name', variable_get('site_name', 'Drupal'));
+ *   drupal_callback($callback, $langcode);
+ *   
+ *   // The result will be the same as
+ *   t('Welcome to !site_name', variable_get('site_name', 'Drupal'), $langcode);
+ * @endcode
+ * 
+ * @param $callback
+ *   Callback object.
+ * @param $param1, $param2...
+ *   Optional aditional parameters for the callback function.
+ */
+function drupal_callback() {
+  $params = func_get_args();
+  $callback = array_shift($params);
+  if (is_object($callback) && isset($callback->type) && $callback->type == 'callback' && is_callable($callback->execute)) {
+    return call_user_func_array($callback->execute, array_merge($callback->params, $params));
+  } else {
+    return $callback;
   }
 }
 
 /**
+ * Produces a callback for t() function, to be executed later with optional $langcode argument 
+ */
+function t_later($string, $args = 0) {
+  $callback = new StdClass();
+  $callback->type = 'callback';
+  $callback->execute = 't';
+  $callback->params = array($string, $args);
+  return $callback;
+}
+
+/**
  * @defgroup validation Input validation
  * @{
  * Functions to validate user input.
Index: modules/contact/contact.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/contact/contact.module,v
retrieving revision 1.84
diff -u -r1.84 contact.module
--- modules/contact/contact.module	14 May 2007 13:43:35 -0000	1.84
+++ modules/contact/contact.module	25 May 2007 13:45:24 -0000
@@ -363,41 +363,38 @@
   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
+  $subject = t_later('[!site_name] !subject');
+  // Compose the body
+  $message[] = "!name-to,";
+  $message[] = t_later("!name-from (!name-from-url) has sent you a message via your contact form (!form-url) at !site_name.");
+  $message[] = t_later("If you don't want to receive such e-mails, you can change your settings at !url.");
+  $message[] = t_later('Message:');
+  $message[] = '!message';
+
+  // Prepare variables for replacement. User mail tokens will be
+  // added automatically in user_mail, so they're not needed here.
+  $variables = array(
+    '!name-from'     => $user->name,
+    '!name-to'       => $account->name,
+    '!name-from-url' => url("user/$user->uid", array('absolute' => TRUE)),
+    '!form-url'      => url($_GET['q'], array('absolute' => TRUE)),
+    '!url'           => url("user/$account->uid", array('absolute' => TRUE)),
+    '!subject'       => $form_values['subject'],
+    '!message'       => $form_values['message']
+  );
+  
+  // Send the e-mail
+  user_mail('contact-mail', $account, $subject, $message, $variables, $user->mail);
 
-  // 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);
+    user_mail('contact-copy', $user, $subject, $message, $variables, $user->mail);
   }
 
-  // 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,51 +506,54 @@
  * Process the site-wide contact page form submission.
  */
 function contact_mail_page_submit($form_values, $form, &$form_state) {
-
+  global $user;
+  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:
+  // Format the subject
   $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:
+  $subject_text = t_later('[!category] !subject');
+  // Compose the body
+  $message[] = t_later("!name sent a message using the contact form at !form.");
+  $message[] = '!message';
+
+  // Prepare variables for replacement
+  $variables = array(
+    '!name'     => $form_values['name'], 
+    '!form'     => url($_GET['q'], array('absolute' => TRUE)),
+    '!message'  => $form_values['message'],
+    '!category' => $contact->category,
+    '!subject'  => $form_values['subject']
+  );
+  
+  // Send the e-mail to the recipients using the site default language
+  $default = language_default();
+  $body = drupal_format_text($message, $variables, $default->language, TRUE, "\n\n");
+  $subject = drupal_format_text($subject_text, $variables, $default->language);
   drupal_mail('contact-page-mail', $contact->recipients, $subject, $body, $from);
 
-  // If the user requests it, send a copy.
+  // The subject for the following emails will be in the current page language
+  $subject = drupal_format_text($subject_text, $variables, $language->language);
+
+  // If the user requests it, send a copy using the current page language
   if ($form_values['copy']) {
+    $body = drupal_format_text($message, $variables, $language->language, NULL, TRUE, "\n\n");
     drupal_mail('contact-page-copy', $from, $subject, $body, $from);
   }
 
-  // Send an auto-reply if necessary:
+  // Send an auto-reply if necessary
   if ($contact->reply) {
     drupal_mail('contact-page-autoreply', $from, $subject, wordwrap($contact->reply), $contact->recipients);
   }
 
-  // Log the operation:
   flood_register_event('contact');
   watchdog('mail', '%name-from sent an e-mail regarding %category.', array('%name-from' => $form_values['name'] ." [$from]", '%category' => $contact->category));
-
-  // Update user:
   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.
   $form_state['redirect'] = '';
   return;
 }
-
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.174
diff -u -r1.174 locale.module
--- modules/locale/locale.module	22 May 2007 07:42:37 -0000	1.174
+++ modules/locale/locale.module	25 May 2007 13:45:25 -0000
@@ -186,10 +186,11 @@
  * 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) {
+  // Show language field when user is editing own account and administrator is creating account
+  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 || $user->language == '') {
       $default = language_default();
       $user->language = $default->language;
     }
@@ -203,9 +204,9 @@
     );
     $form['locale']['language'] = array('#type' => 'radios',
       '#title' => t('Language'),
-      '#default_value' => $user->language,
+      '#default_value' => $user ? $user->language : $default->language,
       '#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.785
diff -u -r1.785 user.module
--- modules/user/user.module	22 May 2007 05:52:17 -0000	1.785
+++ modules/user/user.module	25 May 2007 13:45:28 -0000
@@ -1215,6 +1215,76 @@
 }
 
 /**
+ * Send an e-mail message to a user account, automatically providing
+ * subjects and bodies for user e-mails defined in user.module.
+ *
+ * @param $key
+ *   A key to identify the mail sent. The mailkey for altering
+ *   will be 'user-'. $key.
+ * @param $destination
+ *   User account or e-mail address to which the e-mail will be sent.
+ * @param $subject
+ *   Subject of the e-mail to be passed through localization and
+ *   text replacement before sending. This must not contain any
+ *   newline characters, or the mail may not be sent properly.
+ * @param $body
+ *   Message to be sent to be passed through localization and
+ *   text replacement before sending. Thus, this should be a
+ *   simple string or an array of text components. Drupal will
+ *   format the correct line endings for you.
+ * @param $from
+ *   Optional e-mail address to set From, Reply-To, Return-Path
+ *   and Error-To to this value.
+ * @param $headers
+ *   Optional 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.
+ */
+function user_mail($key, $destination, $subject, $body, $variables = array(), $from = NULL, $headers = array()) {
+  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);
+  }
+  else {
+    // Destination is an e-mail address and it doesn't belong to a known user.
+    $mail = $destination;
+    $default = language_default();
+    $langcode = $default->language;
+    $variables += array('!mailto' => $mail);  
+  }
+  // Add standard variables.
+  $variables += user_mail_tokens($account);
+  $from = $from ? $from : variable_get('site_mail', ini_get('sendmail_from'));
+  
+  // Run subject and body through localization and text replacement.
+  $subject = drupal_format_text($subject, $variables, $langcode);
+  $body = drupal_format_text($body, $variables, $langcode, TRUE, "\n\n");  
+
+  return drupal_mail('user-'. $key, $mail, $subject, $body, $from, $headers);
+}
+
+/**
+ * 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;
+  $default = language_default();
+  
+  // The user language will be the site default if no language is set for the account.
+  return (isset($account->language) && $account->language) ? $account->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) {
@@ -1396,7 +1466,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;
@@ -1404,7 +1474,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)));
       }
@@ -1416,9 +1486,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.'));
-
     }
   }
 }
@@ -1668,43 +1737,51 @@
 
 /*** Administrative features ***********************************************/
 
+/**
+ * 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, $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);
+    // An admin setting overrides the default string.
+    return drupal_replace($admin_setting, $variables);
   }
-  // No override, return with default strings.
   else {
+    // No override, return array with callback to localize default strings.
     switch ($messageid) {
       case 'register_no_approval_required_subject':
-        return t('Account details for !username at !site', $variables);
+        return t_later('Account details for !username at !site_name', $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);
+        return t_later("!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", $variables);
       case 'register_admin_created_subject':
-        return t('An administrator created an account for you at !site', $variables);
+        return t_later('An administrator created an account for you at !site_name', $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);
+        return t_later("!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", $variables);
       case 'register_pending_approval_subject':
-        return t('Account details for !username at !site (pending admin approval)', $variables);
+        return t_later('Account details for !username at !site_name (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);
+        return t_later("!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", $variables);
       case 'password_reset_subject':
-        return t('Replacement login information for !username at !site', $variables);
+        return t_later('Replacement login information for !username at !site_name', $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);
+        return t_later("!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.", $variables);
       case 'status_activated_subject':
-        return t('Account details for !username at !site (approved)', $variables);
+        return t_later('Account details for !username at !site_name (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";
+        return t_later("!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", $variables);
       case 'status_blocked_subject':
-        return t('Account details for !username at !site (blocked)', $variables);
+        return t_later('Account details for !username at !site_name (blocked)', $variables);
       case 'status_blocked_body':
-        return "!username,\n\nYour account on !site has been blocked.";
+        return t_later("!username,\n\nYour account on !site_name has been blocked.", $variables);
       case 'status_deleted_subject':
-        return t('Account details for !username at !site (deleted)', $variables);
+        return t_later('Account details for !username at !site_name (deleted)', $variables);
       case 'status_deleted_body':
-        return "!username,\n\nYour account on !site has been deleted.";
+        return t_later("!username,\n\nYour account on !site_name has been deleted.", $variables);
     }
   }
 }
@@ -2460,7 +2537,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',
@@ -3084,28 +3161,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'      => format_date(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'  => url('user/'. $account->uid .'/edit', array('absolute' => TRUE)),
+    );
   }
   return $tokens;
 }
@@ -3114,50 +3194,54 @@
  * 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) {
-  $mail_id = 'user-'. strtr($op, '_', '-');
+function _user_mail_notify($op, $account, $variables) {
+  $mail_id = 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);
+    $result = user_mail($mail_id, $account, $subject, $body, $variables);
     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);
+      $body[] = t_later("!username has applied for an account.\n\n!edit_uri");
+      user_mail('register-approval-admin', variable_get('site_mail', ini_get('sendmail_from')), $subject, $body, $variables);
     }
   }
+
   return $result;
 }
