Repository: readthedocs/readthedocs.org
Issue: Sitemap assumes that all versions are translated

When the project has a translation, URLs for _all the versions_ from the original one are generated and this is not true in most of the cases.

Example,

    http http://docs.python-requests.org/sitemap.xml

`latest` is the only one translated for `es`. So, the rest of alternate rel link should not be shown in the `sitemap.xml` file.

At this point we need to check that the version of the translation exists, https://github.com/rtfd/readthedocs.org/blob/1615242365ee0663fa0b744a78af8b5cb195d056/readthedocs/core/views/serve.py#L403-L412

Changed files:
readthedocs/core/views/serve.py, readthedocs/rtd_tests/tests/test_doc_serving.py

diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py
index a0212687a..610abce85 100644
--- a/readthedocs/core/views/serve.py
+++ b/readthedocs/core/views/serve.py
@@ -379,7 +379,6 @@ def sitemap_xml(request, project):
             only_active=True,
         ),
     )
-
     versions = []
     for version, priority, changefreq in zip(
             sorted_versions,
@@ -401,15 +400,18 @@ def sitemap_xml(request, project):
 
         if project.translations.exists():
             for translation in project.translations.all():
-                href = project.get_docs_url(
-                    version_slug=version.slug,
-                    lang_slug=translation.language,
-                    private=version.privacy_level == constants.PRIVATE,
-                )
-                element['languages'].append({
-                    'hreflang': translation.language,
-                    'href': href,
-                })
+                translation_versions = translation.versions.public(
+                    ).values_list('slug', flat=True)
+                if version.slug in translation_versions:
+                    href = project.get_docs_url(
+                        version_slug=version.slug,
+                        lang_slug=translation.language,
+                        private=False,
+                    )
+                    element['languages'].append({
+                        'hreflang': translation.language,
+                        'href': href,
+                    })
 
             # Add itself also as protocol requires
             element['languages'].append({
diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py
index f542c0efd..d2ed7adf1 100644
--- a/readthedocs/rtd_tests/tests/test_doc_serving.py
+++ b/readthedocs/rtd_tests/tests/test_doc_serving.py
@@ -235,6 +235,21 @@ class TestPublicDocs(BaseDocServing):
             privacy_level=constants.PRIVATE,
             project=self.public,
         )
+        not_translated_public_version = fixture.get(
+            Version,
+            identifier='not-translated-version',
+            verbose_name='not-translated-version',
+            slug='not-translated-version',
+            privacy_level=constants.PUBLIC,
+            project=self.public,
+            active=True
+        )
+        # This also creates a Version `latest` Automatically for this project
+        translation = fixture.get(
+            Project,
+            main_language_project=self.public,
+            language='translation-es'
+        )
         response = self.client.get(
             reverse('sitemap_xml'),
             HTTP_HOST='public.readthedocs.io',
@@ -260,3 +275,15 @@ class TestPublicDocs(BaseDocServing):
                 private=True,
             ),
         )
+        # The `translation` project doesn't have a version named `not-translated-version`
+        # so, the sitemap should not have a doc url for
+        # `not-translated-version` with `translation-es` language.
+        # ie: http://public.readthedocs.io/translation-es/not-translated-version/
+        self.assertNotContains(
+            response,
+            self.public.get_docs_url(
+                version_slug=not_translated_public_version.slug,
+                lang_slug=translation.language,
+                private=False,
+            ),
+        )