Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish the multilingual feature #2303

Closed
wants to merge 51 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
132035a
Add multilingual support in Hugo
abourget May 14, 2016
ab69f6d
Add proper Language and Languages types
bep Jul 24, 2016
ace89dc
Add Translations and AllTranslations methods to Page
bep Jul 25, 2016
45368b1
Rename MainSites to Sites
bep Jul 25, 2016
ee608e3
Reset translation slice on rebuild
bep Jul 25, 2016
57137dd
Introduce HugoSites type
bep Jul 26, 2016
0cd90de
Rework the i18n template func handling
bep Jul 26, 2016
a414c0b
Add Translations and AllTranslations to Node
bep Jul 26, 2016
b9e234e
Move HugoSites to hugolib
bep Jul 27, 2016
5101f14
Move the Build* methods to HugoSites
bep Jul 27, 2016
e7efd95
Optimize the multilanguage build process
bep Jul 28, 2016
fa30293
Render the shortcodes as late as possible
bep Aug 1, 2016
a875e2d
Rework the multilingual docs
bep Aug 4, 2016
31b31ac
Redirect to main language from root
bep Aug 4, 2016
0cee248
Add sitemap index for multilingual sites
bep Aug 4, 2016
df2a13f
Make the config loading testable
bep Aug 5, 2016
0cb0ed9
Make taxonomies configurable per language
bep Aug 5, 2016
01cf48f
Get the list commands up to multi-site level
bep Aug 5, 2016
34b19ba
cmd: Load config from the common config loader in hugolib
bep Aug 5, 2016
29ccd24
Make it possible to add a language in server mode
bep Aug 6, 2016
d8e0497
Make it possible to configure Blackfroday per language
bep Aug 7, 2016
b00623f
Improve language handling in URLs
bep Aug 7, 2016
eee1132
Fix the shortcode ref tests
bep Aug 7, 2016
cfebc29
Fix livereload in multilingual mode
bep Aug 7, 2016
5182241
Fix some corner cases in revised summary handling
bep Aug 7, 2016
3037b2d
Multilingual TODO-fixes, take 1
bep Aug 7, 2016
d9bcbe6
Make sure drafts etc. are not processed
bep Aug 8, 2016
e7f0e3a
Make the check command work in multilingual mode
bep Aug 8, 2016
f42fc56
Add parent as owner to Site
bep Aug 8, 2016
5fa9d17
Render main content language in root by default
bep Aug 8, 2016
ae6a4fa
Improve i18n string handling
bep Aug 9, 2016
b7a922c
Remove unused Multilingual Viper default
bep Aug 9, 2016
1767750
Add IsTranslated to Node and Page
bep Aug 9, 2016
b3ecfcd
Handle error in config loading
bep Aug 9, 2016
d8291b7
doc: Fix TOML vs YAML in multilang section
bep Aug 9, 2016
1dc571b
Create a Node map to get proper node translations
bep Aug 9, 2016
3dd112e
Add temp MULTILINGUAL version suffix to this branch
bep Aug 10, 2016
b54f2ff
Add data tests
bep Aug 10, 2016
a1b2d47
Fix YAML loading of multilingual config
bep Aug 10, 2016
e0a2715
Add JSON config to the multilanguage testing
bep Aug 10, 2016
949d8b4
docs: Get the variable and funcs re multilingual up-to-date
bep Aug 11, 2016
b7c1d5c
Fix Data in multisites
bep Aug 11, 2016
3834927
Create a copy of the section node for RSS
bep Aug 11, 2016
5af4bc1
docs: Fix spelling: HasTranslations > IsTranslated
bep Aug 12, 2016
5b41d8f
Small adjustment to SiteInfo init
bep Aug 12, 2016
d93a863
Set lang template globals for each site when render shortcodes
bep Aug 12, 2016
7f2871b
Fix multilingual reload when shortcode changes
bep Aug 12, 2016
a5ed858
Fix site reset for benchmarks etc.
bep Aug 14, 2016
9dd7ac2
Make sure the 404 node has .Data.Pages
bep Aug 15, 2016
5c98048
Add a global Reset func
bep Aug 16, 2016
680636c
Fix go vet 1.7 issues
bep Aug 20, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions commands/benchmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ func benchmark(cmd *cobra.Command, args []string) error {
return err
}
for i := 0; i < benchmarkTimes; i++ {
MainSite = nil
_ = buildSite()
_ = resetAndbuildSites(false)
}
pprof.WriteHeapProfile(f)
f.Close()
Expand All @@ -76,8 +75,7 @@ func benchmark(cmd *cobra.Command, args []string) error {
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
for i := 0; i < benchmarkTimes; i++ {
MainSite = nil
_ = buildSite()
_ = resetAndbuildSites(false)
}
}

Expand Down
8 changes: 5 additions & 3 deletions commands/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ package commands

import (
"github.com/spf13/cobra"
"github.com/spf13/hugo/hugolib"
)

var checkCmd = &cobra.Command{
Expand All @@ -34,7 +33,10 @@ func check(cmd *cobra.Command, args []string) error {
if err := InitializeConfig(checkCmd); err != nil {
return err
}
site := hugolib.Site{}

return site.Analyze()
if err := initSites(); err != nil {
return err
}

return Hugo.Analyze()
}
146 changes: 51 additions & 95 deletions commands/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"sync"
"time"

"github.com/spf13/hugo/tpl"

"github.com/spf13/hugo/hugofs"

"github.com/spf13/hugo/parser"
Expand All @@ -46,15 +48,15 @@ import (
"github.com/spf13/viper"
)

// MainSite represents the Hugo site to build. This variable is exported as it
// Hugo 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 Hugo *hugolib.HugoSites

// Reset resets Hugo ready for a new full build. This is mainly only useful
// for benchmark testing etc. via the CLI commands.
func Reset() error {
MainSite = nil
Hugo = nil
viper.Reset()
return nil
}
Expand Down Expand Up @@ -242,6 +244,7 @@ func initHugoBuildCommonFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&preserveTaxonomyNames, "preserveTaxonomyNames", false, `Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")`)
cmd.Flags().BoolVarP(&forceSync, "forceSyncStatic", "", false, "Copy all files when static is changed.")
cmd.Flags().BoolVarP(&noTimes, "noTimes", "", false, "Don't sync modification time of files")
cmd.Flags().BoolVarP(&tpl.Logi18nWarnings, "i18n-warnings", "", false, "Print missing translations")

// Set bash-completion.
// Each flag must first be defined before using the SetAnnotation() call.
Expand Down Expand Up @@ -273,81 +276,12 @@ func init() {
HugoCmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{})
}

func loadDefaultSettings() {
viper.SetDefault("cleanDestinationDir", false)
viper.SetDefault("Watch", false)
viper.SetDefault("MetaDataFormat", "toml")
viper.SetDefault("Disable404", false)
viper.SetDefault("DisableRSS", false)
viper.SetDefault("DisableSitemap", false)
viper.SetDefault("DisableRobotsTXT", false)
viper.SetDefault("ContentDir", "content")
viper.SetDefault("LayoutDir", "layouts")
viper.SetDefault("StaticDir", "static")
viper.SetDefault("ArchetypeDir", "archetypes")
viper.SetDefault("PublishDir", "public")
viper.SetDefault("DataDir", "data")
viper.SetDefault("ThemesDir", "themes")
viper.SetDefault("DefaultLayout", "post")
viper.SetDefault("BuildDrafts", false)
viper.SetDefault("BuildFuture", false)
viper.SetDefault("BuildExpired", false)
viper.SetDefault("UglyURLs", false)
viper.SetDefault("Verbose", false)
viper.SetDefault("IgnoreCache", false)
viper.SetDefault("CanonifyURLs", false)
viper.SetDefault("RelativeURLs", false)
viper.SetDefault("RemovePathAccents", false)
viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"})
viper.SetDefault("Permalinks", make(hugolib.PermalinkOverrides, 0))
viper.SetDefault("Sitemap", hugolib.Sitemap{Priority: -1, Filename: "sitemap.xml"})
viper.SetDefault("DefaultExtension", "html")
viper.SetDefault("PygmentsStyle", "monokai")
viper.SetDefault("PygmentsUseClasses", false)
viper.SetDefault("PygmentsCodeFences", false)
viper.SetDefault("PygmentsOptions", "")
viper.SetDefault("DisableLiveReload", false)
viper.SetDefault("PluralizeListTitles", true)
viper.SetDefault("PreserveTaxonomyNames", false)
viper.SetDefault("ForceSyncStatic", false)
viper.SetDefault("FootnoteAnchorPrefix", "")
viper.SetDefault("FootnoteReturnLinkContents", "")
viper.SetDefault("NewContentEditor", "")
viper.SetDefault("Paginate", 10)
viper.SetDefault("PaginatePath", "page")
viper.SetDefault("Blackfriday", helpers.NewBlackfriday())
viper.SetDefault("RSSUri", "index.xml")
viper.SetDefault("SectionPagesMenu", "")
viper.SetDefault("DisablePathToLower", false)
viper.SetDefault("HasCJKLanguage", false)
viper.SetDefault("EnableEmoji", false)
viper.SetDefault("PygmentsCodeFencesGuessSyntax", false)
viper.SetDefault("UseModTimeAsFallback", false)
}

// InitializeConfig initializes a config file with sensible default configuration flags.
func InitializeConfig(subCmdVs ...*cobra.Command) error {
viper.AutomaticEnv()
viper.SetEnvPrefix("hugo")
viper.SetConfigFile(cfgFile)
// See https://github.com/spf13/viper/issues/73#issuecomment-126970794
if source == "" {
viper.AddConfigPath(".")
} else {
viper.AddConfigPath(source)
}
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigParseError); ok {
return newSystemError(err)
}
return newSystemErrorF("Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details. (%s)\n", err)
if err := hugolib.LoadGlobalConfig(source, cfgFile); err != nil {
return err
}

viper.RegisterAlias("indexes", "taxonomies")

loadDefaultSettings()

for _, cmdV := range append([]*cobra.Command{hugoCmdV}, subCmdVs...) {

if flagChanged(cmdV.PersistentFlags(), "verbose") {
Expand Down Expand Up @@ -491,6 +425,7 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error {
}

return nil

}

func flagChanged(flags *flag.FlagSet, key string) bool {
Expand All @@ -506,8 +441,7 @@ func watchConfig() {
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
// Force a full rebuild
MainSite = nil
utils.CheckErr(buildSite(true))
utils.CheckErr(reCreateAndbuildSites(true))
if !viper.GetBool("DisableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
livereload.ForceRefresh()
Expand All @@ -533,7 +467,7 @@ func build(watches ...bool) error {
if len(watches) > 0 && watches[0] {
watch = true
}
if err := buildSite(buildWatch || watch); err != nil {
if err := buildSites(buildWatch || watch); err != nil {
return fmt.Errorf("Error building site: %s", err)
}

Expand Down Expand Up @@ -632,15 +566,21 @@ 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 {
if err != nil {
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
Expand Down Expand Up @@ -684,6 +624,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() {
Expand All @@ -693,35 +634,50 @@ func getDirList() []string {
return a
}

func buildSite(watching ...bool) (err error) {
fmt.Println("Started building site")
startTime := time.Now()
if MainSite == nil {
MainSite = new(hugolib.Site)
func reCreateAndbuildSites(watching bool) (err error) {
if err := initSites(); err != nil {
return err
}
if len(watching) > 0 && watching[0] {
MainSite.RunMode.Watching = true
fmt.Println("Started building sites ...")
return Hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true, Watching: watching, PrintStats: true})
}

func resetAndbuildSites(watching bool) (err error) {
if err := initSites(); err != nil {
return err
}
fmt.Println("Started building sites ...")
return Hugo.Build(hugolib.BuildCfg{ResetState: true, Watching: watching, PrintStats: true})
}

func initSites() error {
if Hugo != nil {
return nil
}
err = MainSite.Build()

h, err := hugolib.NewHugoSitesFromConfiguration()

if err != nil {
return err
}
MainSite.Stats()
jww.FEEDBACK.Printf("in %v ms\n", int(1000*time.Since(startTime).Seconds()))
Hugo = h

return nil
}

func rebuildSite(events []fsnotify.Event) error {
startTime := time.Now()
err := MainSite.ReBuild(events)
if err != nil {
func buildSites(watching bool) (err error) {
if err := initSites(); err != nil {
return err
}
MainSite.Stats()
jww.FEEDBACK.Printf("in %v ms\n", int(1000*time.Since(startTime).Seconds()))
fmt.Println("Started building sites ...")
return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: true})
}

return nil
func rebuildSites(events []fsnotify.Event) error {
if err := initSites(); err != nil {
return err
}
return Hugo.Rebuild(hugolib.BuildCfg{PrintStats: true, Watching: true}, events...)
}

// NewWatcher creates a new watcher to watch filesystem events.
Expand Down Expand Up @@ -937,7 +893,7 @@ func NewWatcher(port int) error {
const layout = "2006-01-02 15:04 -0700"
fmt.Println(time.Now().Format(layout))

rebuildSite(dynamicEvents)
rebuildSites(dynamicEvents)

if !buildWatch && !viper.GetBool("DisableLiveReload") {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized
Expand Down
30 changes: 21 additions & 9 deletions commands/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,17 @@ var listDraftsCmd = &cobra.Command{

viper.Set("BuildDrafts", true)

site := &hugolib.Site{}
sites, err := hugolib.NewHugoSitesFromConfiguration()

if err := site.Process(); err != nil {
if err != nil {
return newSystemError("Error creating sites", err)
}

if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return newSystemError("Error Processing Source Content", err)
}

for _, p := range site.Pages {
for _, p := range sites.Pages() {
if p.IsDraft() {
fmt.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
}
Expand All @@ -82,13 +86,17 @@ posted in the future.`,

viper.Set("BuildFuture", true)

site := &hugolib.Site{}
sites, err := hugolib.NewHugoSitesFromConfiguration()

if err != nil {
return newSystemError("Error creating sites", err)
}

if err := site.Process(); err != nil {
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return newSystemError("Error Processing Source Content", err)
}

for _, p := range site.Pages {
for _, p := range sites.Pages() {
if p.IsFuture() {
fmt.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
}
Expand All @@ -113,13 +121,17 @@ expired.`,

viper.Set("BuildExpired", true)

site := &hugolib.Site{}
sites, err := hugolib.NewHugoSitesFromConfiguration()

if err != nil {
return newSystemError("Error creating sites", err)
}

if err := site.Process(); err != nil {
if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
return newSystemError("Error Processing Source Content", err)
}

for _, p := range site.Pages {
for _, p := range sites.Pages() {
if p.IsExpired() {
fmt.Println(filepath.Join(p.File.Dir(), p.File.LogicalName()))
}
Expand Down
Loading