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,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

View 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

View 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.
//

View 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

View 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
}

View 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 {

View 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

View 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)

View 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.

View 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 {