Best practices for i18n

Note:

Hugo v0.17 has native support for internationalization. This thread discusses approaches that were used in prior versions.


Currently I’m porting the Freelancer Theme to Hugo. It’s a small single-page portfolio. I want provide the option to let the user translate each string by setting the corresponding translation or change of the text in the config file.

Is no translation set, the template renders a default string. Since the theme is not very complex I’ve to options to implement i18n:

Either I hard code them into the templates with

{{ with .Site.Params.footer.place }}{{.}}{{ else }}Place{{end}}

or I create a another translation file with all default strings that can be accessed this way:

{{ with .Site.Params.footer.place }}{{.}}{{ else }}{{ .Site.Data.translation.footer.place }}{{end}}

Not sure if you’ve seen this @digitalcraftsman, but this helped me get my bilingual site sorted:

For small repeated strings I’m putting them in:

/data/translations/en-US.yaml
/data/translations/ja-JP.yaml

… setting “locale” in config_en.toml and config-ja.toml, and calling from my index template like:

{{ ( index $.Site.Data.translations $.Site.Params.locale ).somestring }}

… where “somestring” is:

somestring: ザ文字列

… in the yaml.

1 Like

Let’s say I created config_en.toml and config-ja.toml, aren’t only config.{yaml, yml,json,toml} legal names for config files that Hugo is looking for?

When there is a way to do so, how do you run the generator with the specific config file to create the translated pages?

Yes, just run Hugo to see the options. You can specify them for hugo server or just hugo.

I’m using zsh functions for it.

There is certainly some easy ways for Hugo to make this even simpler.

Thinking out loud and borrowing from Java’s resource bundles:

/data/translations/messages.yaml
/data/translations/messages_en-US.yaml
/data/translations/messages_ja-JP.yaml
/data/translations/messages_no.yaml

Then add a template func, something like message(key) that gets a resource for the given key from the resource bundle for the locale defined in config.toml or in one of its parents; if not found in any of them, it returns NOT_FOUND.

So the hiearchy goes from right to left, more specific to less, the fallback is messages.yaml (or toml, json).

If not found (NOT_FOUND) it should be logged as a warning. I’m not fan of adding a default string in the func as this will make it impossible to track missing strings.

2 Likes

That would be great @bep , and a wonderful way to get Hugo more traction, with a primary built-in method like that. I like how Java does it, and falling back to the default or a “NOT_FOUND” if there’s no string. I’d personally pick a default and always start there, translating after the default was added to the top-most file in the hierarchy.

Yea, it should probably return “keyname” NOT FOUND or something, to make it easy to add the missing piece.

May have to look for a better name than message - as it could contain any data structure.

Indeed.

I made a tutorial in docs about this, and the PR is here: https://github.com/spf13/hugo/pull/1196

Thanks for all the replies and especially to @RickCogley for providing a whole tutorial. I really appreciate the community around Hugo and the help in this forum.

This goes a bit beyound my requirements but are there also some tips for localizing the content? Hugo provides already a way of formating dates with a template function as far as I’ve seen it in the docs. But is there a way to do this nore dynamically depending on the language?

Other aspects of l10n is the formating of currencies etc that need to kept in mind. Are there also some best practices to recommend?

1 Like

Agree, it’s a great community and I want to participate somehow to make it better.

I see what you mean about other locale localizations. I think I read somewhere that go itself was not yet fully localized (iirc I had searched on making Japanese dates appear correctly), so it was a challenge to make that sort of thing happen with Hugo. The actual developers will know more, for sure.

The more I read and think about i18n the more I’m convinced the core support should be in Go’s stdlib. It’s in their plans, but they haven’t gotten there yet.

@RickCogley, I saw in your ‘pull requested’ tutorial, that you use a lot of ifs. This would result in a lot of template code for each string.

I think the implementation of a function that takes the key and returns the corresponding translation for the current language is much more elegant, as shown by @bep. This would be a nice feature that could be implemented by the Hugo devs. It’s just my opionion for the general use of i18n.

Back to my much smaller needs. Currently I’m using a config.yaml that that stores all strings under params like this:

portfolio:
    title: Portfolio
    modal:
        client: Client
        date: Date
        category: Category
        button:
            text: Close

My template looks if the string is set. Otherwise it renders the default text. This isn’t very elegant since my templates look a bit cluttered.

I also need to support only one language or any other custom string the user wants to see. Knowing this, I can outsource the translations from my config file into its file. Beside exists a file with all default strings.

The tutorial is nice for larger i18n needs (kudos for that) but I just need to generate a single version of my theme that either shows a custom string or the default one.

There is also one other option for larger parts that I would consider: Views and the render func (one view per language).

go-i18n seems to be a nice package. The developer just needs to insert T("A string to translate") in the code. It uses the key approach of @bep. All translations are stored inside .json files. This creates a small file format limitation.

But making the T() function available as template function would give a nice approach to do i18n – regardless of the project size.

Indeed it would, but that does not exist yet.

The tutorial I wrote shows both the use of ifs to show a block of text based on locale, or, how to pull from a data file. In my opinion, you need to use ifs if you have complex formatting needs, like grammar changes based on language, or, inline formats (like if you want part of a translated string formatted in an <em>, say), which don’t survive being pulled from a data file at this time. And, you can use the data file pull if it’s just simple strings with no formatting.

Native and more-well-developed methods would be nice, but if those are a necessity, they are not yet available as far as I know.

Views and Render might be more promising than how I’ve decided to do it for my current site.

Using .Render would be nice, since each locale has it’s seperate file with it’s own logic.

@RickCogley If you translate a page like in the tutorial you wrote, do you insert just the translation or also the rest of the configuration ( like title, baseurl, theme)?

Your deployment scripts runs every config file independently. So this information are required for Hugo to build the site.

The tutorial just shows one part. That’s the basic idea though, and you’d weave those techniques in, for your index, single, or other templates. I’m using both techniques in every template, mostly.