Hugo

Folder structure for multi language

I am trying to use hugo to generate a multi language folder structure as

/Contents/Resources/de.lproj/
/Contents/Resources/en.lproj/
/Contents/Resources/js/
/Contents/Resources/css/

Each lproj file will hold a language version.

I’ve read through https://gohugo.io/content-management/multilingual/ but don’t a see a way to do this yet.

  1. providing a prefix to where the translated root is. Only way I see is to do this as a post processing step.
  2. have the language directory name en.lproj instead of en (Seems I can just name the language `en.lproj. Not sure that’s OK or a hack though)
1 Like

Those folder-names look suspect to me. Why don’t they look like this:

/content/de/project.md
/content/en/project.md
/static/js
/static/css

or

/content/project.de.md
/content/project.en.md
/static/js
/static/css

or did I miss something in the docs?

The target structure is pre-defined. It’s not a β€œnormal” website.

Here is another attempt:

defaultContentLanguage: en

languages:
  en:
    contentDir: Contents/Resources/en.lproj
  de:
    contentDir: Contents/Resources/de.lproj

But it seems hugo seems to ignore the contentDir. It goes straight into en. And the de is nowhere to be found either. What I would have expected:

public/
└── Contents
    └── Resources
        β”œβ”€β”€ css
        β”‚   └── styles.css
        β”œβ”€β”€ de.lproj
        β”‚   └── index.html
        └── en.lproj
            └── index.html

What it really is:

public/
β”œβ”€β”€ Contents
β”‚   └── Resources
β”‚       └── css
β”‚           └── styles.css
└── en
    └── index.html

This does not seem in line with the documentation.
I don’t get it.

As far as I understand contentDir, it only tells Hugo where to look for content.

Take for example, a non-multilang setup.

Say you have content foo.md. By default Hugo will look at content and if it finds content/foo.md it will be published at yoursite.com/foo/.

Setting contentDir to nested/content/dir, Hugo will then look at nested/content/dir/ and if it finds nested/content/dir/foo.md it will be published at yoursite.com/foo/.

Re-reading the documentation it seems you are right :frowning:
So the language destination path is completely baked in?
Or is there any other way I overlooked?

Can you post a simple repo with your issue? I am not sure if I get it.
Maybe the frontmatter of your lproj files defines the slug wrong?
A full view would probably help to debug this. I would search for the issue not in translation setup but in the frontmatter and linkformat setup.

OK. Here is a zip of a test project.

It generates the structure as expected - but it does not make use of any multi language features. I was now trying to see if it was possible to have a single layout with i18n support.

That’s where I am stuck.

I run hugo and have the output that you expect:

public/
└── Contents
    └── Resources
        β”œβ”€β”€ css
        β”‚   └── styles.css
        β”œβ”€β”€ de.lproj
        β”‚   └── index.html
        └── en.lproj
            └── index.html

Hugo v0.58.0/extended.

https://www.dropbox.com/s/4w351kqjh3cfhps/public.zip?dl=0 < this is the rendered result. Isn’t this what you expected?

If so:

  • check your hugo version and update. You are reading the live documentation that is most times up to date to the newest Hugo version
  • delete the public folder before you run hugo next time
  • delete the resources folder before you run hugo next time

The way I read your initial messages it comes out exactly as you expect :slight_smile:

Thanks for trying. But please re-read my last post :slight_smile:

It generates the structure as expected - but it does not make use of any multi language features

Now I would like to make use of the multi language feature instead of manually maintaining a version per language.

Yes, sorry, sometimes you need to talk to me loud and slowly :slight_smile: I thought you were confident the setup is right but not processing properly.

Look at this changed config file, explanations afterwards:

copyright: ''

title: Help
#languageCode: en-US

defaultContentLanguage: en
defaultContentLanguageInSubdir: true
languages:
  en:
    weight: 20
    baseURL: https://example.com
  de:
    weight: 10
    baseURL: https://example.com

relativeURLs: true
canonifyURLs: true
disablePathToLower: true
disableFastRender: true
disableHugoGeneratorInject: true
pygmentsUseClasses: true
disableKinds:
  - home
  - section
  - taxonomy
  - taxonomyTerm
  - RSS
  - sitemap

theme: apple-help
params:
  • I think the languageCode is obsolete if you have multiple languages
  • You need to set defaultContentLanguageInSubdir to true so ALL languages are in subfolders
  • your german was weight 2, en was weight 1, so german was the main language in the index.html, then en in the subfolder, higher weight, more importance
  • with this setup you could produce two different websites under their own urls. set the baseURL property below the language to the baseurl you need.

I am not sure if that solves the main issue yet, because apart from the two language folders there is no root index.html to send people where ever they have to go and it takes some subfolders until we have the first index.html inside of the language folders. But it might be a beginning?

You can set

languages:
  en:
    weight: 20
    baseURL: https://example.com/en
  de:
    weight: 10
    baseURL: https://example.com/de

but need a homepage (that you currently disable in disableKinds) that can send people to your default language or offer selection.

Thanks for you input. Applying this I get:

public/
β”œβ”€β”€ Contents
β”‚   β”œβ”€β”€ Info.plist
β”‚   β”œβ”€β”€ Pkginfo
β”‚   β”œβ”€β”€ Resources
β”‚   β”‚   β”œβ”€β”€ GlobalArt
β”‚   β”‚   β”œβ”€β”€ css
β”‚   β”‚   β”‚   └── styles.css
β”‚   β”‚   └── js
β”‚   └── version.plist
β”œβ”€β”€ en
β”‚   └── Contents
β”‚       └── Resources
β”‚           β”œβ”€β”€ de.lproj
β”‚           β”‚   └── index.html
β”‚           └── en.lproj
β”‚               └── index.html
└── index.html

but…

  • defaultContentLanguageInSubdir lets /en appear - but no /de (why?)
  • I need the language subdir not be /en but /Contents/Resources/en.lproj
  • there is not really a baseURL as this site is only opened locally (from a help viewer)
  • the home would need to go into /Contents/Resources/index.html

Do you now see where I am stuck?

What’s your Hugo version? hugo version.
I can’t re-produce this.

Hugo Static Site Generator v0.58.0/extended darwin/amd64

To make sure we look at the same I have updated the zip.
Feel free to re-download.

In your current setup, you define:

languages:
  en:
    weight: 20
  de:
    weight: 10

There are two ways to manage your content translations. Both ensure each page is assigned a language and is linked to its counterpart translations.

^ From docs, ref here: https://gohugo.io/content-management/multilingual/#translate-your-content

You don’t have contentDir configured for either language; so there is no way for Hugo to know which bits of content is in de vs en.

Additionally none of the content is in the format foo.xx.md or similar, xx being the language code. This means the content has no language explicitly assigned; therefore the content is all assigned to the default en.

So, in short, technically, Hugo sees all of your content as en (as defined in defaultContentLanguage).


  • I need the language subdir not be /en but /Contents/Resources/en.lproj

I think this here is really your problem. It seems Hugo uses the language code (ie en, de) as directory name to publish each language to.

Take for example the docs on multilingual multihost config: https://gohugo.io/content-management/multilingual/#configure-multilingual-multihost

Each language gets its own subdir under public, named after its language code. So it looks like (at least from how I understand the docs + the experimenting I’ve done) the language code dir name is included, and is based on the defined language code in the config.


So how to go around this.

Given:

Contents
└── Resources
    β”œβ”€β”€ de.lproj
    β”‚   └── about.md
    └── en.lproj
        └── about.md
disablePathToLower = true
defaultContentLanguageInSubdir = true
defaultContentLanguage = "en"
publishDir = "public/Contents/Resources"
[languages]
  [languages.en]
    contentDir = "Contents/Resources/en.lproj"
  [languages.de]
    contentDir = "Contents/Resources/de.lproj"

You will get:

public
└── Contents
    └── Resources
        β”œβ”€β”€ de
        β”‚   β”œβ”€β”€ about
        β”‚   β”‚   └── index.html
        β”‚   └── index.html
        β”œβ”€β”€ en
        β”‚   β”œβ”€β”€ about
        β”‚   β”‚   └── index.html
        β”‚   └── index.html
        └── index.html

You could then rename /de/ to /de.lproj/ etc. You might also need to go into ...Resources/index.html to point to the correct url to redirect, and probably check that the URLs are formed correctly.


Or: add a file Contents/Resources/en.lproj/_index.md with:

aliases:
  - "/en.lproj"

and the equivalent to de. This gives you:

public
└── Contents
    └── Resources
        β”œβ”€β”€ de
        β”‚   β”œβ”€β”€ about
        β”‚   β”‚   └── index.html
        β”‚   └── index.html
        β”œβ”€β”€ de.lproj
        β”‚   └── index.html
        β”œβ”€β”€ en
        β”‚   β”œβ”€β”€ about
        β”‚   β”‚   └── index.html
        β”‚   └── index.html
        β”œβ”€β”€ en.lproj
        β”‚   └── index.html
        └── index.html

where /en.lproj/ redirects to /en/.


*Note that I am neither a Hugo dev nor a heavy user of the multilingual feature. Perhaps there is a better way I am overlooking.

1 Like

Thanks a lot for your help and input! There are some good pointers in there.

In the end it seems like hugo is just not flexible enough to make this happen without post processing. Which is really a shame.

I’ll play some more.