Add a way to disable one or more languages

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
This commit is contained in:
Bjørn Erik Pedersen 2018-01-25 17:03:29 +01:00
parent 322c567220
commit 6413559f75
10 changed files with 160 additions and 42 deletions

View file

@ -173,21 +173,24 @@ func server(cmd *cobra.Command, args []string) error {
c.Set("liveReloadPort", serverPorts[0]) c.Set("liveReloadPort", serverPorts[0])
} }
if c.languages.IsMultihost() { isMultiHost := c.languages.IsMultihost()
for i, language := range c.languages { for i, language := range c.languages {
baseURL, err := fixURL(language, baseURL, serverPorts[i]) var serverPort int
if isMultiHost {
serverPort = serverPorts[i]
} else {
serverPort = serverPorts[0]
}
baseURL, err := fixURL(language, baseURL, serverPort)
if err != nil { if err != nil {
return err return err
} }
language.Set("baseURL", baseURL) language.Set("baseURL", baseURL)
} if i == 0 {
} else {
baseURL, err := fixURL(c.Cfg, baseURL, serverPorts[0])
if err != nil {
return err
}
c.Set("baseURL", baseURL) c.Set("baseURL", baseURL)
} }
}
return nil return nil

View file

@ -20,6 +20,7 @@ type Provider interface {
GetBool(key string) bool GetBool(key string) bool
GetStringMap(key string) map[string]interface{} GetStringMap(key string) map[string]interface{}
GetStringMapString(key string) map[string]string GetStringMapString(key string) map[string]string
GetStringSlice(key string) []string
Get(key string) interface{} Get(key string) interface{}
Set(key string, value interface{}) Set(key string, value interface{})
IsSet(key string) bool IsSet(key string) bool

View file

@ -140,6 +140,11 @@ func (l *Language) GetStringMapString(key string) map[string]string {
return cast.ToStringMapString(l.Get(key)) 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 returns a value associated with the key relying on specified language.
// Get is case-insensitive for a key. // Get is case-insensitive for a key.
// //

View file

@ -72,16 +72,46 @@ func LoadConfig(fs afero.Fs, relativeSourcePath, configFilename string) (*viper.
} }
func loadLanguageSettings(cfg config.Provider, oldLangs helpers.Languages) error { 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 ( var (
langs helpers.Languages langs helpers.Languages
err error err error
) )
if len(multilingual) == 0 { if len(languages) == 0 {
langs = append(langs, helpers.NewDefaultLanguage(cfg)) langs = append(langs, helpers.NewDefaultLanguage(cfg))
} else { } else {
langs, err = toSortedLanguages(cfg, multilingual) langs, err = toSortedLanguages(cfg, languages)
if err != nil { if err != nil {
return fmt.Errorf("Failed to parse multilingual config: %s", err) 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 // The defaultContentLanguage is something the user has to decide, but it needs
// to match a language in the language definition list. // to match a language in the language definition list.
langExists := false langExists := false

View file

@ -31,6 +31,9 @@ type fileInfo struct {
bundleTp bundleDirType bundleTp bundleDirType
source.ReadableFile source.ReadableFile
overriddenLang string overriddenLang string
// Set if the content language for this file is disabled.
disabled bool
} }
func (fi *fileInfo) Lang() string { func (fi *fileInfo) Lang() string {
@ -60,6 +63,9 @@ func newFileInfo(sp *source.SourceSpec, baseDir, filename string, fi os.FileInfo
ReadableFile: baseFi, ReadableFile: baseFi,
} }
lang := f.Lang()
f.disabled = lang != "" && sp.DisabledLanguages[lang]
return f return f
} }

View file

@ -149,10 +149,12 @@ func (c *capturer) capturePartial(filenames ...string) error {
// create the proper mapping for it. // create the proper mapping for it.
c.getRealFileInfo(dir) c.getRealFileInfo(dir)
f := c.newFileInfo(resolvedFilename, fi, tp) f, active := c.newFileInfo(resolvedFilename, fi, tp)
if active {
c.copyOrHandleSingle(f) c.copyOrHandleSingle(f)
} }
} }
}
return nil return nil
} }
@ -228,7 +230,10 @@ func (c *capturer) handleBranchDir(dirname string) error {
tp, isContent := classifyBundledFile(fi.Name()) 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() { if f.isOwner() {
dirs.addBundleHeader(f) dirs.addBundleHeader(f)
} else if !isContent { } else if !isContent {
@ -309,7 +314,7 @@ func (c *capturer) handleDir(dirname string) error {
return c.handleNonBundle(dirname, files, state == dirStateSinglesOnly) 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 { for i, fi := range files {
currentType := bundleNot currentType := bundleNot
@ -324,8 +329,12 @@ func (c *capturer) handleDir(dirname string) error {
if bundleType == bundleNot && currentType != bundleNot { if bundleType == bundleNot && currentType != bundleNot {
bundleType = currentType 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 var todo []*fileInfo
@ -377,8 +386,11 @@ func (c *capturer) handleNonBundle(
} }
} else { } else {
if singlesOnly { if singlesOnly {
file := c.newFileInfo(fi.filename, fi, bundleNot) f, active := c.newFileInfo(fi.filename, fi, bundleNot)
c.handler.handleSingles(file) if !active {
continue
}
c.handler.handleSingles(f)
} else { } else {
c.handler.handleCopyFiles(fi.filename) c.handler.handleCopyFiles(fi.filename)
} }
@ -462,7 +474,10 @@ func (c *capturer) collectFiles(dirname string, handleFiles func(fis ...*fileInf
return err return err
} }
} else { } 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 return fis, nil
} }
func (c *capturer) newFileInfo(filename string, fi os.FileInfo, tp bundleDirType) *fileInfo { func (c *capturer) newFileInfo(filename string, fi os.FileInfo, tp bundleDirType) (*fileInfo, bool) {
return newFileInfo(c.sourceSpec, c.baseDir, filename, fi, tp) f := newFileInfo(c.sourceSpec, c.baseDir, filename, fi, tp)
return f, !f.disabled
} }
type fileInfoName struct { type fileInfoName struct {

View file

@ -174,6 +174,7 @@ func TestPageBundlerCaptureMultilingual(t *testing.T) {
expected := ` expected := `
F: F:
/work/base/1s/mypage.md /work/base/1s/mypage.md
/work/base/1s/mypage.nn.md
/work/base/bb/_1.md /work/base/bb/_1.md
/work/base/bb/_1.nn.md /work/base/bb/_1.nn.md
/work/base/bb/en.md /work/base/bb/en.md

View file

@ -192,6 +192,10 @@ func TestPageBundlerSiteMultilingual(t *testing.T) {
s := sites.Sites[0] 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") bundleWithSubPath := s.getPage(KindPage, "lb/index")
assert.NotNil(bundleWithSubPath) assert.NotNil(bundleWithSubPath)
@ -214,6 +218,8 @@ func TestPageBundlerSiteMultilingual(t *testing.T) {
assert.Equal(bfBundle, s.getPage(KindPage, "my-bf-bundle")) assert.Equal(bfBundle, s.getPage(KindPage, "my-bf-bundle"))
nnSite := sites.Sites[1] nnSite := sites.Sites[1]
assert.Equal(7, len(nnSite.RegularPages))
bfBundleNN := nnSite.getPage(KindPage, "bf/my-bf-bundle/index") bfBundleNN := nnSite.getPage(KindPage, "bf/my-bf-bundle/index")
assert.NotNil(bfBundleNN) assert.NotNil(bfBundleNN)
assert.Equal("nn", bfBundleNN.Lang()) 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) { func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) {
assert := require.New(t) assert := require.New(t)
cfg, fs, workDir := newTestBundleSymbolicSources(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, "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.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", "1s", "mylogo.png"), "content")
writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.md"), pageContent) writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.md"), pageContent)

View file

@ -1207,30 +1207,28 @@ func (s *Site) checkDirectories() (err error) {
} }
type contentCaptureResultHandler struct { type contentCaptureResultHandler struct {
defaultContentProcessor *siteContentProcessor
contentProcessors map[string]*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) { func (c *contentCaptureResultHandler) handleSingles(fis ...*fileInfo) {
for _, fi := range fis { for _, fi := range fis {
// May be connected to a language (content files) proc := c.getContentProcessor(fi.Lang())
proc, found := c.contentProcessors[fi.Lang()]
if !found {
panic("proc not found")
}
proc.fileSinglesChan <- fi proc.fileSinglesChan <- fi
} }
} }
func (c *contentCaptureResultHandler) handleBundles(d *bundleDirs) { func (c *contentCaptureResultHandler) handleBundles(d *bundleDirs) {
for _, b := range d.bundles { for _, b := range d.bundles {
lang := b.fi.Lang() proc := c.getContentProcessor(b.fi.Lang())
proc, found := c.contentProcessors[lang]
if !found {
panic("proc not found")
}
proc.fileBundlesChan <- b proc.fileBundlesChan <- b
} }
} }
@ -1247,13 +1245,17 @@ func (s *Site) readAndProcessContent(filenames ...string) error {
sourceSpec := source.NewSourceSpec(s.owner.Cfg, s.Fs) sourceSpec := source.NewSourceSpec(s.owner.Cfg, s.Fs)
baseDir := s.absContentDir() baseDir := s.absContentDir()
defaultContentLanguage := s.SourceSpec.DefaultContentLanguage
contentProcessors := make(map[string]*siteContentProcessor) contentProcessors := make(map[string]*siteContentProcessor)
var defaultContentProcessor *siteContentProcessor
sites := s.owner.langSite() sites := s.owner.langSite()
for k, v := range sites { for k, v := range sites {
proc := newSiteContentProcessor(baseDir, len(filenames) > 0, v) proc := newSiteContentProcessor(baseDir, len(filenames) > 0, v)
contentProcessors[k] = proc contentProcessors[k] = proc
if k == defaultContentLanguage {
defaultContentProcessor = proc
}
g.Go(func() error { g.Go(func() error {
return proc.process(ctx) return proc.process(ctx)
}) })
@ -1264,7 +1266,7 @@ func (s *Site) readAndProcessContent(filenames ...string) error {
bundleMap *contentChangeMap bundleMap *contentChangeMap
) )
mainHandler := &contentCaptureResultHandler{contentProcessors: contentProcessors} mainHandler := &contentCaptureResultHandler{contentProcessors: contentProcessors, defaultContentProcessor: defaultContentProcessor}
if s.running() { if s.running() {
// Need to track changes. // Need to track changes.

View file

@ -35,6 +35,7 @@ type SourceSpec struct {
Languages map[string]interface{} Languages map[string]interface{}
DefaultContentLanguage string DefaultContentLanguage string
DisabledLanguages map[string]bool
} }
// NewSourceSpec initializes SourceSpec using languages from a given configuration. // 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") defaultLang := cfg.GetString("defaultContentLanguage")
languages := cfg.GetStringMap("languages") languages := cfg.GetStringMap("languages")
disabledLangsSet := make(map[string]bool)
for _, disabledLang := range cfg.GetStringSlice("disableLanguages") {
disabledLangsSet[disabledLang] = true
}
if len(languages) == 0 { if len(languages) == 0 {
l := helpers.NewDefaultLanguage(cfg) l := helpers.NewDefaultLanguage(cfg)
languages[l.Lang] = l 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 { func (s *SourceSpec) IgnoreFile(filename string) bool {