Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandre Bourget <alex@bourget.cc>2016-05-14 07:35:16 +0300
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2016-09-06 18:32:15 +0300
commitec33732fbe84f67c1164fb713d6cb738609f2e2e (patch)
treee4d361fda15e254617fb0fc2fdba275a269afc65 /commands
parentfaa3472fa299adb287d575e6d404d4ddcddbff4e (diff)
Add multilingual support in Hugo
Implements: * support to render: * content/post/whatever.en.md to /en/2015/12/22/whatever/index.html * content/post/whatever.fr.md to /fr/2015/12/22/whatever/index.html * gets enabled when `Multilingual:` is specified in config. * support having language switchers in templates, that know where the translated page is (with .Page.Translations) (when you're on /en/about/, you can have a "Francais" link pointing to /fr/a-propos/) * all translations are in the `.Page.Translations` map, including the current one. * easily tweak themes to support Multilingual mode * renders in a single swift, no need for two config files. Adds a couple of variables useful for multilingual sites Adds documentation (content/multilingual.md) Added language prefixing for all URL generation/permalinking see in the code base. Implements i18n. Leverages the great github.com/nicksnyder/go-i18n lib.. thanks Nick. * Adds "i18n" and "T" template functions..
Diffstat (limited to 'commands')
-rw-r--r--commands/benchmark.go4
-rw-r--r--commands/hugo.go73
-rw-r--r--commands/list.go6
-rw-r--r--commands/multilingual.go41
4 files changed, 99 insertions, 25 deletions
diff --git a/commands/benchmark.go b/commands/benchmark.go
index 3f5aa6ef3..53d2c8e9e 100644
--- a/commands/benchmark.go
+++ b/commands/benchmark.go
@@ -57,7 +57,7 @@ func benchmark(cmd *cobra.Command, args []string) error {
return err
}
for i := 0; i < benchmarkTimes; i++ {
- MainSite = nil
+ MainSites = nil
_ = buildSite()
}
pprof.WriteHeapProfile(f)
@@ -76,7 +76,7 @@ func benchmark(cmd *cobra.Command, args []string) error {
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
for i := 0; i < benchmarkTimes; i++ {
- MainSite = nil
+ MainSites = nil
_ = buildSite()
}
}
diff --git a/commands/hugo.go b/commands/hugo.go
index 7afd78a9d..57a426458 100644
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -46,10 +46,10 @@ import (
"github.com/spf13/viper"
)
-// MainSite represents the Hugo site to build. This variable is exported as it
+// MainSites represents the Hugo sites to build. This variable is exported as it
// is used by at least one external library (the Hugo caddy plugin). We should
// provide a cleaner external API, but until then, this is it.
-var MainSite *hugolib.Site
+var MainSites map[string]*hugolib.Site
// Reset resets Hugo ready for a new full build. This is mainly only useful
// for benchmark testing etc. via the CLI commands.
@@ -287,6 +287,7 @@ func loadDefaultSettings() {
viper.SetDefault("ArchetypeDir", "archetypes")
viper.SetDefault("PublishDir", "public")
viper.SetDefault("DataDir", "data")
+ viper.SetDefault("I18nDir", "i18n")
viper.SetDefault("ThemesDir", "themes")
viper.SetDefault("DefaultLayout", "post")
viper.SetDefault("BuildDrafts", false)
@@ -323,6 +324,8 @@ func loadDefaultSettings() {
viper.SetDefault("EnableEmoji", false)
viper.SetDefault("PygmentsCodeFencesGuessSyntax", false)
viper.SetDefault("UseModTimeAsFallback", false)
+ viper.SetDefault("Multilingual", false)
+ viper.SetDefault("DefaultContentLanguage", "en")
}
// InitializeConfig initializes a config file with sensible default configuration flags.
@@ -490,6 +493,8 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error {
helpers.HugoReleaseVersion(), minVersion)
}
+ readMultilingualConfiguration()
+
return nil
}
@@ -506,7 +511,7 @@ func watchConfig() {
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
// Force a full rebuild
- MainSite = nil
+ MainSites = nil
utils.CheckErr(buildSite(true))
if !viper.GetBool("DisableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
@@ -632,6 +637,7 @@ func copyStatic() error {
func getDirList() []string {
var a []string
dataDir := helpers.AbsPathify(viper.GetString("DataDir"))
+ i18nDir := helpers.AbsPathify(viper.GetString("I18nDir"))
layoutDir := helpers.AbsPathify(viper.GetString("LayoutDir"))
staticDir := helpers.AbsPathify(viper.GetString("StaticDir"))
walker := func(path string, fi os.FileInfo, err error) error {
@@ -639,8 +645,13 @@ func getDirList() []string {
if path == dataDir && os.IsNotExist(err) {
jww.WARN.Println("Skip DataDir:", err)
return nil
+ }
+ if path == i18nDir && os.IsNotExist(err) {
+ jww.WARN.Println("Skip I18nDir:", err)
+ return nil
}
+
if path == layoutDir && os.IsNotExist(err) {
jww.WARN.Println("Skip LayoutDir:", err)
return nil
@@ -684,6 +695,7 @@ func getDirList() []string {
helpers.SymbolicWalk(hugofs.Source(), dataDir, walker)
helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("ContentDir")), walker)
+ helpers.SymbolicWalk(hugofs.Source(), i18nDir, walker)
helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("LayoutDir")), walker)
helpers.SymbolicWalk(hugofs.Source(), staticDir, walker)
if helpers.ThemeSet() {
@@ -695,31 +707,52 @@ func getDirList() []string {
func buildSite(watching ...bool) (err error) {
fmt.Println("Started building site")
- startTime := time.Now()
- if MainSite == nil {
- MainSite = new(hugolib.Site)
- }
- if len(watching) > 0 && watching[0] {
- MainSite.RunMode.Watching = true
+ t0 := time.Now()
+
+ if MainSites == nil {
+ MainSites = make(map[string]*hugolib.Site)
}
- err = MainSite.Build()
- if err != nil {
- return err
+
+ for _, lang := range langConfigsList {
+ t1 := time.Now()
+ mainSite, present := MainSites[lang]
+ if !present {
+ mainSite = new(hugolib.Site)
+ MainSites[lang] = mainSite
+ mainSite.SetMultilingualConfig(lang, langConfigsList, langConfigs)
+ }
+
+ if len(watching) > 0 && watching[0] {
+ mainSite.RunMode.Watching = true
+ }
+
+ if err := mainSite.Build(); err != nil {
+ return err
+ }
+
+ mainSite.Stats(lang, t1)
}
- MainSite.Stats()
- jww.FEEDBACK.Printf("in %v ms\n", int(1000*time.Since(startTime).Seconds()))
+
+ jww.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
return nil
}
func rebuildSite(events []fsnotify.Event) error {
- startTime := time.Now()
- err := MainSite.ReBuild(events)
- if err != nil {
- return err
+ t0 := time.Now()
+
+ for _, lang := range langConfigsList {
+ t1 := time.Now()
+ mainSite := MainSites[lang]
+
+ if err := mainSite.ReBuild(events); err != nil {
+ return err
+ }
+
+ mainSite.Stats(lang, t1)
}
- MainSite.Stats()
- jww.FEEDBACK.Printf("in %v ms\n", int(1000*time.Since(startTime).Seconds()))
+
+ jww.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds()))
return nil
}
diff --git a/commands/list.go b/commands/list.go
index 5267a4f8b..bc5bb557a 100644
--- a/commands/list.go
+++ b/commands/list.go
@@ -57,7 +57,7 @@ var listDraftsCmd = &cobra.Command{
return newSystemError("Error Processing Source Content", err)
}
- for _, p := range site.Pages {
+ for _, p := range site.AllPages {
if p.IsDraft() {
fmt.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
}
@@ -88,7 +88,7 @@ posted in the future.`,
return newSystemError("Error Processing Source Content", err)
}
- for _, p := range site.Pages {
+ for _, p := range site.AllPages {
if p.IsFuture() {
fmt.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
}
@@ -119,7 +119,7 @@ expired.`,
return newSystemError("Error Processing Source Content", err)
}
- for _, p := range site.Pages {
+ for _, p := range site.AllPages {
if p.IsExpired() {
fmt.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
}
diff --git a/commands/multilingual.go b/commands/multilingual.go
new file mode 100644
index 000000000..68da7c96d
--- /dev/null
+++ b/commands/multilingual.go
@@ -0,0 +1,41 @@
+package commands
+
+import (
+ "sort"
+
+ "github.com/spf13/cast"
+ "github.com/spf13/viper"
+)
+
+var langConfigs map[string]interface{}
+var langConfigsList langConfigsSortable
+
+func readMultilingualConfiguration() {
+ multilingual := viper.GetStringMap("Multilingual")
+ if len(multilingual) == 0 {
+ langConfigsList = append(langConfigsList, "")
+ return
+ }
+
+ langConfigs = make(map[string]interface{})
+ for lang, config := range multilingual {
+ langConfigs[lang] = config
+ langConfigsList = append(langConfigsList, lang)
+ }
+ sort.Sort(langConfigsList)
+}
+
+type langConfigsSortable []string
+
+func (p langConfigsSortable) Len() int { return len(p) }
+func (p langConfigsSortable) Less(i, j int) bool { return weightForLang(p[i]) < weightForLang(p[j]) }
+func (p langConfigsSortable) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+func weightForLang(lang string) int {
+ conf := langConfigs[lang]
+ if conf == nil {
+ return 0
+ }
+ m := cast.ToStringMap(conf)
+ return cast.ToInt(m["weight"])
+}