Index: includes/translation.node.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/translation/includes/translation.node.inc,v
retrieving revision 1.2
diff -u -r1.2 translation.node.inc
--- includes/translation.node.inc	3 Oct 2010 16:36:14 -0000	1.2
+++ includes/translation.node.inc	26 Jan 2011 17:30:50 -0000
@@ -37,7 +37,7 @@
  * Node specific access callback.
  */
 function translation_node_tab_access($node) {
-  return !empty($node->language) && (translation_supported_type($node->type) || translation_node('node', $node)) && translation_tab_access('node');
+  return !empty($node->language) && ((translation_supported_type($node->type) && translation_tab_access('node')) || translation_node('node', $node));
 }
 
 /**
Index: modules/translation_node/translation_node.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/translation/modules/translation_node/translation_node.pages.inc,v
retrieving revision 1.1
diff -u -r1.1 translation_node.pages.inc
--- modules/translation_node/translation_node.pages.inc	23 Sep 2010 11:58:28 -0000	1.1
+++ modules/translation_node/translation_node.pages.inc	26 Jan 2011 17:30:52 -0000
@@ -13,6 +13,8 @@
  *   Node object.
  */
 function translation_node_overview($node) {
+  include_once DRUPAL_ROOT . '/includes/language.inc';
+
   if ($node->tnid) {
     // Already part of a set, grab that set.
     $tnid = $node->tnid;
@@ -24,18 +26,24 @@
     $translations = array($node->language => $node);
   }
 
+  $type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
   $header = array(t('Language'), t('Title'), t('Status'), t('Operations'));
 
-  foreach (language_list() as $language) {
+  foreach (language_list() as $langcode => $language) {
     $options = array();
     $language_name = $language->name;
-    if (isset($translations[$language->language])) {
+    if (isset($translations[$langcode])) {
       // Existing translation in the translation set: display status.
       // We load the full node to check whether the user can edit it.
-      $translation_node = node_load($translations[$language->language]->nid);
-      $title = l($translation_node->title, 'node/' . $translation_node->nid);
+      $translation_node = node_load($translations[$langcode]->nid);
+      $path = 'node/' . $translation_node->nid;
+      $links = language_negotiation_get_switch_links($type, $path);
+      $title = empty($links->links[$langcode]) ? l($translation_node->title, $path) : l($translation_node->title, $links->links[$langcode]['href'], $links->links[$langcode]);
       if (node_access('update', $translation_node)) {
-        $options[] = l(t('edit'), "node/$translation_node->nid/edit");
+        $text = t('edit');
+        $path = 'node/' . $translation_node->nid . '/edit';
+        $links = language_negotiation_get_switch_links($type, $path);
+        $options[] = empty($links->links[$langcode]) ? l($text, $path) : l($text, $links->links[$langcode]['href'], $links->links[$langcode]);
       }
       $status = $translation_node->status ? t('Published') : t('Not published');
       $status .= $translation_node->translate ? ' - <span class="marker">' . t('outdated') . '</span>' : '';
@@ -47,7 +55,11 @@
       // No such translation in the set yet: help user to create it.
       $title = t('n/a');
       if (node_access('create', $node)) {
-        $options[] = l(t('add translation'), 'node/add/' . str_replace('_', '-', $node->type), array('query' => array('translation' => $node->nid, 'target' => $language->language)));
+        $text = t('add translation');
+        $path = 'node/add/' . str_replace('_', '-', $node->type);
+        $links = language_negotiation_get_switch_links($type, $path);
+        $query = array('query' => array('translation' => $node->nid, 'target' => $langcode));
+        $options[] = empty($links->links[$langcode]) ? l($text, $path, $query) : l($text, $links->links[$langcode]['href'], array_merge_recursive($links->links[$langcode], $query));
       }
       $status = t('Not translated');
     }
Index: modules/translation_node/translation_node.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/translation/modules/translation_node/translation_node.info,v
retrieving revision 1.1
diff -u -r1.1 translation_node.info
--- modules/translation_node/translation_node.info	23 Sep 2010 11:58:28 -0000	1.1
+++ modules/translation_node/translation_node.info	26 Jan 2011 17:30:50 -0000
@@ -5,6 +5,4 @@
 dependencies[] = translation
 package = Multilingual
 core = 7.x
-files[] = translation_node.module
-files[] = translation_node.pages.inc
-;files[] = translation_node.test
+files[] = translation_node.test
Index: modules/translation_node/translation_node.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/translation/modules/translation_node/translation_node.module,v
retrieving revision 1.1
diff -u -r1.1 translation_node.module
--- modules/translation_node/translation_node.module	23 Sep 2010 11:58:28 -0000	1.1
+++ modules/translation_node/translation_node.module	26 Jan 2011 17:30:52 -0000
@@ -72,7 +72,7 @@
 function translation_node_permission() {
   return array(
     'translate content' => array(
-      'title' => t('Translate content'),
+      'title' => t('Create node translations'),
     ),
   );
 }
@@ -83,6 +83,7 @@
 function translation_node_form_node_type_form_alter(&$form, &$form_state) {
   // Add translation option to content type form.
   $form['workflow']['language_content_type']['#options'][TRANSLATION_NODE_ENABLED] = t('Enabled, with node translation');
+  // Description based on text from locale.module.
   $form['workflow']['language_content_type']['#description'] .= ' ' . t('If <em>node translation</em> is enabled every translation will be a different node.');
 }
 
@@ -93,9 +94,27 @@
  * - Alters language fields on node forms when a translation
  *   is about to be created.
  */
-function translation_node_form_alter(&$form, &$form_state, $form_id) {
-  if (!empty($form['#node_edit_form']) && translation_node_supported_type($form['#node']->type)) {
+function translation_node_form_node_form_alter(&$form, &$form_state) {
+  if (translation_node_supported_type($form['#node']->type)) {
     $node = $form['#node'];
+    $languages = language_list('enabled');
+    $disabled_languages = isset($languages[0]) ? $languages[0] : FALSE;
+    $translator_widget = $disabled_languages && user_access('translate content');
+    $groups = array(t('Disabled'), t('Enabled'));
+    // Allow translators to enter content in disabled languages. Translators
+    // might need to distinguish between enabled and disabled languages, hence
+    // we divide them in two option groups.
+    if ($translator_widget) {
+      $options = array();
+      $language_list = locale_language_list('name', TRUE);
+      foreach (array(1, 0) as $status) {
+        $group = $groups[$status];
+        foreach ($languages[$status] as $langcode => $language) {
+          $options[$group][$langcode] = $language_list[$langcode];
+        }
+      }
+      $form['language']['#options'] = $options;
+    }
     if (!empty($node->translation_source)) {
       // We are creating a translation. Add values and lock language field.
       $form['translation_source'] = array('#type' => 'value', '#value' => $node->translation_source);
@@ -106,9 +125,15 @@
       // node to some language which is already in the translation set. Also remove the
       // language neutral option.
       unset($form['language']['#options'][LANGUAGE_NONE]);
-      foreach (translation_node_get_translations($node->tnid) as $translation) {
+      foreach (translation_node_get_translations($node->tnid) as $langcode => $translation) {
         if ($translation->nid != $node->nid) {
-          unset($form['language']['#options'][$translation->language]);
+          if ($translator_widget) {
+            $group = $groups[(int)!isset($disabled_languages[$langcode])];
+            unset($form['language']['#options'][$group][$langcode]);
+          }
+          else {
+            unset($form['language']['#options'][$langcode]);
+          }
         }
       }
       // Add translation values and workflow options.
@@ -147,20 +172,61 @@
 /**
  * Implements hook_node_view().
  *
- * Display translation links with native language names, if this node
- * is part of a translation set.
+ * Display translation links with native language names, if this node is part of
+ * a translation set. If no language provider is enabled "fall back" to the
+ * simple links built through the result of translation_node_get_translations().
  */
 function translation_node_node_view($node, $view_mode) {
-  if (isset($node->tnid) && drupal_multilingual() &&
-      translation_node_supported_type($node->type) && $translations = translation_node_get_translations($node->tnid)) {
-    $path = 'node/' . $node->nid;
-    $links = language_negotiation_get_switch_links(LANGUAGE_TYPE_CONTENT, $path);
-    if (is_object($links)) {
-      $links = $links->links;
-      // Do not show link to the same node.
-      unset($links[$node->language]);
-      $node->content['links']['#links'] = array_merge($node->content['links']['#links'], $links);
+  // If the site has no translations or is not multilingual we have no content
+  // translation links to display.
+  if (isset($node->tnid) && drupal_multilingual() && $translations = translation_node_get_translations($node->tnid)) {
+    $languages = language_list('enabled');
+    $languages = $languages[1];
+
+    // There might be a language provider enabled defining custom language
+    // switch links which need to be taken into account while generating the
+    // content translation links. As custom language switch links are available
+    // only for configurable language types and interface language is the only
+    // configurable language type in core, we use it as default. Contributed
+    // modules can change this behavior by setting the system variable below.
+    $type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
+    $custom_links = language_negotiation_get_switch_links($type, "node/$node->nid");
+    $links = array();
+
+    foreach ($translations as $langcode => $translation) {
+      // Do not show links to the same node, to unpublished translations or to
+      // translations in disabled languages.
+      if ($translation->status && isset($languages[$langcode]) && $langcode != $node->language) {
+        $language = $languages[$langcode];
+        $key = "translation_$langcode";
+
+        if (isset($custom_links->links[$langcode])) {
+          $links[$key] = $custom_links->links[$langcode];
+        }
+        else {
+          $links[$key] = array(
+            'href' => "node/{$translation->nid}",
+            'title' => $language->native,
+            'language' => $language,
+          );
+        }
+
+        // Custom switch links are more generic than content translation links,
+        // hence we override existing attributes with the ones below.
+        $links[$key] += array('attributes' => array());
+        $attributes = array(
+          'title' => $translation->title,
+          'class' => array('translation-link'),
+        );
+        $links[$key]['attributes'] = $attributes + $links[$key]['attributes'];
+      }
     }
+
+    $node->content['links']['translation'] = array(
+      '#theme' => 'links__node__translation',
+      '#links' => $links,
+      '#attributes' => array('class' => array('links', 'inline')),
+    );
   }
 }
 
@@ -243,6 +309,8 @@
         ))
         ->condition('nid', $node->nid)
         ->execute();
+      // Save tnid to avoid loss in case of resave.
+      $node->tnid = $tnid;
     }
   }
 }
@@ -355,7 +423,7 @@
     if (!isset($translations[$tnid])) {
       $translations[$tnid] = array();
       $result = db_select('node', 'n')
-        ->fields('n', array('nid', 'title', 'language'))
+        ->fields('n', array('nid', 'type', 'uid', 'status', 'title', 'language'))
         ->condition('n.tnid', $tnid)
         ->addTag('node_access')
         ->execute();
@@ -391,7 +459,7 @@
 function translation_node_path_get_translations($path) {
   $paths = array();
   // Check for a node related path, and for its translations.
-  if ((preg_match("!^node/([0-9]+)(/.+|)$!", $path, $matches)) && ($node = node_load((int)$matches[1])) && !empty($node->tnid)) {
+  if ((preg_match("!^node/(\d+)(/.+|)$!", $path, $matches)) && ($node = node_load((int) $matches[1])) && !empty($node->tnid)) {
     foreach (translation_node_get_translations($node->tnid) as $language => $translation_node) {
       $paths[$language] = 'node/' . $translation_node->nid . $matches[2];
     }
@@ -405,15 +473,19 @@
  * Replaces links with pointers to translated versions of the content.
  */
 function translation_node_language_switch_links_alter(array &$links, $type, $path) {
-  if ($type == LANGUAGE_TYPE_CONTENT && $paths = translation_node_path_get_translations($path)) {
+  $language_type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
+  if ($type == $language_type && preg_match("!^node/(\d+)(/.+|)!", $path, $matches) && ($node = node_load((int) $matches[1]))) {
+    $translations = $node->tnid ? translation_node_get_translations($node->tnid) : array($node->language => $node);
+
     foreach ($links as $langcode => $link) {
-      if (isset($paths[$langcode])) {
+      if (isset($translations[$langcode]) && $translations[$langcode]->status) {
         // Translation in a different node.
-        $links[$langcode]['href'] = $paths[$langcode];
+        $links[$langcode]['href'] = 'node/' . $translations[$langcode]->nid . $matches[2];
       }
       else {
         // No translation in this language, or no permission to view.
-        unset($links[$langcode]);
+        unset($links[$langcode]['href']);
+        $links[$langcode]['attributes']['class'] = 'locale-untranslated';
       }
     }
   }
Index: modules/translation_node/translation_node.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/translation/modules/translation_node/translation_node.test,v
retrieving revision 1.1
diff -u -r1.1 translation_node.test
--- modules/translation_node/translation_node.test	23 Sep 2010 11:58:28 -0000	1.1
+++ modules/translation_node/translation_node.test	26 Jan 2011 17:30:54 -0000
@@ -13,46 +13,74 @@
   }
 
   function setUp() {
-    parent::setUp('locale', 'translation', 'translation_node');
-  }
+    parent::setUp('locale', 'translation', 'translation_node', 'translation_node_test');
 
-  /**
-   * Create a basic page with translation, modify the basic page outdating translation, and update translation.
-   */
-  function testContentTranslation() {
     // Setup users.
-    $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
-    $translator = $this->drupalCreateUser(array('create page content', 'edit own page content', 'translate content'));
+    $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages'));
+    $this->translator = $this->drupalCreateUser(array('create page content', 'edit own page content', 'translate content'));
 
-    $this->drupalLogin($admin_user);
+    $this->drupalLogin($this->admin_user);
 
     // Add languages.
     $this->addLanguage('en');
     $this->addLanguage('es');
+    $this->addLanguage('it');
+
+    // Disable Italian to test the translation behavior with disabled languages.
+    $edit = array('enabled[it]' => FALSE);
+    $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
 
-    // Set "Basic page" content type to use multilingual support with translation.
+    // Set "Basic page" content type to use multilingual support with
+    // translation.
     $this->drupalGet('admin/structure/types/manage/page');
     $edit = array();
     $edit['language_content_type'] = 2;
     $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
     $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.'));
 
-    $this->drupalLogout();
-    $this->drupalLogin($translator);
+    // Enable the language switcher block.
+    $language_type = LANGUAGE_TYPE_INTERFACE;
+    $edit = array("blocks[locale_$language_type][region]" => 'sidebar_first');
+    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+    // Enable URL language detection and selection to make the language switcher
+    // block appear.
+    $edit = array('language[enabled][locale-url]' => TRUE);
+    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
+    $this->assertRaw(t('Language negotiation configuration saved.'), t('URL language detection enabled.'));
+    $this->resetCaches();
 
+    $this->drupalLogin($this->translator);
+  }
+
+  /**
+   * Create a basic page with translation, modify the basic page outdating
+   * translation, and update translation.
+   */
+  function testContentTranslation() {
     // Create Basic page in English.
     $node_title = $this->randomName();
     $node_body =  $this->randomName();
     $node = $this->createPage($node_title, $node_body, 'en');
 
+    // Check that the "add translation" link uses a localized path.
+    $languages = language_list();
+    $this->drupalGet('node/' . $node->nid . '/translate');
+    $this->assertLinkByHref($languages['es']->prefix . '/node/add/' . str_replace('_', '-', $node->type), 0, t('The "add translation" link for %language points to the localized path of the target language.', array('%language' => $languages['es']->name)));
+
     // Submit translation in Spanish.
     $node_translation_title = $this->randomName();
     $node_translation_body = $this->randomName();
     $node_translation = $this->createTranslation($node, $node_translation_title, $node_translation_body, 'es');
 
+    // Check that the "edit translation" and "view node" links use localized
+    // paths.
+    $this->drupalGet('node/' . $node->nid . '/translate');
+    $this->assertLinkByHref($languages['es']->prefix . '/node/' . $node_translation->nid . '/edit', 0, t('The "edit" link for the translation in %language points to the localized path of the translation language.', array('%language' => $languages['es']->name)));
+    $this->assertLinkByHref($languages['es']->prefix . '/node/' . $node_translation->nid, 0, t('The "view" link for the translation in %language points to the localized path of the translation language.', array('%language' => $languages['es']->name)));
+
     // Attempt to submit a duplicate translation by visiting the node/add page
     // with identical query string.
-    $languages = language_list();
     $this->drupalGet('node/add/page', array('query' => array('translation' => $node->nid, 'target' => 'es')));
     $this->assertRaw(t('A translation of %title in %language already exists', array('%title' => $node_title, '%language' => $languages['es']->name)), t('Message regarding attempted duplicate translation is displayed.'));
 
@@ -67,13 +95,15 @@
     $this->assertEqual($duplicate->tnid, 0, t('The node does not have a tnid.'));
 
     // Update original and mark translation as outdated.
+    $node_body = $this->randomName();
+    $node->body[$node->language][0]['value'] = $node_body;
     $edit = array();
-    $edit["body[$node->language][0][value]"] = $this->randomName();
+    $edit["body[$node->language][0][value]"] = $node_body;
     $edit['translation[retranslate]'] = TRUE;
     $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
     $this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node_title)), t('Original node updated.'));
 
-    // Check to make sure that interface shows translation as outdated
+    // Check to make sure that interface shows translation as outdated.
     $this->drupalGet('node/' . $node->nid . '/translate');
     $this->assertRaw('<span class="marker">' . t('outdated') . '</span>', t('Translation marked as outdated.'));
 
@@ -83,13 +113,115 @@
     $edit['translation[status]'] = FALSE;
     $this->drupalPost('node/' . $node_translation->nid . '/edit', $edit, t('Save'));
     $this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node_translation_title)), t('Translated node updated.'));
+
+    // Confirm that disabled languages are an option for translators when
+    // creating nodes.
+    $this->drupalGet('node/add/page');
+    $this->assertFieldByXPath('//select[@name="language"]//option', 'it', t('Italian (disabled) is available in language selection.'));
+    $translation_it = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'it');
+    $this->assertRaw($translation_it->body['it'][0]['value'], t('Content created in Italian (disabled).'));
+
+    // Leave just one language enabled and check that the translation overview
+    // page is still accessible.
+    $this->drupalLogin($this->admin_user);
+    $edit = array('enabled[es]' => FALSE);
+    $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
+    $this->drupalLogin($this->translator);
+    $this->drupalGet('node/' . $node->nid . '/translate');
+    $this->assertRaw(t('Translations of %title', array('%title' => $node->title)), t('Translation overview page available with only one language enabled.'));
+  }
+
+  /**
+   * Check that language switch links behave properly.
+   */
+  function testLanguageSwitchLinks() {
+    // Create a Basic page in English and its translations in Spanish and
+    // Italian.
+    $node = $this->createPage($this->randomName(), $this->randomName(), 'en');
+    $translation_es = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'es');
+    $translation_it = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'it');
+
+    // Check that language switch links are correctly shown only for enabled
+    // languages.
+    $this->assertLanguageSwitchLinks($node, $translation_es);
+    $this->assertLanguageSwitchLinks($translation_es, $node);
+    $this->assertLanguageSwitchLinks($node, $translation_it, FALSE);
+
+    // Check that links to the displayed translation appear only in the language
+    // switcher block.
+    $this->assertLanguageSwitchLinks($node, $node, FALSE, 'node');
+    $this->assertLanguageSwitchLinks($node, $node, TRUE, 'block-locale');
+
+    // Unpublish the Spanish translation to check that the related language
+    // switch link is not shown.
+    $this->drupalLogin($this->admin_user);
+    $edit = array('status' => FALSE);
+    $this->drupalPost("node/$translation_es->nid/edit", $edit, t('Save'));
+    $this->drupalLogin($this->translator);
+    $this->assertLanguageSwitchLinks($node, $translation_es, FALSE);
+
+    // Check that content translation links are shown even when no language
+    // negotiation is configured.
+    $this->drupalLogin($this->admin_user);
+    $edit = array('language[enabled][locale-url]' => FALSE);
+    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
+    $this->resetCaches();
+    $edit = array('status' => TRUE);
+    $this->drupalPost("node/$translation_es->nid/edit", $edit, t('Save'));
+    $this->drupalLogin($this->translator);
+    $this->assertLanguageSwitchLinks($node, $translation_es, TRUE, 'node');
+  }
+
+  /**
+   * Test that the language switcher block alterations work as intended.
+   */
+  function testLanguageSwitcherBlockIntegration() {
+    // Enable Italian to have three items in the language switcher block.
+    $this->drupalLogin($this->admin_user);
+    $edit = array('enabled[it]' => TRUE);
+    $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
+    $this->drupalLogin($this->translator);
+
+    // Create a Basic page in English.
+    $type = 'block-locale';
+    $node = $this->createPage($this->randomName(), $this->randomName(), 'en');
+    $this->assertLanguageSwitchLinks($node, $node, TRUE, $type);
+    $this->assertLanguageSwitchLinks($node, $this->emptyNode('es'), TRUE, $type);
+    $this->assertLanguageSwitchLinks($node, $this->emptyNode('it'), TRUE, $type);
+
+    // Create the Spanish translation.
+    $translation_es = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'es');
+    $this->assertLanguageSwitchLinks($node, $node, TRUE, $type);
+    $this->assertLanguageSwitchLinks($node, $translation_es, TRUE, $type);
+    $this->assertLanguageSwitchLinks($node, $this->emptyNode('it'), TRUE, $type);
+
+    // Create the Italian translation.
+    $translation_it = $this->createTranslation($node, $this->randomName(), $this->randomName(), 'it');
+    $this->assertLanguageSwitchLinks($node, $node, TRUE, $type);
+    $this->assertLanguageSwitchLinks($node, $translation_es, TRUE, $type);
+    $this->assertLanguageSwitchLinks($node, $translation_it, TRUE, $type);
+  }
+
+  /**
+   * Reset static caches to make the test code match the client site behavior.
+   */
+  function resetCaches() {
+    drupal_static_reset('locale_url_outbound_alter');
+  }
+
+  /**
+   * Return an empty node data structure.
+   */
+  function emptyNode($langcode) {
+    return (object) array('nid' => NULL, 'language' => $langcode);
   }
 
   /**
    * Install a the specified language if it has not been already. Otherwise make sure that
    * the language is enabled.
    *
-   * @param string $language_code The language code the check.
+   * @param $language_code
+   *   The language code the check.
    */
   function addLanguage($language_code) {
     // Check to make sure that language has not already been installed.
@@ -101,7 +233,7 @@
       $edit['langcode'] = $language_code;
       $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
 
-      // Make sure we're not using a stale list.
+      // Make sure we are not using a stale list.
       drupal_static_reset('language_list');
       $languages = language_list('language');
       $this->assertTrue(array_key_exists($language_code, $languages), t('Language was installed successfully.'));
@@ -125,9 +257,12 @@
   /**
    * Create a "Basic page" in the specified language.
    *
-   * @param string $title Title of basic page in specified language.
-   * @param string $body Body of basic page in specified language.
-   * @param string $language Language code.
+   * @param $title
+   *   Title of basic page in specified language.
+   * @param $body
+   *   Body of basic page in specified language.
+   * @param
+   *   $language Language code.
    */
   function createPage($title, $body, $language) {
     $edit = array();
@@ -146,12 +281,17 @@
   }
 
   /**
-   * Create a translation for the specified basic page in the specified language.
+   * Create a translation for the specified basic page in the specified
+   * language.
    *
-   * @param integer $nid Node id of basic page to create translation for.
-   * @param string $title Title of basic page in specified language.
-   * @param string $body Body of basic page in specified language.
-   * @param string $language Language code.
+   * @param $node
+   *   The basic page to create translation for.
+   * @param $title
+   *   Title of basic page in specified language.
+   * @param $body
+   *   Body of basic page in specified language.
+   * @param $language
+   *   Language code.
    */
   function createTranslation($node, $title, $body, $language) {
     $this->drupalGet('node/add/page', array('query' => array('translation' => $node->nid, 'target' => $language)));
@@ -167,9 +307,108 @@
     $this->assertRaw(t('Basic page %title has been created.', array('%title' => $title)), t('Translation created.'));
 
     // Check to make sure that translation was successful.
-    $node = $this->drupalGetNodeByTitle($title);
-    $this->assertTrue($node, t('Node found in database.'));
+    $translation = $this->drupalGetNodeByTitle($title);
+    $this->assertTrue($translation, t('Node found in database.'));
+    $this->assertTrue($translation->tnid == $node->nid, t('Translation set id correctly stored.'));
 
-    return $node;
+    return $translation;
+  }
+
+  /**
+   * Assert that an element identified by the given XPath has the given content.
+   *
+   * @param $xpath
+   *   XPath used to find the element.
+   * @param array $arguments
+   *   An array of arguments with keys in the form ':name' matching the
+   *   placeholders in the query. The values may be either strings or numeric
+   *   values.
+   * @param $value
+   *   The text content of the matched element to assert.
+   * @param $message
+   *   Message to display.
+   * @param $group
+   *   The group this message belongs to.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  function assertContentByXPath($xpath, array $arguments = array(), $value = NULL, $message = '', $group = 'Other') {
+    $found = $this->findContentByXPath($xpath, $arguments, $value);
+    return $this->assertTrue($found, $message, $group);
+  }
+
+  /**
+   * Check that the specified language switch links are found/not found.
+   *
+   * @param $node
+   *   The node to display.
+   * @param $translation
+   *   The translation whose link has to be checked.
+   * @param $find
+   *   TRUE if the link must be present in the node page.
+   * @param $types
+   *   The page areas to be checked.
+   *
+   * @return
+   *   TRUE if the language switch links are found/not found.
+   */
+  function assertLanguageSwitchLinks($node, $translation, $find = TRUE, $types = NULL) {
+    if (empty($types)) {
+      $types = array('node', 'block-locale');
+    }
+    elseif (is_string($types)) {
+      $types = array($types);
+    }
+
+    $result = TRUE;
+    $languages = language_list();
+    $page_language = $languages[$node->language];
+    $translation_language = $languages[$translation->language];
+    $url = url("node/$translation->nid", array('language' => $translation_language));
+
+    $this->drupalGet("node/$node->nid", array('language' => $page_language));
+
+    foreach ($types as $type) {
+      $args = array('%translation_language' => $translation_language->native, '%page_language' => $page_language->native, '%type' => $type);
+      if ($find) {
+        $message = t('[%page_language] Language switch item found for %translation_language language in the %type page area.', $args);
+      }
+      else {
+        $message = t('[%page_language] Language switch item not found for %translation_language language in the %type page area.', $args);
+      }
+
+      if (!empty($translation->nid)) {
+        $xpath = '//div[contains(@class, :type)]//a[@href=:url]';
+      }
+      else {
+        $xpath = '//div[contains(@class, :type)]//span[@class="locale-untranslated"]';
+      }
+
+      $found = $this->findContentByXPath($xpath, array(':type' => $type, ':url' => $url), $translation_language->native);
+      $result = $this->assertTrue($found == $find, $message) && $result;
+    }
+
+    return $result;
+  }
+
+  /**
+   * Search for elements matching the given xpath and value.
+   */
+  function findContentByXPath($xpath, array $arguments = array(), $value = NULL) {
+    $elements = $this->xpath($xpath, $arguments);
+
+    $found = TRUE;
+    if ($value && $elements) {
+      $found = FALSE;
+      foreach ($elements as $element) {
+        if ((string) $element == $value) {
+          $found = TRUE;
+          break;
+        }
+      }
+    }
+
+    return $elements && $found;
   }
 }
Index: modules/translation_node/tests/translation_node_test.info
===================================================================
RCS file: modules/translation_node/tests/translation_node_test.info
diff -N modules/translation_node/tests/translation_node_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/translation_node/tests/translation_node_test.info	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,6 @@
+; $Id$
+name = "Content translation (Node) Test"
+description = "Support module for the node translation tests."
+core = 7.x
+package = Testing
+hidden = TRUE
Index: modules/translation_node/tests/translation_node_test.module
===================================================================
RCS file: modules/translation_node/tests/translation_node_test.module
diff -N modules/translation_node/tests/translation_node_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/translation_node/tests/translation_node_test.module	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,14 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Mock module for content translation tests.
+ */
+
+/**
+ * Implements hook_node_insert().
+ */
+function translation_test_node_insert($node) {
+  drupal_write_record('node', $node, 'nid');
+}
