Add a way to disable one or more languages
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 25 Jan 2018 16:03:29 +0000 (17:03 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 26 Jan 2018 13:04:14 +0000 (14:04 +0100)
This commit adds a new config setting:

```toml
disableLanguages = ["fr"]
```

If this is a multilingual site:

* No site for the French language will be created
* French content pages will be ignored/not read
* The French language configuration (menus etc.) will also be ignored

This makes it possible to start translating new languages and turn it on when you're happy etc.

Fixes #4297
Fixed #4329

commands/server.go
config/configProvider.go
helpers/language.go
hugolib/config.go
hugolib/fileInfo.go
hugolib/page_bundler_capture.go
hugolib/page_bundler_capture_test.go
hugolib/page_bundler_test.go
hugolib/site.go
source/sourceSpec.go

index 0ae58c99163cc8ba254b2caf1ae1325f70c4cf58..f2522aaf7bee48c56de234f9d13a8180cb4ff9df 100644 (file)
@@ -173,20 +173,23 @@ func server(cmd *cobra.Command, args []string) error {
                        c.Set("liveReloadPort", serverPorts[0])
                }
 
-               if c.languages.IsMultihost() {
-                       for i, language := range c.languages {
-                               baseURL, err := fixURL(language, baseURL, serverPorts[i])
-                               if err != nil {
-                                       return err
-                               }
-                               language.Set("baseURL", baseURL)
+               isMultiHost := c.languages.IsMultihost()
+               for i, language := range c.languages {
+                       var serverPort int
+                       if isMultiHost {
+                               serverPort = serverPorts[i]
+                       } else {
+                               serverPort = serverPorts[0]
                        }
-               } else {
-                       baseURL, err := fixURL(c.Cfg, baseURL, serverPorts[0])
+
+                       baseURL, err := fixURL(language, baseURL, serverPort)
                        if err != nil {
                                return err
                        }
-                       c.Set("baseURL", baseURL)
+                       language.Set("baseURL", baseURL)
+                       if i == 0 {
+                               c.Set("baseURL", baseURL)
+                       }
                }
 
                return nil
index 870341f7f935a686f02bf349ffe5bc5bd68e3c5c..471ce9a1d496b311697682c07560cfe1b8cfed09 100644 (file)
@@ -20,6 +20,7 @@ type Provider interface {
        GetBool(key string) bool
        GetStringMap(key string) map[string]interface{}
        GetStringMapString(key string) map[string]string
+       GetStringSlice(key string) []string
        Get(key string) interface{}
        Set(key string, value interface{})
        IsSet(key string) bool
index fa933fddd7692925b1eaa5fd3d61944adffa7607..934c82de0656e8e07360c2fdf01c5eaca24d1b9a 100644 (file)
@@ -140,6 +140,11 @@ func (l *Language) GetStringMapString(key string) map[string]string {
        return cast.ToStringMapString(l.Get(key))
 }
 
+//  returns the value associated with the key as a slice of strings.
+func (l *Language) GetStringSlice(key string) []string {
+       return cast.ToStringSlice(l.Get(key))
+}
+
 // Get returns a value associated with the key relying on specified language.
 // Get is case-insensitive for a key.
 //
index fe3a64f2af26083ac82aa153f80f0a9d1f84cf20..c30e93f15cae99c264b6c512ed0c7370d61d7357 100644 (file)
@@ -72,16 +72,46 @@ func LoadConfig(fs afero.Fs, relativeSourcePath, configFilename string) (*viper.
 }
 
 func loadLanguageSettings(cfg config.Provider, oldLangs helpers.Languages) error {
-       multilingual := cfg.GetStringMap("languages")
+
+       defaultLang := cfg.GetString("defaultContentLanguage")
+
+       var languages map[string]interface{}
+
+       languagesFromConfig := cfg.GetStringMap("languages")
+       disableLanguages := cfg.GetStringSlice("disableLanguages")
+
+       if len(disableLanguages) == 0 {
+               languages = languagesFromConfig
+       } else {
+               languages = make(map[string]interface{})
+               for k, v := range languagesFromConfig {
+                       isDisabled := false
+                       for _, disabled := range disableLanguages {
+                               if disabled == defaultLang {
+                                       return fmt.Errorf("cannot disable default language %q", defaultLang)
+                               }
+
+                               if strings.EqualFold(k, disabled) {
+                                       isDisabled = true
+                                       break
+                               }
+                       }
+                       if !isDisabled {
+                               languages[k] = v
+                       }
+
+               }
+       }
+
        var (
                langs helpers.Languages
                err   error
        )
 
-       if len(multilingual) == 0 {
+       if len(languages) == 0 {
                langs = append(langs, helpers.NewDefaultLanguage(cfg))
        } else {
-               langs, err = toSortedLanguages(cfg, multilingual)
+               langs, err = toSortedLanguages(cfg, languages)
                if err != nil {
                        return fmt.Errorf("Failed to parse multilingual config: %s", err)
                }
@@ -114,8 +144,6 @@ func loadLanguageSettings(cfg config.Provider, oldLangs helpers.Languages) error
                }
        }
 
-       defaultLang := cfg.GetString("defaultContentLanguage")
-
        // The defaultContentLanguage is something the user has to decide, but it needs
        // to match a language in the language definition list.
        langExists := false
index 582d2be8c22d44580b59e4b07c2323a85aeb5424..b146aede9a763e7aeaaf3a6af84c908f7e5bfdb7 100644 (file)
@@ -31,6 +31,9 @@ type fileInfo struct {
        bundleTp bundleDirType
        source.ReadableFile
        overriddenLang string
+
+       // Set if the content language for this file is disabled.
+       disabled bool
 }
 
 func (fi *fileInfo) Lang() string {
@@ -60,6 +63,9 @@ func newFileInfo(sp *source.SourceSpec, baseDir, filename string, fi os.FileInfo
                ReadableFile: baseFi,
        }
 
+       lang := f.Lang()
+       f.disabled = lang != "" && sp.DisabledLanguages[lang]
+
        return f
 
 }
index 34a1be5fbd37fc57159b1d326b4977252257376f..4d8f39fb753b4231fa7eec3fdae0f5c1455aa330 100644 (file)
@@ -149,8 +149,10 @@ func (c *capturer) capturePartial(filenames ...string) error {
                        // create the proper mapping for it.
                        c.getRealFileInfo(dir)
 
-                       f := c.newFileInfo(resolvedFilename, fi, tp)
-                       c.copyOrHandleSingle(f)
+                       f, active := c.newFileInfo(resolvedFilename, fi, tp)
+                       if active {
+                               c.copyOrHandleSingle(f)
+                       }
                }
        }
 
@@ -228,7 +230,10 @@ func (c *capturer) handleBranchDir(dirname string) error {
 
                tp, isContent := classifyBundledFile(fi.Name())
 
-               f := c.newFileInfo(fi.filename, fi.FileInfo, tp)
+               f, active := c.newFileInfo(fi.filename, fi.FileInfo, tp)
+               if !active {
+                       continue
+               }
                if f.isOwner() {
                        dirs.addBundleHeader(f)
                } else if !isContent {
@@ -309,7 +314,7 @@ func (c *capturer) handleDir(dirname string) error {
                return c.handleNonBundle(dirname, files, state == dirStateSinglesOnly)
        }
 
-       var fileInfos = make([]*fileInfo, len(files))
+       var fileInfos = make([]*fileInfo, 0, len(files))
 
        for i, fi := range files {
                currentType := bundleNot
@@ -324,8 +329,12 @@ func (c *capturer) handleDir(dirname string) error {
                if bundleType == bundleNot && currentType != bundleNot {
                        bundleType = currentType
                }
+               f, active := c.newFileInfo(fi.filename, fi.FileInfo, currentType)
+               if !active {
+                       continue
+               }
 
-               fileInfos[i] = c.newFileInfo(fi.filename, fi.FileInfo, currentType)
+               fileInfos = append(fileInfos, f)
        }
 
        var todo []*fileInfo
@@ -377,8 +386,11 @@ func (c *capturer) handleNonBundle(
                        }
                } else {
                        if singlesOnly {
-                               file := c.newFileInfo(fi.filename, fi, bundleNot)
-                               c.handler.handleSingles(file)
+                               f, active := c.newFileInfo(fi.filename, fi, bundleNot)
+                               if !active {
+                                       continue
+                               }
+                               c.handler.handleSingles(f)
                        } else {
                                c.handler.handleCopyFiles(fi.filename)
                        }
@@ -462,7 +474,10 @@ func (c *capturer) collectFiles(dirname string, handleFiles func(fis ...*fileInf
                                return err
                        }
                } else {
-                       handleFiles(c.newFileInfo(fi.filename, fi.FileInfo, bundleNot))
+                       f, active := c.newFileInfo(fi.filename, fi.FileInfo, bundleNot)
+                       if active {
+                               handleFiles(f)
+                       }
                }
        }
 
@@ -506,8 +521,9 @@ func (c *capturer) readDir(dirname string) ([]fileInfoName, error) {
        return fis, nil
 }
 
-func (c *capturer) newFileInfo(filename string, fi os.FileInfo, tp bundleDirType) *fileInfo {
-       return newFileInfo(c.sourceSpec, c.baseDir, filename, fi, tp)
+func (c *capturer) newFileInfo(filename string, fi os.FileInfo, tp bundleDirType) (*fileInfo, bool) {
+       f := newFileInfo(c.sourceSpec, c.baseDir, filename, fi, tp)
+       return f, !f.disabled
 }
 
 type fileInfoName struct {
index 176f752e0366519e7f23bb5a34804d97a0c7ba63..a7a7054b40ec35054287c5c2851bd16318eaef72 100644 (file)
@@ -174,6 +174,7 @@ func TestPageBundlerCaptureMultilingual(t *testing.T) {
        expected := `
 F:
 /work/base/1s/mypage.md
+/work/base/1s/mypage.nn.md
 /work/base/bb/_1.md
 /work/base/bb/_1.nn.md
 /work/base/bb/en.md
index 474f6676deb04021ef14a04b79b88be1bba695e4..cef1e0239b8b2505c6ad11ace694084f6d8cb214 100644 (file)
@@ -192,6 +192,10 @@ func TestPageBundlerSiteMultilingual(t *testing.T) {
 
                                s := sites.Sites[0]
 
+                               assert.Equal(8, len(s.RegularPages))
+                               assert.Equal(18, len(s.Pages))
+                               assert.Equal(35, len(s.AllPages))
+
                                bundleWithSubPath := s.getPage(KindPage, "lb/index")
                                assert.NotNil(bundleWithSubPath)
 
@@ -214,6 +218,8 @@ func TestPageBundlerSiteMultilingual(t *testing.T) {
                                assert.Equal(bfBundle, s.getPage(KindPage, "my-bf-bundle"))
 
                                nnSite := sites.Sites[1]
+                               assert.Equal(7, len(nnSite.RegularPages))
+
                                bfBundleNN := nnSite.getPage(KindPage, "bf/my-bf-bundle/index")
                                assert.NotNil(bfBundleNN)
                                assert.Equal("nn", bfBundleNN.Lang())
@@ -233,6 +239,48 @@ func TestPageBundlerSiteMultilingual(t *testing.T) {
        }
 }
 
+func TestMultilingualDisableDefaultLanguage(t *testing.T) {
+       t.Parallel()
+
+       assert := require.New(t)
+       cfg, _ := newTestBundleSourcesMultilingual(t)
+
+       cfg.Set("disableLanguages", []string{"en"})
+
+       err := loadDefaultSettingsFor(cfg)
+       assert.Error(err)
+       assert.Contains(err.Error(), "cannot disable default language")
+}
+
+func TestMultilingualDisableLanguage(t *testing.T) {
+       t.Parallel()
+
+       assert := require.New(t)
+       cfg, fs := newTestBundleSourcesMultilingual(t)
+       cfg.Set("disableLanguages", []string{"nn"})
+
+       assert.NoError(loadDefaultSettingsFor(cfg))
+       sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
+       assert.NoError(err)
+       assert.Equal(1, len(sites.Sites))
+
+       assert.NoError(sites.Build(BuildCfg{}))
+
+       s := sites.Sites[0]
+
+       assert.Equal(8, len(s.RegularPages))
+       assert.Equal(18, len(s.Pages))
+       // No nn pages
+       assert.Equal(18, len(s.AllPages))
+       for _, p := range s.rawAllPages {
+               assert.True(p.Lang() != "nn")
+       }
+       for _, p := range s.AllPages {
+               assert.True(p.Lang() != "nn")
+       }
+
+}
+
 func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) {
        assert := require.New(t)
        cfg, fs, workDir := newTestBundleSymbolicSources(t)
@@ -509,6 +557,7 @@ TheContent.
        writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), layout)
 
        writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mypage.md"), pageContent)
+       writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mypage.nn.md"), pageContent)
        writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mylogo.png"), "content")
 
        writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.md"), pageContent)
index 0dbf84a070344fa4f4770a79c0e667ebbf827253..55eb6ae720542a4a77060f5b6c4be6155a770689 100644 (file)
@@ -1207,30 +1207,28 @@ func (s *Site) checkDirectories() (err error) {
 }
 
 type contentCaptureResultHandler struct {
-       contentProcessors map[string]*siteContentProcessor
+       defaultContentProcessor *siteContentProcessor
+       contentProcessors       map[string]*siteContentProcessor
+}
+
+func (c *contentCaptureResultHandler) getContentProcessor(lang string) *siteContentProcessor {
+       proc, found := c.contentProcessors[lang]
+       if found {
+               return proc
+       }
+       return c.defaultContentProcessor
 }
 
 func (c *contentCaptureResultHandler) handleSingles(fis ...*fileInfo) {
        for _, fi := range fis {
-               // May be connected to a language (content files)
-               proc, found := c.contentProcessors[fi.Lang()]
-               if !found {
-                       panic("proc not found")
-               }
+               proc := c.getContentProcessor(fi.Lang())
                proc.fileSinglesChan <- fi
-
        }
 }
 func (c *contentCaptureResultHandler) handleBundles(d *bundleDirs) {
        for _, b := range d.bundles {
-               lang := b.fi.Lang()
-
-               proc, found := c.contentProcessors[lang]
-               if !found {
-                       panic("proc not found")
-               }
+               proc := c.getContentProcessor(b.fi.Lang())
                proc.fileBundlesChan <- b
-
        }
 }
 
@@ -1247,13 +1245,17 @@ func (s *Site) readAndProcessContent(filenames ...string) error {
 
        sourceSpec := source.NewSourceSpec(s.owner.Cfg, s.Fs)
        baseDir := s.absContentDir()
+       defaultContentLanguage := s.SourceSpec.DefaultContentLanguage
 
        contentProcessors := make(map[string]*siteContentProcessor)
+       var defaultContentProcessor *siteContentProcessor
        sites := s.owner.langSite()
        for k, v := range sites {
                proc := newSiteContentProcessor(baseDir, len(filenames) > 0, v)
                contentProcessors[k] = proc
-
+               if k == defaultContentLanguage {
+                       defaultContentProcessor = proc
+               }
                g.Go(func() error {
                        return proc.process(ctx)
                })
@@ -1264,7 +1266,7 @@ func (s *Site) readAndProcessContent(filenames ...string) error {
                bundleMap *contentChangeMap
        )
 
-       mainHandler := &contentCaptureResultHandler{contentProcessors: contentProcessors}
+       mainHandler := &contentCaptureResultHandler{contentProcessors: contentProcessors, defaultContentProcessor: defaultContentProcessor}
 
        if s.running() {
                // Need to track changes.
index 74a754a2684d334727d0485473e5305277ce2250..e40010162f37f92155cf938cf55d2cd947fd9c4a 100644 (file)
@@ -35,6 +35,7 @@ type SourceSpec struct {
 
        Languages              map[string]interface{}
        DefaultContentLanguage string
+       DisabledLanguages      map[string]bool
 }
 
 // NewSourceSpec initializes SourceSpec using languages from a given configuration.
@@ -42,6 +43,12 @@ func NewSourceSpec(cfg config.Provider, fs *hugofs.Fs) *SourceSpec {
        defaultLang := cfg.GetString("defaultContentLanguage")
        languages := cfg.GetStringMap("languages")
 
+       disabledLangsSet := make(map[string]bool)
+
+       for _, disabledLang := range cfg.GetStringSlice("disableLanguages") {
+               disabledLangsSet[disabledLang] = true
+       }
+
        if len(languages) == 0 {
                l := helpers.NewDefaultLanguage(cfg)
                languages[l.Lang] = l
@@ -62,7 +69,7 @@ func NewSourceSpec(cfg config.Provider, fs *hugofs.Fs) *SourceSpec {
                }
        }
 
-       return &SourceSpec{ignoreFilesRe: regexps, Cfg: cfg, Fs: fs, Languages: languages, DefaultContentLanguage: defaultLang}
+       return &SourceSpec{ignoreFilesRe: regexps, Cfg: cfg, Fs: fs, Languages: languages, DefaultContentLanguage: defaultLang, DisabledLanguages: disabledLangsSet}
 }
 
 func (s *SourceSpec) IgnoreFile(filename string) bool {