Localization with Jekyll

This post assumes familiarity with static site generators and Jekyll.

I recently made a localized website in Jekyll. Localization is a great application for a generator since it allows you to define pages once and have the generator create one version for each language.

I defined the localization data in the _data folder in a file called localization.yml. Opting for only one localization file makes it easy to work with the variables and also to see both versions of the data at the same time.

Here is my _data/localization.yml file:

localization'

Next I defined an HTML file for each language. These can be arranged in different ways. Because of relative path values (ie: for css includes) it’s easier to have them at the same directory level. It’s also possible the have them at different levels. This later option let’s us use a structure like http://www.notanactualsitename.com/fr where fr is the language name. The downside is that you’ve got to control the paths with variables.

Here is the English version of my index page (index.html):


layout: default
title: Consulting and Development
language: en
englishActive: class=”active”

{% assign locale = site.data.localization[page.language] %}

{% include main.html %}

And here is the French version (index-fr.html):


layout: default
title: Consultation et développement
language: fr
frenchActive: class=”active”

{% assign locale = site.data.localization[page.language] %}

{% include main.html %}

As you can see, the pages only function is to define a language variable and use it to assign a locale from the collection data.

The page then includes main.html which is where the actual HTML content is.

And here is a sample of main.html (link to original file):

<div class="info">
  <div class="action-box left">
    <h3><i class="fa fa-question fa-3x"></i> {{ locale.action1 }}</h3>
    {{ locale.actiontext1 }}
  </div>
  <div class="action-box right">
    <h3><i class="fa fa-road fa-3x"></i> {{ locale.action2 }}</h3>
    {{ locale.actiontext2 }}
  </div>
  <div class="action-box left last">
    <h3><i class="fa fa-desktop fa-3x"></i> {{ locale.action3 }}</h3>
    {{ locale.actiontext3 }}
  </div>
</div>

This file only contains the html structure of the page and the template instructions. All of the copy material is obtained from the collection which was already set to the correct language in the index page.

It’s not more complicated than that!

 

 

Static site generators

I’ve recently built another static website with Jekyll, a popular static site generator. Static websites, in opposition to dynamic websites do not connect to a database or execute server-side code.

Rather they are generated on the developer’s machine from files and then published as a static site. Generators automate part of the process by creating HTML files from data and allowing for code reuse. They use special markup and can include other files, use variables and generate complete pages using only local data.

In this simple example, a code generator could reuse this HTML file multiple times substituting {{ page.title }} and {{ content }} allowing us to create many pages with the same basic structure and <head> element without having to duplicate this information on every page.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{{ page.title }}</title>
<meta name="author" content="Gilles Leblanc">
	<link rel="stylesheet" href="./css/main.css">
	<link rel="shortcut icon" href="./img/favicon.ico" />
</head>
<body>
{{ content }}
</body>
</html>

In many situations a dynamic site isn’t needed and going static can save a lot of hassle. Generators even support creating blogs, where apart from the comments section, you can actually get by with a static site (and for comments there are external services available that you can embed in a static site like Disqus).

Going with a static generator has many advantages:

  • Speed. Since the site is serving plain HTML and CSS files it’s blazing fast. No need for database connections, connection pools, starting a process or interpreting any code.
  • Security. Again having no server side code or databases reduces the attack vector on a site.
  • Cheaper hosting. I use GitHub repositories to publish most of my static generated sites and thus don’t have to pay anything for hosting. There are also other free hosting solutions (like Neocities which I haven’t used). It’s also possible to pay only for a domain name and have it redirect to the freely hosted GitHub site.
  • Allows for the reuse of headers and footers. This was the reason for my first foray with static generators. I wanted to create a static site and I wanted to keep things DRY. I didn’t want to copy header and footer code on every page. Going with something like ASP.Net, Rails or PHP seemed overkill for a simple project with no need for a database.
  • Localization. Working with a static generator allows to create files with all the localized text of a Web site and then define the HTML code in a separate file. Upon generation, one page is created for each language in the localization files. That’s what I used to create a localized site for my personal consultancy NP-complet. I’ll detail the technique I used in another post.

You can use Top Open-Source Static Generators to find static generators. I can recommend Jekyll for most purposes.

Shell Script to update master

I was tired of always typing in the same commands to update my GitHub forks so I added this small shell script in /usr/local/bin (so it’s accessible from everywhere):


git fetch upstream
git merge upstream/master
git push origin master

So I can update my master branch quickly. I always work in feature branches so the merge from upstream is always a fast forward.

I named it update-master. Don’t forget to chmod 755.

Tree in Rust

I re-implemented my C tree program from my last post in Rust. Here is the GitHub link.

use std::collections::VecDeque;

struct TreeNode {
    value: i32,
    left: Option<Box<TreeNode>>,
    right: Option<Box<TreeNode>>,
}

fn main() {
    let root = build_tree();
    root.breadth_first();
}

fn build_tree() -> TreeNode {
    let root = TreeNode { value: 2,
        left: Some(Box::new(TreeNode { value: 7,
                            left: Some(Box::new(TreeNode { value: 2, left: None, right: None })),
                            right: Some(Box::new(TreeNode { value: 6,
                                                left: Some(Box::new(TreeNode { value: 5, left: None, right: None })),
                                                right: Some(Box::new(TreeNode { value: 11, left: None, right: None })) })) })),
        right: Some(Box::new(TreeNode { value: 5,
                            left: None,
                            right: Some(Box::new(TreeNode { value: 9,
                                                left: Some(Box::new(TreeNode { value: 4, left: None, right: None })),
                                                right: None })) }))};
    return root;
}

impl TreeNode {
    fn depth_first_pre(self) {
        print!("{}, ", self.value);

        if self.left.is_some() {
            self.left.unwrap().depth_first_pre();
        }

        if self.right.is_some() {
            self.right.unwrap().depth_first_pre();
        }
    }

    fn depth_first_post(self) {
        if self.left.is_some() {
            self.left.unwrap().depth_first_post();
        }

        if self.right.is_some() {
            self.right.unwrap().depth_first_post();
        }

        print!("{}, ", self.value);
    }

    fn breadth_first(self) {
        let mut queue = VecDeque::new();
        queue.push_back(self);

        while !queue.is_empty() {
            let node = queue.pop_front();

            match node {
                Some(e) => {
                    print!("{}, ", e.value);

                    if e.left.is_some() {
                        queue.push_back(*e.left.unwrap());
                    }

                    if e.right.is_some() {
                        queue.push_back(*e.right.unwrap());
                    }
                },
                None => return,
            }
        }
    }
}

It’s pretty simple stuff. The main problem is that this consumes the tree as I’ve not dealt with ownership and borrowing, two things I really need to grok in Rust.

Update
I have updated the GitHub repository with non consuming versions of all three algorithms.