Custom/Multi-language theme strings?

Hijacking the translation function to let users customize their strings.

Custom/Multi-language theme strings?
Photo by Nick Fewings / Unsplash

I have an oddball problem most of you probably don't have. I manage a single Github theme repo that auto-deploys to about 30(?) different small newsroom websites, managed by ~60 different people, with at least 90 different opinions on well... everything. (I love my Tiny News Collective fam, but oh my, some days it's a lot.) Sometimes I end up making a custom copy of the theme, for a newsroom whose needs just don't match what's in TNC's house theme. But then I've got to deal with keeping another thing up to date. That seems ok if we're doing a big rearrangement of everything, but when they just want the subscribe button to say something different? Too much.

Yes, I know about custom theme variables. I don't have enough of them for all the different spots someone would like to tweak the wording.

Did I mention that I really really don't want to maintain several dozen long-running branches?

It occurred to me recently that maybe I needed to exploit the translation helper. After all, changing which string displays is exactly what it does, right?

So I set out to do that. Unfortunately, there's no front-end way to load a custom set of translations that doesn't involve loading the whole theme, and we're doing an autodeploy from a single branch on Github.

So, I had to get clever. And by clever, I mean hacky.

This approach broadly parallels the one I took for The Initium, but there I'm detecting which language should be served based some combination of slug/URL/tag, so that they can run two separate languages sitewide. [This works for everything but email.]

It works like this: All t-wrapped strings become calls to a tr partial, like so:

Before:
{{t "Subscribe"}}

After:

{{> tr trString="Subscribe"}} 

(picking a different variable name would make this a little more compact)

And then we add new partials:

partials/tr.hbs:

{{#foreach (concat trString "_" @custom.mylocale)}}
{{> "tr-inner" 
   customTranslatedString=(t this) 
   rawString=this
   originalTranslatedString=(t ../trString)
}}
{{/foreach}}

Why #foreach? it sets 'this'. And then I use the nested t-inner partial to set my variables, so that I only call t twice.

partials/tr-inner.hbs

{{#match customTranslatedString rawString}} 
   {{log "no custom translation"}}
   {{originalTranslatedString}}
{{else}} 
   {{log "custom translation found"}}
   {{customTranslatedString}}
{{/match}}

The #match checks if the translated custom string is untranslated, meaning that the locale file doesn't contain it. If that occurs, we return the default translated string. For the example below, Subscribe -> "Subscribe", Subscribe_informal -> "Sign up, yo!", and Subscribe_formal -> "Subscribe".

In package.json, add an extra object under custom:

            "mylocale": {
                "type": "select",
                "options": ["informal", "formal"],
                "default": "informal"
            },

locales/en.json:

{
    "Homepage": "Homepage",
    "Homepage_informal": "My slappin' homepage",
    "Homepage_formal": "My very formal homepage"
    "Subscribe": "Subscribe",
    "Subscribe_informal": "Sign up, yo!",
}

End result? If someone wants a couple of custom strings, I can just assign their site an option in package.json and add their string overrides to my en.json. Redeploy and problem sorted! No more forks/branches just to change some words!

No, I haven't deployed it yet. Still thinking about it.