Index: token.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/token/token.test,v
retrieving revision 1.4
diff -u -p -r1.4 token.test
--- token.test	7 Mar 2010 07:21:55 -0000	1.4
+++ token.test	9 Mar 2010 18:46:14 -0000
@@ -1,7 +1,24 @@
 <?php
 // $Id: token.test,v 1.4 2010/03/07 07:21:55 davereid Exp $
 
-class TokenUnitTestCase extends DrupalWebTestCase {
+/**
+ * Helper test class with some added functions for testing.
+ */
+class TokenTestHelper extends DrupalWebTestCase {
+  function setUp() {
+    // Call parent::setUp() allowing test cases to pass further modules.
+    $modules = func_get_args();
+    $modules = array_merge(array('token'), $modules);
+    call_user_func_array(array('parent', 'setUp'), $modules);
+  }
+
+  function assertToken($type, $object, $token, $expected, $options = array()) {
+    $tokens = token_generate($type, array($token => $token), array($type => $object), $options);
+    $this->assertEqual($tokens[$token], $expected, t("Token value for [@type:@token] was '!actual', expected value '!expected'.", array('@type' => $type, '@token' => $token, '!actual' => $tokens[$token], '!expected' => $expected)));
+  }
+}
+
+class TokenUnitTestCase extends TokenTestHelper {
   public static function getInfo() {
     return array(
       'name' => 'Token unit tests',
@@ -10,10 +27,6 @@ class TokenUnitTestCase extends DrupalWe
     );
   }
 
-  function setUp() {
-    parent::setUp('token');
-  }
-
   /**
    * Test token_get_invalid_tokens() and token_get_invalid_tokens_by_context().
    */
@@ -50,3 +63,20 @@ class TokenUnitTestCase extends DrupalWe
     }
   }
 }
+
+class TokenNodeTestCase extends TokenTestHelper {
+  public static function getInfo() {
+    return array(
+      'name' => 'Node token tests',
+      'description' => 'Test the node tokens.',
+      'group' => 'Token',
+    );
+  }
+
+  function testSource() {
+    $source_node = $this->drupalCreateNode();
+    $translated_node = $this->drupalCreateNode(array('tnid' => $source_node->nid));
+    $this->assertToken('node', $translated_node, 'source', $source_node->title);
+    $this->assertToken('node', $translated_node, 'source:nid', $source_node->nid);
+  }
+}
Index: token.tokens.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/token/token.tokens.inc,v
retrieving revision 1.1
diff -u -p -r1.1 token.tokens.inc
--- token.tokens.inc	8 Mar 2010 03:46:06 -0000	1.1
+++ token.tokens.inc	9 Mar 2010 18:46:14 -0000
@@ -13,3 +13,48 @@ function token_token_info_alter(&$data) 
   // Add a 'dynamic' key to any tokens that have chained but dynamic tokens.
   $data['tokens']['date']['custom']['dynamic'] = TRUE;
 }
+
+/**
+ * Implements hook_token_info().
+ */
+function token_token_info() {
+  $info['tokens']['node']['source'] = array(
+    'name' => t('Translation source node'),
+    'description' => t("Source node for this current node's translation set."),
+    'type' => 'node',
+  );
+
+  return $info;
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function token_tokens($type, $tokens, array $data = array(), array $options = array()) {
+  $replacements = array();
+  $sanitize = !empty($options['sanitize']);
+
+  // Node tokens.
+  if ($type == 'node' && !empty($data['node'])) {
+    $node = $data['node'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'source':
+          if (!empty($node->tnid) && $source_node = node_load($node->tnid)) {
+            $title = $source_node->title;
+            $replacements[$original] = $sanitize ? filter_xss($title) : $title;
+          }
+          break;
+      }
+    }
+
+    // Chained token relationships.
+    if (!empty($node->tnid) && $source_tokens = token_find_with_prefix($tokens, 'source')) {
+      $source_node = node_load($node->tnid);
+      $replacements += token_generate('node', $source_tokens, array('node' => $source_node), $options);
+    }
+  }
+
+  return $replacements;
+}
