Belén Albeza

Building a static multi-language site with Metalsmith (part I)

These holidays I decided to start compiling my cooking recipes online, since some of my friends ask for them and I've grown tired of creating gists for them. And then, the dilemma: should I upload the recipes in English or in Spanish? I couldn't decide since some of my non-Spaniard friends have asked me for some recipe occasionally.

The solution: create a multi-language web with a static site generator. I opted for Metalsmith, since I prefer to use Node nowadays. And it turned out that the documentation for doing this kind of sites is not that great, so I thought it would be good to share how I did it.

While I was writing this post I got well over 1500 words, so I decided to split it up in two parts:

If you are already familiar you can jump into the second part right away. You can also see the final source code for this example project in Github.

And, of course, this is my online cookbook ;) It only has a handful of recipes right now, but I will be adding more.

The basic pipeline

OK, time to start! We will build the skeleton for a multi-language cookbook. First create an empty directory for this project and install Metalsmith:

npm install --save metalsmith  

Let's make a directory where our content will live and put a sample file there.

mkdir content  
touch content/index.html  

Create an index.js file. We will put our site's code in there.

var path = require('path');

var metalsmith = require('metalsmith');

metalsmith(__dirname)  
    .source('content')
    .destination('dist')
    .build(function (err) {
        if (err) { console.error(err); }
    });

Metalsmith works by processing the files inside a directory (which we indicate with .source()) and copying them to a different directory (indicated via destination). The copying part happens when we call build(). In this pipeline we can inject our own middleware –or existing plugins– to modify these files.

For example, this is how our current directory looks now:

.
├── content
│   └── index.html
├── index.js
└── package.json

Running node index.js will create a dist directory with the index.html file inside it.

dist  
└── index.html

Transforming files

Having your content in Markdown is something common, whether you are creating a website, or writing a book, etc.

We can use an existing plugin to process markdown and convert it to HTML. This plugin also allows us to add metadata fields to our files, which can then be used by other plugins, or to pass that data into layouts, etc.

With the Markdown plugin, this metadata must be added at the top of the file in YAML format. Rename the content/index.html to content/index.md and edit it:

---
title: Home  
---

My collection of **recipes**.  

Install the plugin:

npm install --save metalsmith-markdown  

Now we just need to require it and add it to Metalsmith's pipeline:

var path = require('path');

var metalsmith = require('metalsmith');  
var markdown = require('metalsmith-markdown');

metalsmith(__dirname)  
    .source('content')
    .destination('dist')
    .use(markdown())
    .build(function (err) {
        if (err) { console.error(err); }
    });

If you execute the code now, you will see that a dist/index.html is generated, with HTML code parsed from Markdown:

<p>My collection of <strong>recipes</strong>.</p>  

Right now we are not using the title metadata field, but it will come handy later.

Copying over raw assets

Besides our content, we'll also have some assets (images, CSS files, etc.) that don't require processing but we still need to copy to our dist folder.

There's the metalsmith-assets plugin just for that:

npm install --save metalsmith-assets  

We will store our assets in an assets folder in the root of our project. The content of this folder we'll be copied to dist.

For example, let's drop a CSS file into assets/css:

.
├── assets
│   └── css
│       └── styles.css
├── content
│   └── index.md
├── index.js
└── package.json

And add the following to the code:

// ...
var assets = require('metalsmith-assets');

metalsmith(__dirname)  
    // ...
    .use(assets({
        source: 'assets'
    }))
    .build(function (err) {
        if (err) { console.error(err); }
    });

Running it will yield the following dist structure:

dist  
├── css
│   └── styles.css
└── index.html

Using layouts

We don't want to repeat the same HTML code in all of our content files. This is what layouts are for: we can use an engine (Handlebars, Swig, etc.) of our choice and then passing to the layout the file's data.

All file objects in metalsmith have a contents attribute with its full (processed) contents. It will also have an attribute per each metadata field.

In my cookbook I opted for using Jade as template engine. Besides it, we need to install the metalsmith-layouts plugin.

npm install --save jade metalsmith-layouts  

Now create a layouts directory with a default.jade file inside:

doctype html  
head  
    title Cookbook - #{title}
    meta(charset='utf-8')
    link(rel='stylesheet', href='/css/styles.css')
body  
    header
        h1 Cookbook
    main: article
        h1= title
        != contents

You can see that we will be using two attributes of the files: their title metadata field and their contents.

When adding the metalsmith-assets plugin to the pipeline, we just need to specify the layout engine of our choice, the default layout to use in case the file hasn't specified it into its metadata, and also a glob pattern of files to apply the layout to.

Note that since the files have been already processed with Markdown, this glob pattern need to match .html files instead of .md:

var layouts = require('metalsmith-layouts');

metalsmith(__dirname)  
    // ...
    .use(markdown())
    .use(layouts({
        engine: 'jade',
        default: 'default.jade',
        pattern: '**/*.html'
    }))
    // ...

Run it and voilà:

Screenshot of applying a layout

Continue on part II.