diff --git a/includes/token.inc b/includes/token.inc
index 2db6c78c906e5b7d44679e7f2b3bcfea0b329d68..57c15ea4f28a4a6e21d9dde795256308b9cfc474 100644
--- a/includes/token.inc
+++ b/includes/token.inc
@@ -152,17 +152,24 @@ function token_scan($text) {
  *   An associative array of replacement values, keyed by the original 'raw'
  *   tokens that were found in the source text. For example:
  *   $results['[node:title]'] = 'My new node';
+ *
+ * @see hook_tokens()
+ * @see hook_tokens_alter()
  */
 function token_generate($type, array $tokens, array $data = array(), array $options = array()) {
-  $results = array();
   $options += array('sanitize' => TRUE);
+  $replacements = module_invoke_all('tokens', $type, $tokens, $data, $options);
 
-  $result = module_invoke_all('tokens', $type, $tokens, $data, $options);
-  foreach ($result as $original => $replacement) {
-    $results[$original] = $replacement;
-  }
+  // Allow other modules to alter the replacements.
+  $context = array(
+    'type' => $type,
+    'tokens' => $tokens,
+    'data' => $data,
+    'options' => $options,
+  );
+  drupal_alter('tokens', $replacements, $context);
 
-  return $results;
+  return $replacements;
 }
 
 /**
diff --git a/modules/system/system.api.php b/modules/system/system.api.php
index 059bafcd6570a0b8d2b6b4eb2faaa7ded5256512..d26548131a6d5a2fcf0e98465bb54b9935f076e9 100644
--- a/modules/system/system.api.php
+++ b/modules/system/system.api.php
@@ -3807,6 +3807,8 @@ function hook_username_alter(&$name, $account) {
  *   An associative array of replacement values, keyed by the original 'raw'
  *   tokens that were found in the source text. For example:
  *   $results['[node:title]'] = 'My new node';
+ *
+ * @see hook_tokens_alter()
  */
 function hook_tokens($type, $tokens, array $data = array(), array $options = array()) {
   $url_options = array('absolute' => TRUE);
@@ -3864,6 +3866,46 @@ function hook_tokens($type, $tokens, array $data = array(), array $options = arr
   return $replacements;
 }
 
+/**
+ * Alter replacement values for placeholder tokens.
+ *
+ * @param $replacements
+ *   An associative array of replacements returned by hook_tokens().
+ * @param $context
+ *   The context in which hook_tokens() was called. An associative array with
+ *   the following keys, which have the same meaning as the corresponding
+ *   parameters of hook_tokens():
+ *   - 'type'
+ *   - 'tokens'
+ *   - 'data'
+ *   - 'options'
+ *
+ * @see hook_tokens()
+ */
+function hook_tokens_alter(array &$replacements, array $context) {
+  $options = $context['options'];
+
+  if (isset($options['language'])) {
+    $url_options['language'] = $options['language'];
+    $language_code = $options['language']->language;
+  }
+  else {
+    $language_code = NULL;
+  }
+  $sanitize = !empty($options['sanitize']);
+
+  if ($context['type'] == 'node' && !empty($context['data']['node'])) {
+    $node = $context['data']['node'];
+
+    // Alter the [node:title] token, and replace it with the rendered content
+    // of a field (field_title).
+    if (isset($context['tokens']['title'])) {
+      $title = field_view_field('node', $node, 'field_title', 'default', $language_code);
+      $replacements[$context['tokens']['title']] = drupal_render($title);
+    }
+  }
+}
+
 /**
  * Provide metadata about available placeholder tokens and token types.
  *
@@ -3913,34 +3955,6 @@ function hook_token_info() {
   );
 }
 
-/**
- * Alter batch information before a batch is processed.
- *
- * Called by batch_process() to allow modules to alter a batch before it is
- * processed.
- *
- * @param $batch
- *   The associative array of batch information. See batch_set() for details on
- *   what this could contain.
- *
- * @see batch_set()
- * @see batch_process()
- *
- * @ingroup batch
- */
-function hook_batch_alter(&$batch) {
-  // If the current page request is inside the overlay, add ?render=overlay to
-  // the success callback URL, so that it appears correctly within the overlay.
-  if (overlay_get_mode() == 'child') {
-    if (isset($batch['url_options']['query'])) {
-      $batch['url_options']['query']['render'] = 'overlay';
-    }
-    else {
-      $batch['url_options']['query'] = array('render' => 'overlay');
-    }
-  }
-}
-
 /**
  * Alter the metadata about available placeholder tokens and token types.
  *
@@ -3968,6 +3982,33 @@ function hook_token_info_alter(&$data) {
   );
 }
 
+/**
+ * Alter batch information before a batch is processed.
+ *
+ * Called by batch_process() to allow modules to alter a batch before it is
+ * processed.
+ *
+ * @param $batch
+ *   The associative array of batch information. See batch_set() for details on
+ *   what this could contain.
+ *
+ * @see batch_set()
+ * @see batch_process()
+ *
+ * @ingroup batch
+ */
+function hook_batch_alter(&$batch) {
+  // If the current page request is inside the overlay, add ?render=overlay to
+  // the success callback URL, so that it appears correctly within the overlay.
+  if (overlay_get_mode() == 'child') {
+    if (isset($batch['url_options']['query'])) {
+      $batch['url_options']['query']['render'] = 'overlay';
+    }
+    else {
+      $batch['url_options']['query'] = array('render' => 'overlay');
+    }
+  }
+}
 
 /**
  * Provide information on Updaters (classes that can update Drupal).