Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.302
diff -u -p -r1.302 locale.module
--- modules/locale/locale.module	5 Oct 2010 19:59:10 -0000	1.302
+++ modules/locale/locale.module	9 Oct 2010 08:59:46 -0000
@@ -941,7 +941,11 @@ function locale_block_view($type) {
 function locale_url_outbound_alter(&$path, &$options, $original_path) {
   // Only modify internal URLs.
   if (!$options['external'] && drupal_multilingual()) {
-    static $callbacks;
+    static $drupal_static_fast;
+    if (!isset($drupal_static_fast)) {
+      $drupal_static_fast['callbacks'] = &drupal_static(__FUNCTION__);
+    }
+    $callbacks = &$drupal_static_fast['callbacks'];
 
     if (!isset($callbacks)) {
       $callbacks = array();
@@ -969,6 +973,11 @@ function locale_url_outbound_alter(&$pat
     foreach ($callbacks as $callback) {
       $callback($path, $options);
     }
+
+    // No language dependent path allowed in this mode.
+    if (empty($callbacks)) {
+      unset($options['language']);
+    }
   }
 }
 
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.40
diff -u -p -r1.40 path.test
--- modules/path/path.test	5 Aug 2010 23:53:38 -0000	1.40
+++ modules/path/path.test	9 Oct 2010 08:59:46 -0000
@@ -299,6 +299,7 @@ class PathLanguageTestCase extends Drupa
 
     // Confirm that the alias is returned by url().
     drupal_static_reset('language_list');
+    drupal_static_reset('locale_url_outbound_alter');
     $languages = language_list();
     $url = url('node/' . $french_node->nid, array('language' => $languages[$french_node->language]));
     $this->assertTrue(strpos($url, $edit['path[alias]']), t('URL contains the path alias.'));
Index: modules/translation/translation.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/translation/translation.module,v
retrieving revision 1.87
diff -u -p -r1.87 translation.module
--- modules/translation/translation.module	9 Oct 2010 03:43:31 -0000	1.87
+++ modules/translation/translation.module	9 Oct 2010 09:21:26 -0000
@@ -201,19 +201,57 @@ function translation_form_node_form_alte
 /**
  * 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_view($node, $view_mode) {
+  // 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)) {
-    $path = 'node/' . $node->nid;
-    $links = language_negotiation_get_switch_links(LANGUAGE_TYPE_INTERFACE, $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);
+    $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']['#links'] += $links;
   }
 }
 
@@ -460,7 +498,8 @@ function translation_path_get_translatio
  * Replaces links with pointers to translated versions of the content.
  */
 function translation_language_switch_links_alter(array &$links, $type, $path) {
-  if ($type == LANGUAGE_TYPE_INTERFACE && $paths = translation_path_get_translations($path)) {
+  $language_type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
+  if ($type == $language_type && $paths = translation_path_get_translations($path)) {
     $path = explode('/', $path);
     $node = node_load($path[1]);
     $translations = translation_node_get_translations($node->tnid);
Index: modules/translation/translation.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/translation/translation.test,v
retrieving revision 1.31
diff -u -p -r1.31 translation.test
--- modules/translation/translation.test	9 Oct 2010 03:43:31 -0000	1.31
+++ modules/translation/translation.test	9 Oct 2010 08:59:46 -0000
@@ -14,6 +14,10 @@ class TranslationTestCase extends Drupal
 
   function setUp() {
     parent::setUp('locale', 'translation', 'translation_test');
+
+    // 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'));
   }
 
   /**
@@ -39,7 +43,13 @@ class TranslationTestCase extends Drupal
 
     $this->drupalLogout();
     $this->drupalLogin($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();
@@ -103,6 +113,32 @@ class TranslationTestCase extends Drupal
   }
 
   /**
+   * Check that content translation links behave properly.
+   */
+  function testContentTranslationLinks() {
+    // Create Basic page in English.
+    $node_title = $this->randomName();
+    $node_body = $this->randomName();
+    $node = $this->createPage($node_title, $node_body, 'en');
+
+    // 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 content translation links are shown even when no language
+    // negotiation is configured.
+    $languages = language_list();
+    $this->drupalGet("node/$node->nid");
+    $url = url("node/$node_translation->nid");
+    $this->assertContentByXPath('//a[@href=:url]', array(':url' => $url), $languages['es']->native, t('Spanish translation link found.'));
+
+    $this->drupalGet("node/$node_translation->nid");
+    $url = url("node/$node->nid");
+    $this->assertContentByXPath('//a[@href=:url]', array(':url' => $url), $languages['en']->native, t('English translation link found.'));
+  }
+
+  /**
    * Install a the specified language if it has not been already. Otherwise make sure that
    * the language is enabled.
    *
@@ -163,6 +199,42 @@ class TranslationTestCase extends Drupal
   }
 
   /**
+   * 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') {
+    $elements = $this->xpath($xpath, $arguments);
+
+    $found = TRUE;
+    if ($value && $elements) {
+      $found = FALSE;
+      foreach ($elements as $element) {
+        if ((string) $element == $value) {
+          $found = TRUE;
+          break;
+        }
+      }
+    }
+
+    return $this->assertTrue($elements && $found, $message, $group);
+  }
+
+  /**
    * Create a translation for the specified basic page in the specified language.
    *
    * @param object $node The basic page to create translation for.
