diff --git a/commands/server.go b/commands/server.go index 0ae58c99163..f2522aaf7be 100644 --- a/commands/server.go +++ b/commands/server.go @@ -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 diff --git a/config/configProvider.go b/config/configProvider.go index 870341f7f93..471ce9a1d49 100644 --- a/config/configProvider.go +++ b/config/configProvider.go @@ -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 diff --git a/helpers/language.go b/helpers/language.go index fa933fddd76..934c82de065 100644 --- a/helpers/language.go +++ b/helpers/language.go @@ -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. // diff --git a/hugolib/config.go b/hugolib/config.go index fe3a64f2af2..c30e93f15ca 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -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 diff --git a/hugolib/fileInfo.go b/hugolib/fileInfo.go index 582d2be8c22..b146aede9a7 100644 --- a/hugolib/fileInfo.go +++ b/hugolib/fileInfo.go @@ -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 } diff --git a/hugolib/page_bundler_capture.go b/hugolib/page_bundler_capture.go index 34a1be5fbd3..4d8f39fb753 100644 --- a/hugolib/page_bundler_capture.go +++ b/hugolib/page_bundler_capture.go @@ -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 { diff --git a/hugolib/page_bundler_capture_test.go b/hugolib/page_bundler_capture_test.go index 176f752e036..a7a7054b40e 100644 --- a/hugolib/page_bundler_capture_test.go +++ b/hugolib/page_bundler_capture_test.go @@ -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 diff --git a/hugolib/page_bundler_test.go b/hugolib/page_bundler_test.go index 474f6676deb..cef1e0239b8 100644 --- a/hugolib/page_bundler_test.go +++ b/hugolib/page_bundler_test.go @@ -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) diff --git a/hugolib/site.go b/hugolib/site.go index 0dbf84a0703..55eb6ae7205 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -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. diff --git a/source/sourceSpec.go b/source/sourceSpec.go index 74a754a2684..e40010162f3 100644 --- a/source/sourceSpec.go +++ b/source/sourceSpec.go @@ -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 {