Setting Ghost excerpt length based on language tag

I'll preface this post by stating that:

  1. I have spent a great deal of time in my life thinking about internationalization and localization, and this post addresses neither
  2. I am very new to the Ghost blogging platform and making themes for it

With those two things in mind, I'm going to show one way that you can handle Ghost's excerpt length for a blog that publishes articles in different languages to the same feed.

This is admittedly an edge case (why would you write in Spanish and Chinese in the same blog feed?). But the blog you are reading is such an edge case (for English and Japanese).

If you have better ideas about how to handle this specific issue, feel free to let me know.

Contents


The setup

You can see my code for reference on GitHub.

Let's start with a very clean slate for a Ghost theme. Within the theme, in addition to all the standard requirements, you have a partials/loop.hbs file that looks like this:

{{#foreach posts}}
<article>
  <header>
    <h2>{{title}}</h2>
  </header>

  <section></section>
</article>
{{/foreach}}

This loop.hbs template is where, say, on the main blog page, you would get a list of blog posts that you might be able to click on to go to each individual article.

Everything I talk about in this post is going to happen within the <section> tag above.

The problem

Within the <section> tag, most themes are going to have something like this (super simplified here for clarity):

<p>{{excerpt words="26"}}</p>
<p>{{tags}}</p>

In other words, for each blog post we are asking Ghost to display the excerpt (limited to 26 words), followed by all of the tags on the post.

In reality, you'd also have a "Read more" link. I'm omitting it here for focus.

The result would look like this:

This is fine... until we write a post in a language that doesn't have "words" [1], like Japanese:

Yikes.

The problem is that the excerpt helper's words attribute doesn't work for Japanese. [1:1]

Going to the other extreme

At this point we might check the Ghost docs for excerpt and find that there is a characters attribute. Let's try it out:

<p>{{excerpt characters="55"}}</p>
<p>{{tags}}</p>

Womp womp:

The Japanese excerpt looks good. But now we are overaggressively shortening the English one.

Basing the excerpt length on tags

Handlebars is the templating language for Ghost. Handlebars gives us access to just enough logic to solve the problem at hand in the template.

Since the author of the example blog in the screenshots has given each post a tag for the language the post is written in, we can use the has helper with a tag attribute to check for the language tag. From there, we adjust excerpt length accordingly:

{{#has tag="English"}} // Is it English?
<p>{{excerpt words="26"}}</p> // 26 words please!
{{else}}
  {{#has tag="日本語"}} // Is it Japanese?
  <p>{{excerpt characters="55"}}</p> // 55 chars please!
  {{/has}}
{{/has}}
<p>{{tags}}</p>

In other words, we're saying:

  • If the post has a tag of "English", make the excerpt 26 words long
  • If the post has a tag of "日本語" (Japanese), make the excerpt 55 characters long

Here's what we get:

Nice.

Lastly, be sure to add a base case, in case an author forgets to add a language tag:

{{#has tag="#English"}}
<p>{{excerpt words="26"}}</p>
{{else}}
  {{#has tag="#日本語"}}
  <p>{{excerpt characters="55"}}</p>
  {{else}}
  <p>{{excerpt characters="11"}}</p> // Base case
  {{/has}}
{{/has}}
<p>{{tags}}</p>

Without a base case, no excerpt would show for posts without a language tag.

Hiding the language tags because reasons

It could be that you want to hide the language tags for some reason. Ghost can make your dreams come true with internal tags.

Read the article for details (tl;dr: enable the beta feature and prepend tag names with # hash symbols).

The resulting template code would look like this:

{{#has tag="#English"}} // # means it's an internal tag
<p>{{excerpt words="26"}}</p>
{{else}}
  {{#has tag="#日本語"}} // Same here
  <p>{{excerpt characters="55"}}</p>
  {{else}}
  <p>{{excerpt characters="11"}}</p>
  {{/has}}
{{/has}}
<p>{{tags}}</p>

Resulting in:

Now those language tags aren't showing to the user any more, nor are they anywhere to be found in the markup.

Caveats

This is not i18n or L10n.

As I mentioned at the outset, this approach is for the rare blog that writes in multiple languages for the same feed. I doubt that tons of successful blogs do this because it's confusing for the reader (sorry, reader!).

If you want to run a truly multilingual website, you should look into internationalization (i18n) and localization (L10n) options for Ghost. I don't know enough about Ghost to help you there yet.

Handlebars doesn't seem to have else has

If you're one of those annoying smart and attractive people who can write in all the languages, this approach will not scale nicely in the template code, as far as I can tell.

To keep the logic flat, you would need something like a else has helper (as in, if/else if). The has helper doesn't have this.

Instead, you would need to nest your has helpers. This is the example from the Ghost docs:

{{#has tag="photo"}}
    ...post has the photo tag...
{{else}}
    {{#has tag="video"}}
        ...post has the video tag...
    {{else}}
        {{#has tag="audio"}}
            ...post has the audio tag...
        {{else}}
            ...post has none of the mentioned tags...
        {{/has}}
    {{/has}}
{{/has}}

So you can imagine that if you want to use the approach I'm describing in this post for a blog written in 8 languages, that's quite a bit of has nesting you're going to have to do.

Update: Fabian Becker pointed out on the Ghost Slack that an alternative option to nested has logic is to keep multiple has blocks on the same level, then setting up a base case with negation (^has). See his Gist here for an example.

Multiple languages on a single post will get tricky

If you need to optionally tag multiple languages on a single post, you're in for a logic game.

The has helper allows for AND and OR logic for tags, but the resulting code to handle all possible combinations could get ugly fast.

At that point, I might be looking for another approach entirely.


  1. For the record, it seems to be counting the Romaji alphanumerics, though I'm unsure why it stops where it does in this case, on a full-width closed parenthesis. For the subject at hand, it doesn't matter. ↩︎ ↩︎