Repository: getnikola/nikola

Issue:
There's something wrong with translation links
This is a page that is written only in "es" in a blog with primary "en", viewed in the "en" pages:

![bug1](https://cloud.githubusercontent.com/assets/1579/2586008/3b2a2f48-b9f7-11e3-8e6d-ec6cf8637440.png)

That should probably not say "Also available"

---

I can't quite reprouce that, but a similar testcase  ends with a spanish-only post having a "available in english" link and no analog link in spanish. So there's things to fix.

There's something wrong with translation links
This is a page that is written only in "es" in a blog with primary "en", viewed in the "en" pages:

![bug1](https://cloud.githubusercontent.com/assets/1579/2586008/3b2a2f48-b9f7-11e3-8e6d-ec6cf8637440.png)

That should probably not say "Also available"

---

I can't quite reprouce that, but a similar testcase  ends with a spanish-only post having a "available in english" link and no analog link in spanish. So there's things to fix.



Discussion:

Resolving patch:
diff --git a/nikola/nikola.py b/nikola/nikola.py
--- a/nikola/nikola.py
+++ b/nikola/nikola.py
@@ -477,12 +477,18 @@ def __init__(self, **config):
             self.plugin_manager.activatePluginByName(plugin_info.name)
             plugin_info.plugin_object.set_site(self)
 
+        compilers = defaultdict(set)
         # Also add aliases for combinations with TRANSLATIONS_PATTERN
-        self.config['COMPILERS'] = dict([(lang, list(exts) + [
-            utils.get_translation_candidate(self.config, "f" + ext, lang)[1:]
-            for ext in exts
-            for lang in self.config['TRANSLATIONS'].keys()])
-            for lang, exts in list(self.config['COMPILERS'].items())])
+        for compiler, exts in self.config['COMPILERS'].items():
+            for ext in exts:
+                compilers[compiler].add(ext)
+                for lang in self.config['TRANSLATIONS'].keys():
+                    candidate = utils.get_translation_candidate(self.config, "f" + ext, lang)
+                    compilers[compiler].add(candidate)
+
+        # Avoid redundant compilers
+        for k, v in compilers.items():
+            self.config['COMPILERS'][k] = sorted(list(v))
 
         # Activate all required compiler plugins
         for plugin_info in self.plugin_manager.getPluginsOfCategory("PageCompiler"):
@@ -1030,27 +1036,28 @@ def scan_posts(self, really=False):
             print(".", end='', file=sys.stderr)
             dirname = os.path.dirname(wildcard)
             for dirpath, _, _ in os.walk(dirname):
-                dir_glob = os.path.join(dirpath, os.path.basename(wildcard))
                 dest_dir = os.path.normpath(os.path.join(destination,
-                                            os.path.relpath(dirpath, dirname)))
-                full_list = glob.glob(dir_glob)
-                # Now let's look for things that are not in default_lang
+                                            os.path.relpath(dirpath, dirname)))  # output/destination/foo/
+                # Get all the untranslated paths
+                dir_glob = os.path.join(dirpath, os.path.basename(wildcard))  # posts/foo/*.rst
+                untranslated = glob.glob(dir_glob)
+                # And now get all the translated paths
+                translated = set([])
                 for lang in self.config['TRANSLATIONS'].keys():
-                    lang_glob = utils.get_translation_candidate(self.config, dir_glob, lang)
-                    translated_list = glob.glob(lang_glob)
-                    # dir_glob could have put it already in full_list
-                    full_list = list(set(full_list + translated_list))
-
-                # Eliminate translations from full_list if they are not the primary,
-                # or a secondary with no primary
-                limited_list = full_list[:]
-                for fname in full_list:
-                    for lang in self.config['TRANSLATIONS'].keys():
-                        translation = utils.get_translation_candidate(self.config, fname, lang)
-                        if translation in full_list:
-                            limited_list.remove(translation)
-                full_list = limited_list
-
+                    if lang == self.config['DEFAULT_LANG']:
+                        continue
+                    lang_glob = utils.get_translation_candidate(self.config, dir_glob, lang)  # posts/foo/*.LANG.rst
+                    translated = translated.union(set(glob.glob(lang_glob)))
+                # untranslated globs like *.rst often match translated paths too, so remove them
+                # and ensure x.rst is not in the translated set
+                untranslated = set(untranslated) - translated
+
+                # also remove from translated paths that are translations of
+                # paths in untranslated_list, so x.es.rst is not in the untranslated set
+                for p in untranslated:
+                    translated = translated - set([utils.get_translation_candidate(self.config, p, l) for l in self.config['TRANSLATIONS'].keys()])
+
+                full_list = list(translated) + list(untranslated)
                 # We eliminate from the list the files inside any .ipynb folder
                 full_list = [p for p in full_list
                              if not any([x.startswith('.')
diff --git a/nikola/post.py b/nikola/post.py
--- a/nikola/post.py
+++ b/nikola/post.py
@@ -129,16 +129,13 @@ def __init__(
 
         # Load internationalized metadata
         for lang in self.translations:
+            if os.path.isfile(get_translation_candidate(self.config, self.source_path, lang)):
+                self.translated_to.add(lang)
             if lang != self.default_lang:
-                if os.path.isfile(get_translation_candidate(self.config, self.source_path, lang)):
-                    self.translated_to.add(lang)
-
                 meta = defaultdict(lambda: '')
                 meta.update(default_metadata)
                 meta.update(get_meta(self, self.config['FILE_METADATA_REGEXP'], lang))
                 self.meta[lang] = meta
-            elif os.path.isfile(self.source_path):
-                self.translated_to.add(self.default_lang)
 
         if not self.is_translation_available(self.default_lang):
             # Special case! (Issue #373)
@@ -385,10 +382,7 @@ def translated_source_path(self, lang):
 
     def translated_base_path(self, lang):
         """Return path to the translation's base_path file."""
-        if lang == self.default_lang:
-            return self.base_path
-        else:
-            return get_translation_candidate(self.config, self.base_path, lang)
+        return get_translation_candidate(self.config, self.base_path, lang)
 
     def _translated_file_path(self, lang):
         """Return path to the translation's file, or to the original."""
diff --git a/nikola/utils.py b/nikola/utils.py
--- a/nikola/utils.py
+++ b/nikola/utils.py
@@ -964,9 +964,54 @@ def get_root_dir():
 def get_translation_candidate(config, path, lang):
     """
     Return a possible path where we can find the translated version of some page
-    based on the TRANSLATIONS_PATTERN configuration variable
+    based on the TRANSLATIONS_PATTERN configuration variable.
+
+    >>> config = {'TRANSLATIONS_PATTERN': '{path}.{lang}.{ext}', 'DEFAULT_LANG': 'en', 'TRANSLATIONS': {'es':'1', 'en': 1}}
+    >>> print(get_translation_candidate(config, '*.rst', 'es'))
+    *.es.rst
+    >>> print(get_translation_candidate(config, 'fancy.post.rst', 'es'))
+    fancy.post.es.rst
+    >>> print(get_translation_candidate(config, '*.es.rst', 'es'))
+    *.es.rst
+    >>> print(get_translation_candidate(config, '*.es.rst', 'en'))
+    *.rst
+    >>> print(get_translation_candidate(config, 'cache/posts/fancy.post.es.html', 'en'))
+    cache/posts/fancy.post.html
+    >>> print(get_translation_candidate(config, 'cache/posts/fancy.post.html', 'es'))
+    cache/posts/fancy.post.es.html
+
+    >>> config = {'TRANSLATIONS_PATTERN': '{path}.{ext}.{lang}', 'DEFAULT_LANG': 'en', 'TRANSLATIONS': {'es':'1', 'en': 1}}
+    >>> print(get_translation_candidate(config, '*.rst', 'es'))
+    *.rst.es
+    >>> print(get_translation_candidate(config, '*.rst.es', 'es'))
+    *.rst.es
+    >>> print(get_translation_candidate(config, '*.rst.es', 'en'))
+    *.rst
+    >>> print(get_translation_candidate(config, 'cache/posts/fancy.post.html.es', 'en'))
+    cache/posts/fancy.post.html
+    >>> print(get_translation_candidate(config, 'cache/posts/fancy.post.html', 'es'))
+    cache/posts/fancy.post.html.es
+
     """
+    # Convert the pattern into a regexp
     pattern = config['TRANSLATIONS_PATTERN']
-    path, ext = os.path.splitext(path)
-    ext = ext[1:] if len(ext) > 0 else ext
-    return pattern.format(path=path, lang=lang, ext=ext)
+    pattern = pattern.replace('{path}', '(?P<path>.+?)')
+    pattern = pattern.replace('{ext}', '(?P<ext>[^\./]+)')
+    pattern = pattern.replace('{lang}', '(?P<lang>{0})'.format('|'.join(config['TRANSLATIONS'].keys())))
+    m = re.match(pattern, path)
+    if m and all(m.groups()):  # It's a translated path
+        p, e, l = m.group('path'), m.group('ext'), m.group('lang')
+        if l == lang:  # Nothing to do
+            return path
+        elif lang == config['DEFAULT_LANG']:  # Return untranslated path
+            return '{0}.{1}'.format(p, e)
+        else:  # Change lang and return
+            return config['TRANSLATIONS_PATTERN'].format(path=p, ext=e, lang=lang)
+    else:
+        # It's a untranslated path, assume it's path.ext
+        p, e = os.path.splitext(path)
+        e = e[1:]  # No initial dot
+        if lang == config['DEFAULT_LANG']:  # Nothing to do
+            return path
+        else:  # Change lang and return
+            return config['TRANSLATIONS_PATTERN'].format(path=p, ext=e, lang=lang)


Test patch:
diff --git a/tests/base.py b/tests/base.py
--- a/tests/base.py
+++ b/tests/base.py
@@ -205,6 +205,7 @@ def __init__(self):
             'EXTRA_PLUGINS': [],
             'DEFAULT_LANG': 'en',
             'MARKDOWN_EXTENSIONS': ['fenced_code', 'codehilite'],
+            'TRANSLATIONS_PATTERN': '{path}.{lang}.{ext}',
         }
         self.EXTRA_PLUGINS = self.config['EXTRA_PLUGINS']
         self.plugin_manager = PluginManager(categories_filter={
diff --git a/tests/test_rss_feeds.py b/tests/test_rss_feeds.py
--- a/tests/test_rss_feeds.py
+++ b/tests/test_rss_feeds.py
@@ -29,6 +29,7 @@
 fake_conf['TRANSLATIONS'] = {'en': ''}
 fake_conf['BASE_URL'] = 'http://some.blog/'
 fake_conf['BLOG_AUTHOR'] = nikola.nikola.utils.TranslatableSetting('BLOG_AUTHOR', 'Nikola Tesla')
+fake_conf['TRANSLATIONS_PATTERN'] = '{path}.{lang}.{ext}'
 
 
 class FakeCompiler(object):