Menu icon Foundation
Multilingual website and Panini's built-in {{root}} variable

Hello all

I'm building a multilingual website where the language (not the country) is encoded in the the URL, like so:

www.host.com/de/index.html       > for german
www.host.com/en/index.html       > for english

For this purpose I'm using an npm based tool called gulp-html-i18n.This is the relevant part in the gulp file:

// Copy page templates into finished HTML files
function pages() {
  return gulp.src('src/pages/**/*.{html,hbs,handlebars}')
    .pipe(panini({
      root: 'src/pages/',
      layouts: 'src/layouts/',
      partials: 'src/partials/',
      data: 'src/data/',
      helpers: 'src/helpers/'
    }))
    .pipe(i18n({
      langDir: 'src/languages',
      delimiters: ['{${', '}$}'],
      createLangDirs: true,
      trace: true
    }))
    .pipe(gulp.dest(PATHS.dist));
}

Assuming I've got only a single index.html page, using only Panini would yield this:

/dist/index.html

whereas using gulp-html-i18n I now get this:

/dist/de/index.html
/dist/en/index.html

That's exactly what I want. Unfortunately, this creates an issue with Panini's use of the built-in {{root}} variable as described here:

http://foundation.zurb.com/sites/docs/panini.html#root

Panini has no idea that the page it is assembling won't end up under root directory (i.e. dist). As a result, in ends up replacing {{root}} with "" rather than "../".

What would be the best way to fix this?

It would be very handy if Panini could deal with the html files in two passes, where the first does everything except replace {{root}}, and the second does nothing but that. I could then insert gulp-html-i18n into the pipeline between those two passes and everything would work as desired. Is there any way to achieve that? If not, what are my options?

Panini {{root}} gulp multilingual

Hello all

I'm building a multilingual website where the language (not the country) is encoded in the the URL, like so:

www.host.com/de/index.html       > for german
www.host.com/en/index.html       > for english

For this purpose I'm using an npm based tool called gulp-html-i18n.This is the relevant part in the gulp file:

// Copy page templates into finished HTML files
function pages() {
  return gulp.src('src/pages/**/*.{html,hbs,handlebars}')
    .pipe(panini({
      root: 'src/pages/',
      layouts: 'src/layouts/',
      partials: 'src/partials/',
      data: 'src/data/',
      helpers: 'src/helpers/'
    }))
    .pipe(i18n({
      langDir: 'src/languages',
      delimiters: ['{${', '}$}'],
      createLangDirs: true,
      trace: true
    }))
    .pipe(gulp.dest(PATHS.dist));
}

Assuming I've got only a single index.html page, using only Panini would yield this:

/dist/index.html

whereas using gulp-html-i18n I now get this:

/dist/de/index.html
/dist/en/index.html

That's exactly what I want. Unfortunately, this creates an issue with Panini's use of the built-in {{root}} variable as described here:

http://foundation.zurb.com/sites/docs/panini.html#root

Panini has no idea that the page it is assembling won't end up under root directory (i.e. dist). As a result, in ends up replacing {{root}} with "" rather than "../".

What would be the best way to fix this?

It would be very handy if Panini could deal with the html files in two passes, where the first does everything except replace {{root}}, and the second does nothing but that. I could then insert gulp-html-i18n into the pipeline between those two passes and everything would work as desired. Is there any way to achieve that? If not, what are my options?

Andrew Henning over 1 year ago

Really? Nobody has come across this issue before? If not, why not? How are you building multilingual sites?

Or are people perhaps just hard-coding their paths to the assets folder? I suspect I could do that too, but if panini ever inserts a partial into a html page with a different relative path to the root directory than all the other html pages, then it's game over for the hard-coded approach.

Vladimir 9 months ago

I thought about it the same way as you do - that I need to complicate things with static site builder and to think in a way dynamic sites are built, but reality is much simpler. Here is my approach:

In my pages folder I have organized content in this way:

1. Root of my pages folder belongs to main language used on a site

2. Subfolders with language names belong to other languages ('en/', 'sl/', 'rs/' ...)


When you create a menu in your data folder, just set that the home-page for your main language starts from their folder and not the root. 

Here is how my menu.yml looks like. It is composed of language switch under 'languages' and language specific menus and routes under language long code.

You build the rest of the site on top of that.

 

languages:
  -
    name: Srpski
    code-long: 'sr-YU'
    url: /
    image: rs
  -
    name: Slovenački
    code-long: 'sl-SI'
    url: /sl/
    image: sl
  -
    name: English
    code-long: 'en-GB'
    url: /en/
    image: en
sr-YU:
  -
    name: Naslovna
    url: /
    page: 'index'
  -
    name: O nama
    url: /ilic-dent-tim.html
    page: 'ilic-dent-tim'
  -
    name: Kontakt
    url: /kontakt.html
    page: 'kontakt'
en-GB:
  -
    name: Home
    url: /en/
    page: 'index'
  -
    name: About Us
    url: /en/ilic-dent-team.html
    page: 'ilic-dent-team'
  -
    name: Contact
    url: /en/contact.html
    page: 'contact'
sl-SI:
  -
    name: Domača stran
    url: /sl/
    page: 'index'
  -
    name: O nas
    url: /sl/o-nas.html
    page: 'o-nas'
  -
    name: Kontakt
    url: /sl/kontakt.html
    page: 'kontakt'



sambhav garg 9 months ago

thanks for the helpful post pls keep sharing simple receipt templates

Andrew Henning 9 months ago

Hello @Vladimir

I don't see how your answer relates to my question.

Assume you have a partial HTML file which includes the following line

<link rel="stylesheet" href="{{root}}assets/css/app.css">

Also assume that this partial is assembled as part of multiple HTML files which reside at different locations on the filesystem.

Most importantly, consider that as part of the HTML file assembly process, Panini replaces all occurrences of {{root}} with a value that depends on where the HTML file resides on the filesystem. As a result, no gulp build step following Panini is allowed to modify the path under which the HTML file is saved, as that would invalidate the value Panini replaced {{root}} with. Unfortunately, that is what my internationalization plugin must do.

So, how does what you described solve this problem?

 

Vladimir 9 months ago

Andrew, my internationalization approach works. Maybe I haven't explained it properly, or you overlooked something.

Important thing is - you don't need any internationalization plugins or gulp processes to make the multi-language site with Panini. Key is how you organize your files and data in yaml and json. 

Here is my simple example:

This is my folder structure:

src/
|-some project files and folders
|-site/
||-data/
||-helpers/
||-layouts/
||-partials/
||-pages/
|||-en/
|||-sl/
|||-files and folders for default language are in root folder for pages

 

Let's say you have partial just for styles called head__css.html

<link href="{{root}}styles/style.min.css?build={{timestamp}}" type="text/css" rel="stylesheet">

 

That partial is included in partial called head.html

<meta charset="UTF-8">
<title>{{page-title}} | Site name</title>

{{> head__meta}}
{{> head__css}}
{{> head__scripts}}
{{> head__og}}
{{> head__favicons}}

 

Partial Head.html is included in layout default.html

<!doctype html>
<html lang="{{language}}" class="no-js">
<head>
  {{> head}}
</head>

<body>
  <div class="wrap">
    {{> main}}
    {{> body}}
  </div>
  <footer class="site-footer">
    {{> footer }}
  </footer>
  {{> footer__scripts}}
</body>
</html>

 

In my first post you can find menu structure located in data/menu.yml file.

languages:
  -
    name: Srpski
    code-long: 'sr-YU'
    url: /
    image: rs
  -
    name: Slovenački
    code-long: 'sl-SI'
    url: /sl/
    image: sl
  -
    name: English
    code-long: 'en-GB'
    url: /en/
    image: en
sr-YU:
  -
    name: Naslovna
    url: /
    page: 'index'
  -
    name: O nama
    url: /ilic-dent-tim.html
    page: 'ilic-dent-tim'
  -
    name: Kontakt
    url: /kontakt.html
    page: 'kontakt'
en-GB:
  -
    name: Home
    url: /en/
    page: 'index'
  -
    name: About Us
    url: /en/ilic-dent-team.html
    page: 'ilic-dent-team'
  -
    name: Contact
    url: /en/contact.html
    page: 'contact'
sl-SI:
  -
    name: Domača stran
    url: /sl/
    page: 'index'
  -
    name: O nas
    url: /sl/o-nas.html
    page: 'o-nas'
  -
    name: Kontakt
    url: /sl/kontakt.html
    page: 'kontakt'

In that file notice the url key. This is the location where you insert language prefix (language folder). Since main language on my site is Serbian, pages that use it have url start just with '/'. Files for English language have prefix '/en/'.

 

For each page, in frontmatter I have created key language.

This is how it looks like for Serbian pages:

---
layout: article
language: 'sr-YU'

#other frontmatter stuff
---

 

This is how simplified partial menu.html looks like for 3 languages. Menu is included in Main.html partial.

<nav>
  <ul class="bit-menu">
    {{#ifequal language 'sr-YU'}}
      {{#each menu.sr-RS }}
      <li><a href="{{ url }}" {{#ifpage page}} class="active" {{/ifpage}}>{{ name }}</a></li>
      {{/each}}
    {{/ifequal}}
    {{#ifequal language 'en-GB'}}
      {{#each menu.en-GB }}
      <li><a href="{{ url }}" {{#ifpage page}} class="active" {{/ifpage}}>{{ name }}</a></li>
      {{/each}}
    {{/ifequal}}
    {{#ifequal language 'sl-SI'}}
      {{#each menu.sl-SI }}
      <li><a href="{{ url }}" {{#ifpage page}} class="active" {{/ifpage}}>{{ name }}</a></li>
      {{/each}}
    {{/ifequal}}
  </ul>
</nav>

 

{{root}} helper will work properly in this setup. If you try to mess around with folder structure with 3rd party NPM scripts for multilanguage content, you will disrupt the way Panini is meant to work. Remember - this is not PHP or some other dynamic language where you can use classes and routers. This is as simple as it can be. It requires different approach and it took me a while to come down from PHP Slim framework and that way of thinking to this. ;)

Vladimir 9 months ago

It seems that I have found the error you are talking about - {{root}} doesn't work properly when used from within the pages.

Vladimir 8 months ago

I've found out where is the problem and have opened an issue on Github: https://github.com/zurb/panini/issues/138

Andrew Henning 6 months ago

I doubt what you've found is related to my problem, but thanks for reporting it nonetheless. ;-)