Multilingual blog on DocPad

I write my blog in two languages. But they’re just two builds of the same DocPad installation with different settings.

You can use this blog’s source code as an example of this technique. Below I’ll describe all necessary steps to translate your blog’s content and user interface.

Translating posts

Create separate folders for each language’s blog content. In my case it’s src/documents_en and src/documents_ru (for English and Russian correspondingly).

Then add this code to your DocPad config (docpad.coffee):

docpadConfig = {
  ...
  environments:
    en:
      documentsPaths: ['documents_en']
      outPath: 'htdocs_en'
    ru:
      documentsPaths: ['documents_ru']
      outPath: 'htdocs_ru'
  ...
}

Now you can run or generate blog in desired language:

docpad run --env en  # Run local server with English version
docpad generate --env ru  # Generate files for Russian version

Translating unser interface

Create YAML files for every language.

For example, src/lang/en.yml:

lang: en
url: http://blog.sapegin.me
title: Artem Sapegin’s Blog
poweredBy: Powered by <a href="{dp}" class="link">DocPad</a>
visibleTags:
  - tools
  - html
  - css
tagNames:
  tools: Tools
  html: HTML
  css: CSS

You can add here all the data you want to translate.

You’ll also need few helper functions, so add them to DocPad config file:

YAML = require 'yamljs'
moment = require 'moment'
_ = require 'lodash'
 
pluralTypes =
  en: (n) -> (if n isnt 1 then 1 else 0)
  ru: (n) -> (if n % 10 is 1 and n % 100 isnt 11 then 0 else (if n % 10 >= 2 and n % 10 <= 4 and (n % 100 < 10 or n % 100 >= 20) then 1 else 2))
 
docpadConfig = {
  templateData:
    ...
    # Localized date
    pubDate: (date) ->
      moment(date).format('LL')  # December 23 2013
 
    # Translated string
    # Will return input string if thanslation not found
    # You can use simple templates: @_ 'Lorem {num} ipsum', num: 42
    _: (s, params=null) ->
      params ?= []
      s = @site[s] or s
      s.replace /\{([^\}]+)\}/g, (m, key) ->
        params[key] or m
 
    # Plural form: @plural(3, 'dog|dogs')
    plural: (n, s) ->
      ((@_ s).split '|')[pluralTypes[@site.lang](n)]
 
  events:
    generateBefore: (opts) ->
      # Get current language from DocPad’s environment
      lang = @docpad.getConfig().env
      # Load translated strings for current language
      strings = YAML.load("src/lang/#{lang}.yml")
      _.merge(@docpad.getTemplateData().site, strings)
      # Configure Moment.js
      moment.lang(lang)
 
    ...
}

Install libraries used in above code from npm:

npm install --save-dev yamljs moment lodash

You can find plural functions for you language in polyglot.js.

Now you need to replace all local specific data in your templates with this helpers.

Regular strings:

<a href="/about"><%= @_ 'About' %></a> <%- (@_ 'poweredBy', dp:
'http://docpad.org/') %>

Plurals:

<%= @_ '{num} {posts}', num: documents.length, posts:
@plural(documents.length, 'post|posts') %>

And dates:

<%= @pubDate @document.date %>

Language switcher

Add few lines to your YAML files:

transLang: ru
transUrl: http://nano.sapegin.ru
translation: По-русски

And few to docpad.coffee:

docpadConfig = {
  templateData:
    ...
    # URL of a page in other language or homepage if there’s no translation of that page
    translationUrl: ->
      if fs.existsSync "src/documents_#{@site.transLang}/#{@document.relativePath}"
        "#{@site.transUrl}#{@document.url}"
      else
        @site.transUrl
    ...
}

Add a link to a template:

<a href="<%= @translationUrl() %>"><%= @_ 'translation' %></a>

Server configuration

The last thing you need is to point your server to the right folders with published files. I use different hosts for each language but you could use subfolders but it’s beyond the scope of this blog post.

P. S. Here is another approach by Camille Bissuel.