A shortcode for dynamic abbreviations
Last updated: Mar 21, 2024
Hugo has a scratchpad to change a page’s state.
This shortcode uses the scratchpad to conditionally format an abbreviation:
This shortcode ensures that an abbreviation is always defined on a page and always has an HTTL abbr
element.
It also saves writers from writing verbose, error-prone HTML and needing to track when a term first appears on a page.
For example, this text uses a shortcode:
_{{< abbr "mttr" >}}_ is a basic measure
of maintainability in repairable systems.
If two systems have equal types
of failure and equal failure rates,
the system with the lower {{< abbr "mttr" >}}
has higher availability.
Let's say it one more time for no reason:
{{< abbr "mttr" >}}.
On the page, it renders as follows. Notice the abbreviations have tooltips:
is a basic measure of maintainability in repairable systems. If two systems have equal types of failure and equal failure rates, the system with the lower has higher availability. Let’s say it one more time for no reason: .
Abbreviations should generally be avoided. But when they are necessary, this shortcode improves the experience for both readers and writers.
Hugo requirements
The following examples are ready to copy and paste. To use them, change the terms and abbreviations for your lexis.
But, to modify these or make your own versions, it helps to know the following Hugo concepts:
Steps to implement a conditional shortcode
The general procedure is as follows:
Define a term and its abbreviation. To facilitate reuse, store the data in variables or key-value pairs.
{{ $term := "Mozilla Developer Network" }} {{ $abbreviation := "MDN" }}
Use another variable to give each term a unique counter.
{{ $count_id := print ( $abbreviation | lower ) "_count"}}
In a shortcode, create a conditional statement based on the counter’s value. If the count has started, write the HTML abbreviation.
{{ if gt ( .Page.Scratch.Get $count_id) 0 }} <abbr title="{{- $term -}}">{{- $abbreviation -}}</abbr> {{ .Scratch.Get $count_id }}
In the
else
block, write the full phrase with its abbreviation. Then use the scratch pad to set the counter to1
.{{ else }} {{ $term }} ({{ $abbreviation }}) {{ .Page.Scratch.Set ($count_id) 1 }} {{ end }}
I combined these snippets in a shortcode located at layouts/shortcodes/abbreviations/mdn.html
.
I can call it as follows:
> Do you know the {{< abbreviations/mdn >}} docs?
>
> Yes! I love the {{< abbreviations/mdn >}} docs.
It renders as follows:
Do you know the Mozilla Developer Network (MDN) docs?
Yes! I love the MDN docs.
However, if you have multiple terms, this shortcode has avoidable boilerplate. The following sections describe some better, more general solutions.
Design decisions (and examples)
Hugo is flexible about how it handles variables and logic, so you have many ways to implement this shortcode. The following sections outline some approaches to take. When possible, I recommend keeping terms in a separate data file.
Keep terms in a data file
One method is to use a data file to store your terms in an array. I like this approach because it separates concerns and because the list can be reused as the source of other templated pages (like glossaries).
Write your abbreviations in
data/terms.yaml
:- term: Transmission Control Protocol abbreviation: TCP - term: User Datagram Protocol abbreviation: UDP
Range over the abbreviations in a shortcode.
<!--- variables based on shortcode argument --> {{ $entry := (.Get 0 | lower) }} {{ $count_id := print $entry "_count"}} <!-- range over data file --> {{ range $.Site.Data.terms }} <!-- Find matches --> {{ if eq ( .abbreviation | lower) $entry }} {{ if gt ( $.Page.Scratch.Get ( $count_id )) 0 }} <abbr title="{{- .term -}}">{{- .abbreviation -}}</abbr> {{ else }} {{ .term }} ({{ .abbreviation }}) {{ $.Page.Scratch.Set ( $count_id ) 1 }} {{ end }} {{ end }} {{ end }}
Call it using the abbreviation as an argument.
The {{< abbreviations "tcp" >}} is not the {{< abbreviations "udp" >}}. Again, {{< abbreviations "tcp" >}} is not {{< abbreviations "udp" >}}.
This renders as:
The Transmission Control Protocol (TCP) is not the User Datagram Protocol (UDP) . Again, TCP is not UDP .
The drawback is that it requires a little more conditional coding, and the syntax to call it is a bit more complex.
A shortcode for each file
If you want each acronym to use its own file or shortcode call, try this approach:
In a partial, write the reusable logic. This file is called
abbreviation.html
:{{ $count_id := print ( .acronym | lower ) "_count"}} {{ if gt ( .context.Scratch.Get $count_id) 0 }} <abbr title="{{ .term }}">{{- .acronym -}}</abbr> {{ .Scratch.Get $count_id }} {{ else }} {{ .term }} ({{ .acronym }}) {{ .context.Scratch.Set ($count_id) 1 }} {{ end }}
In a shortcode, define the term and abbreviation, and then call the partial.
{{ $term := "transmission control protocol" }} {{ $abbreviation := "tcp" }} <!-- reusable boilerplate --> {{ partial "abbreviation.html" (dict "term" $term "abbreviation" $abbreviation "context" .page ) }}
The call syntax is slightly simpler, but I don’t like much else about it. Still, I’m happy I figured out how to do this because now I know how to pass variables and context to a partial (thanks to help from the Hugo forums).
In-line
If you plan to use the shortcode exactly once, you can use an inline shortcode and define terms in the page parameters. Setting terms in the page parameters probably has a number of reasonable uses, but I can imagine only two times where an inline shortcode makes sense for this use case:
- If you are writing a long blog with acronyms that you never plan to use again.
- If you are writing in a modularized set-up where it’s difficult to change the
layouts
ordata
directories.
Nevertheless, if you want to do this, read the inline example.
Discussion and links
This shortcode is handy for docs where abbreviations for specialized terms are often used. Perhaps you could combine it with a prose linter to enforce formatting.
On broader note, this shortcode hints at something more powerful: a way to conditionally format text based on a mutable page state. Now that I’ve figured out these small examples, maybe I’ll discover some greater use of the Hugo scratch pad.
For more reading, these helped me make this post.
- Blog posts about Hugo
.Scratch
- The forum post I made about how to share logic and variables between shortcodes.