Localizing a Ghost Theme

Lately I've spent some time setting up a blog for a friend. Ghost is where it's at these days when it comes to blogging — unless you're a nerd like me who loves to mess around with static site generators every now and then. 😅

Ghost provides many beautiful themes out of the box but most of them don't seem to support localization, which would be a nice thing to have for my friends blog. So I did some digging and essentially it comes down to:

  1. Using the {{t}} helper for any strings that should be localized (docs)
  2. Providing a locales/de.json file with mappings to localized strings

The Dawn theme that we were using was pretty light on strings that needed localization so with a little bit of vim-sandwich magic and a custom mapping I was able to update it to use the {{t}} helper in maybe half an hour.

xmap <Leader>t sai{{t "<CR>"}}<CR>

With the visual mapping above all I needed to do is select the text that I want to localize and hit <space>t.

selecting some text and using vim-sandwich to wrap it in {{t}}

Now the last step was to create the initial locales/en.json file. Later on I will use the English one as a template to create a German localization.

Since typing out the more than a dozen strings manually would have been boring I instead wrote a babashka script to generate the English locales file for me.

#!/usr/bin/env bb
(require '[clojure.java.io :as io]
         '[cheshire.core :as json]
         '[babashka.fs :as fs])

(def entries
  (->> (fs/glob "." "**/*.hbs")
       (map (fn [p] (slurp (io/file (str p)))))
       (mapcat (fn [file-contents]
                 (map second (re-seq #"\{\{t \"(.*)\"\}\}" file-contents))))
       (map (fn [s] [s s]))
       (into {})))

(println (json/generate-string entries {:pretty true}))

This script essentially finds all usages of the {{t}} helper and spits out a JSON object where the keys are identical to the values (i.e. if the theme was English, that would be the locales/en.json file).

Babashka makes figuring this stuff out such a breeze because I can just incrementally build this out in a connected babashka nREPL session instead of changing the file and running the script as a whole on every change. REPLs for the win!

In the end I created this little PR to the theme.

Other Posts

  1. Clojure Macros: Creating vars from a mapFebruary 2021
  2. Promises in a ClojureScript REPLMay 2020
  3. Improved Support for Foreign Libs in cljdocMay 2020
  4. Static Blogging, Some Lessons LearnedMay 2020
  5. Working with Firebase Documents in ClojureScriptSeptember 2019
  6. 4 Small Steps Towards Awesome Clojure DocstringsJanuary 2019
  7. Sustainable Open Source: Current EffortsJanuary 2018
  8. Maven SnapshotsJune 2017
  9. Requiring Closure NamespacesMay 2017
  10. Simple Debouncing in ClojureScriptApril 2017
  11. Making Remote WorkMarch 2017
  12. Just-in-Time Script Loading With React And ClojureScriptNovember 2016
  13. Props, Children & Component Lifecycle in ReagentMay 2016
  14. Om/Next Reading ListNovember 2015
  15. Parameterizing ClojureScript BuildsAugust 2015
  16. ClojureBridge BerlinJuly 2015
  17. Managing Local and Project-wide Development Parameters in LeiningenJune 2015
  18. Formal Methods at AmazonApril 2015
  19. (lisp keymap)February 2015
  20. CLJSJS - Use Javascript Libraries in Clojurescript With EaseJanuary 2015
  21. Why Boot is Relevant For The Clojure EcosystemNovember 2014
  22. S3-Beam — Direct Upload to S3 with Clojure & ClojurescriptOctober 2014
  23. Patalyze — An Experiment Exploring Publicly Available Patent DataOctober 2014
  24. Running a Clojure Uberjar inside DockerSeptember 2014
  25. Using core.async and Transducers to upload files from the browser to S3September 2014
  26. Emacs & VimJuly 2014
  27. Heroku-like Deployment With Dokku And DigitalOceanMarch 2014
  28. Woodworking MasterclassesFebruary 2014
  29. Early Adopters And Inverted Social ProofFebruary 2014
  30. Living SmallFebruary 2014
  31. Sending You a TelegramJanuary 2014
  32. Running a Marathon, Or NotJanuary 2014
  33. Code SimplicityJanuary 2014
  34. What do we need to know?December 2013
  35. Sculley's DiseaseDecember 2013
  36. A Resurrection PostDecember 2013
  37. A Trip To The USSeptember 2013
  38. Analytics DataApril 2013
  39. Asynchronous CommunicationApril 2013
  40. From Zero to Marathon in Six MonthtsMarch 2013
  41. Git Information in Fish Shell’s PromptDecember 2012
  42. When We Build StuffAugust 2012
  43. Models, Operations, Views and EventsJuly 2012
  44. The Twelve Factor AppJune 2012
  45. Paris And BackMay 2012
  46. A Friend Is Looking For A Summer InternshipMay 2012
  47. Kandan Team ChatMay 2012
  48. Entypo Icon SetMarch 2012
  49. Startups, This Is How Design WorksMarch 2012
  50. Hosting A Static Site On Amazon S3February 2012
  51. Exim4 Fix Wrongly Decoded Mail SubjectJanuary 2012