Repository: getnikola/nikola

Issue:
Serious build instabiliy creeping again
```
(nikola)ralsina@yoga:~/Desktop/proyectos/nikola/master/foo$ nikola build
Scanning posts....done!
.  render_archive:output/archive.html
.  render_archive:output/es/archive.html
(nikola)ralsina@yoga:~/Desktop/proyectos/nikola/master/foo$ nikola build
Scanning posts....done!
.  render_listings:output/listings/index.html
.  render_listings:output/listings/hello.py.html
.  render_tags:output/categories/index.html
.  render_tags:output/es/categories/index.html
(nikola)ralsina@yoga:~/Desktop/proyectos/nikola/master/foo$ nikola build
Scanning posts....done!
.  render_galleries:output/galleries
.  render_galleries:output/galleries/demo
.  render_galleries:output/galleries/index.html
.  render_galleries:output/galleries/rss.xml
.  render_galleries:output/es/galleries/index.html
(nikola)ralsina@yoga:~/Desktop/proyectos/nikola/master/foo$ nikola build
Scanning posts....done!
.  render_galleries:output/es/galleries/rss.xml
.  render_galleries:cache/galleries/demo/index.html
.  render_galleries:cache/galleries/demo/index.es.html
.  render_galleries:output/es/galleries/demo/index.html
(nikola)ralsina@yoga:~/Desktop/proyectos/nikola/master/foo$ nikola build
Scanning posts....done!
.  render_galleries:output/es/galleries/demo/rss.xml
.  render_tags:output/categories/demo.html
.  render_indexes:output/index.html
.  render_tags:output/categories/blog.html
.  render_tags:output/categories/nikola.html
.  render_tags:output/categories/python.html
.  render_tags:output/es/categories/demo.html
(nikola)ralsina@yoga:~/Desktop/proyectos/nikola/master/foo$ nikola build
Scanning posts....done!
.  render_tags:output/es/categories/blog.html
(nikola)ralsina@yoga:~/Desktop/proyectos/nikola/master/foo$ nikola build
Scanning posts....done!
.  render_tags:output/es/categories/python.html
```

This is master on a multilingual test site



Discussion:
First suspect: TranslatableSettings. It changes between runs, here's what it looks like on 1st run (only parts that change):

```
"navigation_links": "<TranslatableSetting> en=((u'/archive.html',
u'Archives'), (u'/categories/index.html', u'Tags'), (u'/rss.xml', u'RSS
feed')), es=((u'/archive.html', u'Archives'), (u'/categories/index.html', u'Tags'),
(u'/rss.xml', u'RSS 
feed'))"
```

And here's the second one:

```
"navigation_links": "<TranslatableSetting> en=((u'/archive.html',
u'Archives'), (u'/categories/index.html', u'Tags'), (u'/rss.xml', u'RSS
feed'))"
```

So it looks like sometimes it has both translations and sometimes it doesn't.

Seems to me that TranslatableSetting is trying to be too smart by evaluating Lazily. Any comments, @Kwpolska ?

@ralsina TranslatableSettings are kinda hacky.  What does your conf.py say in NAVIGATION_LINKS?

It says

```
NAVIGATION_LINKS = {
    DEFAULT_LANG: (
        ("/archive.html", "Archives"),
        ("/categories/index.html", "Tags"),
        ("/rss.xml", "RSS feed"),
    ),
}
```

Which of course could make it go nuts. Maybe we should just copy to all TRANSLATIONS?

Alternative is to make **repr**() just reflect the input of TranslatableSettings and not the current state.

We should.  I was actually kinda confused by it going `en=…`, but then I remembered how NAVIGATION_LINKS work.  I believe I have a fix, sort of.


Resolving patch:
diff --git a/nikola/nikola.py b/nikola/nikola.py
--- a/nikola/nikola.py
+++ b/nikola/nikola.py
@@ -295,6 +295,8 @@ def __init__(self, **config):
             self.config['NAVIGATION_LINKS'] = {self.config['DEFAULT_LANG']: ()}
 
         # Translatability configuration.
+        self.config['TRANSLATIONS'] = self.config.get('TRANSLATIONS',
+                                                      {self.config['DEFAULT_LANG']: ''})
         utils.TranslatableSetting.default_lang = self.config['DEFAULT_LANG']
 
         self.TRANSLATABLE_SETTINGS = ('BLOG_AUTHOR',
@@ -323,7 +325,7 @@ def __init__(self, **config):
 
         for i in self.TRANSLATABLE_SETTINGS:
             try:
-                self.config[i] = utils.TranslatableSetting(i, self.config[i])
+                self.config[i] = utils.TranslatableSetting(i, self.config[i], self.config['TRANSLATIONS'])
             except KeyError:
                 pass
 
@@ -380,9 +382,6 @@ def __init__(self, **config):
         if not self.config.get('COPY_SOURCES'):
             self.config['SHOW_SOURCELINK'] = False
 
-        self.config['TRANSLATIONS'] = self.config.get('TRANSLATIONS',
-                                                      {self.config['DEFAULT_LANG']: ''})
-
         self.default_lang = self.config['DEFAULT_LANG']
         self.translations = self.config['TRANSLATIONS']
 
diff --git a/nikola/utils.py b/nikola/utils.py
--- a/nikola/utils.py
+++ b/nikola/utils.py
@@ -228,7 +228,7 @@ def __getattribute__(self, attr):
     def __dir__(self):
         return list(set(self.__dict__).union(set(dir(str))))
 
-    def __init__(self, name, inp):
+    def __init__(self, name, inp, translations):
         """Initialize a translated setting.
 
         Valid inputs include:
@@ -240,6 +240,7 @@ def __init__(self, name, inp):
         """
         self.name = name
         self._inp = inp
+        self.translations = translations
         self.overriden_default = False
         self.values = defaultdict()
 
@@ -250,6 +251,9 @@ def __init__(self, name, inp):
                 self.default_lang = list(self.values.keys())[0]
                 self.overridden_default = True
             self.values.default_factory = lambda: self.values[self.default_lang]
+            for k in translations.keys():
+                if k not in self.values.keys():
+                    self.values[k] = inp[self.default_lang]
         else:
             self.translated = False
             self.values[self.default_lang] = inp


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -240,7 +240,7 @@ class TranslatableSettingsTest(unittest.TestCase):
     def test_string_input(self):
         """Tests for string input."""
         inp = 'Fancy Blog'
-        S = TranslatableSetting('S', inp)
+        S = TranslatableSetting('S', inp, {'xx': ''})
         S.default_lang = 'xx'
         S.lang = 'xx'
 
@@ -265,7 +265,7 @@ def test_dict_input(self):
         inp = {'xx': 'Fancy Blog',
                'zz': 'Schmancy Blog'}
 
-        S = TranslatableSetting('S', inp)
+        S = TranslatableSetting('S', inp, {'xx': '', 'zz': ''})
         S.default_lang = 'xx'
         S.lang = 'xx'
 
@@ -290,7 +290,7 @@ def test_dict_input_lang(self):
         inp = {'xx': 'Fancy Blog',
                'zz': 'Schmancy Blog'}
 
-        S = TranslatableSetting('S', inp)
+        S = TranslatableSetting('S', inp, {'xx': '', 'zz': ''})
         S.default_lang = 'xx'
         S.lang = 'xx'
 
@@ -321,82 +321,6 @@ def test_dict_input_lang(self):
         self.assertEqual(inp['zz'], u)
         self.assertEqual(inp['zz'], cn)
 
-    def test_dict_input_default_lang(self):
-        """Test dict input, with a default language change along the way."""
-        inp = {'xx': 'Fancy Blog',
-               'zz': 'Schmancy Blog'}
-
-        S = TranslatableSetting('S', inp)
-        S.default_lang = 'xx'
-        S.lang = 'xx'
-
-        try:
-            u = unicode(S)
-        except NameError:  # Python 3
-            u = str(S)
-
-        cn = S()
-
-        self.assertEqual(inp['xx'], u)
-        self.assertEqual(inp['xx'], cn)
-
-        # Change the default language.
-        # WARNING: DO NOT set default_lang locally in real code!
-        #          Set it globally instead!
-        S.default_lang = 'zz'
-
-        try:
-            u = unicode(S)
-        except NameError:  # Python 3
-            u = str(S)
-
-        cn = S()
-        cf = S('ff')
-
-        self.assertEqual(inp['xx'], u)
-        self.assertEqual(inp['xx'], cn)
-        self.assertEqual(inp['zz'], cf)
-
-    def test_dict_input_both_langs(self):
-        """Test dict input, with both languages changed along the way."""
-        inp = {'xx': 'Fancy Blog',
-               'yy': 'Dancy Blog',
-               'zz': 'Schmancy Blog'}
-
-        S = TranslatableSetting('S', inp)
-        S.default_lang = 'xx'
-        S.lang = 'xx'
-
-        try:
-            u = unicode(S)
-        except NameError:  # Python 3
-            u = str(S)
-
-        cn = S()
-
-        self.assertEqual(inp['xx'], u)
-        self.assertEqual(inp['xx'], cn)
-
-        # Change the default language.
-        # WARNING: DO NOT set those locally in real code!
-        #          Set it globally instead!
-        # WARNING: TranslatableSetting.lang is used to override the current
-        #          locale settings returned by LocaleBorg!  Use with care!
-        S.default_lang = 'zz'
-        S.lang = 'yy'
-
-        try:
-            u = unicode(S)
-        except NameError:  # Python 3
-            u = str(S)
-
-        cn = S()
-        cf = S('ff')
-
-        self.assertEqual(inp['yy'], u)
-        self.assertEqual(inp['yy'], cn)
-        self.assertEqual(inp['zz'], cf)
-
 
 if __name__ == '__main__':
     unittest.main()