Creating emoji flags in Emacs Lisp and Bash

Vexil is an Emacs package.
Flaggify is a Bash package.

They can both convert territory and subdivision codes to their corresponding flags.

A territory is typically a “country”, with some exceptions (e.g. EU → 🇪🇺).

There’re two approaches for conversion:

With lookups

The first approach: dictionary lookups.

It’s straightforward in any language:

  1. Copy all territory codes
  2. Copy all territory flag emojis
  3. Put those pairs in some key–value structure
  4. Write a lookup function

And done.

Behold:

cat flag.csv
k,v
AC,🇦🇨
AD,🇦🇩
..... 250+ other pairs here .....
ZA,🇿🇦
ZM,🇿🇲
ZW,🇿🇼

In Bash:

code2flag() { look "$1" flag.csv | cut -d, -f2 ;}
code2flag AQ  #⇒ 🇦🇶

In Emacs Lisp, either with xht:

(defun code2flag (code)
  (-> "flag.csv"  h-read-csv  h<-csv  (h-get* code "v")))

or with native functions:

(defun code2flag (code)
  (with-temp-buffer
    (insert-file-contents "flag.csv")
    (when (re-search-forward (format "\n%s,\\(.*\\)" code) nil t 1)
      (match-string 1))))

you’ll get the same:

(code2flag "AQ") => "🇦🇶"

Without lookups

Here we’ll calculate the flag. We only need letters and some math.

Territories

We need to convert each of the two letters to something called a regional indicator, which looks like this:

A ⇒ 🇦

A regional indicator is just a unicode character.

And it turns out that the difference between it and its corresponding “normal letter” is constant.

Emacs Lisp

In Vexil, this is accomplished by this internal function:

(defun vexil--convert-str-territ-code->flag (code)
  "Convert territory two-letter CODE to flag."
  (let ((Δ (- ?🇦 ?A)))
    (with-output-to-string
      (dotimes (i 2)
        (princ (string (+ (aref code i) Δ)))))))
(vexil--convert-str-territ-code->flag "AQ") => "🇦🇶"

But you should use the public vexil-flag instead.
It uses the above, but can also deal with symbols and errors:

(vexil-flag "AQ")  => "🇦🇶"
(vexil-flag 'AQ)   => '🇦🇶
(vexil-flag 'aq)  !!> error   ; must be two uppercase letters

There are no lookups, and any two letters will do — so we could work out the flag of a non-existing country.

Behold the flag of Flandrewnia:

(vexil-flag 'FL) => '🇫🇱

Since FL is not assigned, there’s no flag.

Depending on your fonts, you’ll probably see either:

  • a question mark on a white flag
  • or two regional indicators: 🇫​🇱

Bash

Flaggify was initially conceived to use only lookups.

The goal was to find country codes in text strings, replacing only those of existing countries. Lookups allow for conservative replacements, since it includes only 250 or so flags, instead of the possible 676 (26 × 26) combinations:

flaggify r "Next week you'll go to JP, OK?"
#⇒ Next week you'll go to 🇯🇵, OK?

So the “OK” has been preserved, which is good.

But I also wanted Bash to be able to convert without lookups — so I wrote this:

f-territ()
while read -N1 -r c
do : "$(printf "$c" | od -An -t x1)"
   : "$(printf "%X" "$((0xa5 + 0x${_// }))")"
   printf "\U1F1$_"
done < <(printf "$1")

While not as readable as vexil--convert-str-territ-code->flag, it’s short and it works.

And flaggify t is simply a call to f-territ. So:

flaggify t AQ  #⇒ 🇦🇶

Subdivisions

Flags of country subdivisions can also be calculated from codes.

Subdivision codes come in two flavors. Example for Quebec, Canada:

  • ISO 3166-2: CA-QC
  • Unicode CLDR: caqc

To get from CLDR to flag, we need to:

  • start with a waving black flag: 🏴
  • follow it with each character converted to its respective tag letter
  • end with a cancel tag

And it turns out that the difference between a letter and its corresponding tag letter is constant.

Emacs Lisp

In Vexil, this is accomplished by these three internal functions:

(defun vexil--convert-str-subdiv-code->flag (code)
  "Convert subdivision CODE to flag."
  (let ((wbf #x1f3f4)                         ; WAVING BLACK FLAG
        (stl (vexil--subdiv-str->tags code))  ; TAG CHARS (lowercase, list)
        (ctg #xe007f))                        ; CANCEL TAG
    (concat `(,wbf ,@stl ,ctg))))

(defun vexil--subdiv-str->tags (str)
  "From string STR to list of their corresponding lowercase tag chars."
  (mapcar #'vexil--subdiv-char->tag
          (replace-regexp-in-string "-" "" str)))

(defun vexil--subdiv-char->tag (char)
  "From character CHAR to its lowercase tag equivalent."
  (+ (downcase char) (- #xe0061 ?a)))

And vexil-flag uses vexil--convert-str-subdiv-code->flag behind the scenes. So:

(vexil-flag 'gbsct) => '🏴󠁧󠁢󠁳󠁣󠁴󠁿  ; Scotland, United Kingdom
(vexil-flag 'US-SC) => '🏴󠁵󠁳󠁳󠁣󠁿  ; South Carolina, USA
(vexil-flag 'CA-QC) => '🏴󠁣󠁡󠁱󠁣󠁿  ; Quebec, Canada

Bash

Flaggify can also deal with subdivision flags.

Here’s my short solution:

taggify()
while read -N1 -r c
do : "$(printf "$c" | od -An -t x1)"
   printf "\UE00${_// }"
done < <(printf "${1,,}" | tr -d -)

f-subdiv() { printf "\U1F3F4$(taggify "$1")\UE007F" ;}

And flaggify s is simply a call to f-subdiv. So:

flaggify s gbsct  #⇒ 🏴󠁧󠁢󠁳󠁣󠁴󠁿
flaggify s US-SC  #⇒ 🏴󠁵󠁳󠁳󠁣󠁿
flaggify s CA-QC  #⇒ 🏴󠁣󠁡󠁱󠁣󠁿

Visualizing all territory flags

We can create a 26×26 matrix of all possible two-letter territory flag combinations — without using lookups.

(That’ll only look good if you have a font that renders as flag any pair combination, showing an “unknown flag” emoji when no territory code is assigned — probably a question mark on a white flag. Otherwise, it’ll show as a pair of regional indicator letters, which takes double the space — and that won’t look too good.)

So how do we do that?

Emacs Lisp

No need for Vexil: direct solutions for this are short and simple.

Any of these three would produce it:

;;; Using dash
(require 'dash)
(let ((az (-iterate #'1+ ?🇦 26)))
  (with-output-to-string
    (--each az
      (-each az
        ;; Want to transpose the result?
        ;; Just replace ‘->>’ with ‘->’:
        (-cut ->> <> (string it) princ))
      (terpri))))

or

;;; Using mapcs and lambdas
(let ((az (number-sequence ?🇦 ?🇿)))
  (with-output-to-string
    (mapc (lambda (1st)
            (mapc (lambda (2nd)
                    (princ (string 1st 2nd)))
                  az)
            (terpri))
          az)))

or

;;; Using dolists
(let ((az (number-sequence ?🇦 ?🇿)))
  (with-output-to-string
    (dolist (1st az)
      (dolist (2nd az)
        (princ (string 1st 2nd)))
      (terpri))))

As much as I like dash, I find the last one more readable and more elegant. It’s also faster.

To insert the string, wrap any of them with (insert…).

Bash

Ok, what about Bash?

Using Flaggify

With spaces between them:

echo {A..Z}{A..Z} | tr ' ' '\n' | sed "s/^/flaggify t /e" | paste {,,,,,,,,,,,,}{,}-

Tight:

echo {A..Z}{A..Z} | sed "s/Z /Z\n/g; s/ //g" | sed "s/^/flaggify t /e"
Directly

But wait a minute! Say we don’t have Flaggify. Couldn’t we perhaps just—

echo {🇦..🇿}{🇦..🇿}

Ahhhh. 🫤
No, we couldn’t:

{🇦..🇿}{🇦..🇿}

Well, then I guess we’ll have to do this:

for i in 1274{62..87}; do
    for j in 1274{62..87}; do
        :        "\U$(printf "%X" "$i")"
        printf "$_\U$(printf "%X" "$j") "
    done | sed "s/ $/\n/"
done

which produces this:

🇦🇦 🇦🇧 🇦🇨 🇦🇩 🇦🇪 🇦🇫 🇦🇬 🇦🇭 🇦🇮 🇦🇯 🇦🇰 🇦🇱 🇦🇲 🇦🇳 🇦🇴 🇦🇵 🇦🇶 🇦🇷 🇦🇸 🇦🇹 🇦🇺 🇦🇻 🇦🇼 🇦🇽 🇦🇾 🇦🇿
🇧🇦 🇧🇧 🇧🇨 🇧🇩 🇧🇪 🇧🇫 🇧🇬 🇧🇭 🇧🇮 🇧🇯 🇧🇰 🇧🇱 🇧🇲 🇧🇳 🇧🇴 🇧🇵 🇧🇶 🇧🇷 🇧🇸 🇧🇹 🇧🇺 🇧🇻 🇧🇼 🇧🇽 🇧🇾 🇧🇿
🇨🇦 🇨🇧 🇨🇨 🇨🇩 🇨🇪 🇨🇫 🇨🇬 🇨🇭 🇨🇮 🇨🇯 🇨🇰 🇨🇱 🇨🇲 🇨🇳 🇨🇴 🇨🇵 🇨🇶 🇨🇷 🇨🇸 🇨🇹 🇨🇺 🇨🇻 🇨🇼 🇨🇽 🇨🇾 🇨🇿
🇩🇦 🇩🇧 🇩🇨 🇩🇩 🇩🇪 🇩🇫 🇩🇬 🇩🇭 🇩🇮 🇩🇯 🇩🇰 🇩🇱 🇩🇲 🇩🇳 🇩🇴 🇩🇵 🇩🇶 🇩🇷 🇩🇸 🇩🇹 🇩🇺 🇩🇻 🇩🇼 🇩🇽 🇩🇾 🇩🇿
🇪🇦 🇪🇧 🇪🇨 🇪🇩 🇪🇪 🇪🇫 🇪🇬 🇪🇭 🇪🇮 🇪🇯 🇪🇰 🇪🇱 🇪🇲 🇪🇳 🇪🇴 🇪🇵 🇪🇶 🇪🇷 🇪🇸 🇪🇹 🇪🇺 🇪🇻 🇪🇼 🇪🇽 🇪🇾 🇪🇿
🇫🇦 🇫🇧 🇫🇨 🇫🇩 🇫🇪 🇫🇫 🇫🇬 🇫🇭 🇫🇮 🇫🇯 🇫🇰 🇫🇱 🇫🇲 🇫🇳 🇫🇴 🇫🇵 🇫🇶 🇫🇷 🇫🇸 🇫🇹 🇫🇺 🇫🇻 🇫🇼 🇫🇽 🇫🇾 🇫🇿
🇬🇦 🇬🇧 🇬🇨 🇬🇩 🇬🇪 🇬🇫 🇬🇬 🇬🇭 🇬🇮 🇬🇯 🇬🇰 🇬🇱 🇬🇲 🇬🇳 🇬🇴 🇬🇵 🇬🇶 🇬🇷 🇬🇸 🇬🇹 🇬🇺 🇬🇻 🇬🇼 🇬🇽 🇬🇾 🇬🇿
🇭🇦 🇭🇧 🇭🇨 🇭🇩 🇭🇪 🇭🇫 🇭🇬 🇭🇭 🇭🇮 🇭🇯 🇭🇰 🇭🇱 🇭🇲 🇭🇳 🇭🇴 🇭🇵 🇭🇶 🇭🇷 🇭🇸 🇭🇹 🇭🇺 🇭🇻 🇭🇼 🇭🇽 🇭🇾 🇭🇿
🇮🇦 🇮🇧 🇮🇨 🇮🇩 🇮🇪 🇮🇫 🇮🇬 🇮🇭 🇮🇮 🇮🇯 🇮🇰 🇮🇱 🇮🇲 🇮🇳 🇮🇴 🇮🇵 🇮🇶 🇮🇷 🇮🇸 🇮🇹 🇮🇺 🇮🇻 🇮🇼 🇮🇽 🇮🇾 🇮🇿
🇯🇦 🇯🇧 🇯🇨 🇯🇩 🇯🇪 🇯🇫 🇯🇬 🇯🇭 🇯🇮 🇯🇯 🇯🇰 🇯🇱 🇯🇲 🇯🇳 🇯🇴 🇯🇵 🇯🇶 🇯🇷 🇯🇸 🇯🇹 🇯🇺 🇯🇻 🇯🇼 🇯🇽 🇯🇾 🇯🇿
🇰🇦 🇰🇧 🇰🇨 🇰🇩 🇰🇪 🇰🇫 🇰🇬 🇰🇭 🇰🇮 🇰🇯 🇰🇰 🇰🇱 🇰🇲 🇰🇳 🇰🇴 🇰🇵 🇰🇶 🇰🇷 🇰🇸 🇰🇹 🇰🇺 🇰🇻 🇰🇼 🇰🇽 🇰🇾 🇰🇿
🇱🇦 🇱🇧 🇱🇨 🇱🇩 🇱🇪 🇱🇫 🇱🇬 🇱🇭 🇱🇮 🇱🇯 🇱🇰 🇱🇱 🇱🇲 🇱🇳 🇱🇴 🇱🇵 🇱🇶 🇱🇷 🇱🇸 🇱🇹 🇱🇺 🇱🇻 🇱🇼 🇱🇽 🇱🇾 🇱🇿
🇲🇦 🇲🇧 🇲🇨 🇲🇩 🇲🇪 🇲🇫 🇲🇬 🇲🇭 🇲🇮 🇲🇯 🇲🇰 🇲🇱 🇲🇲 🇲🇳 🇲🇴 🇲🇵 🇲🇶 🇲🇷 🇲🇸 🇲🇹 🇲🇺 🇲🇻 🇲🇼 🇲🇽 🇲🇾 🇲🇿
🇳🇦 🇳🇧 🇳🇨 🇳🇩 🇳🇪 🇳🇫 🇳🇬 🇳🇭 🇳🇮 🇳🇯 🇳🇰 🇳🇱 🇳🇲 🇳🇳 🇳🇴 🇳🇵 🇳🇶 🇳🇷 🇳🇸 🇳🇹 🇳🇺 🇳🇻 🇳🇼 🇳🇽 🇳🇾 🇳🇿
🇴🇦 🇴🇧 🇴🇨 🇴🇩 🇴🇪 🇴🇫 🇴🇬 🇴🇭 🇴🇮 🇴🇯 🇴🇰 🇴🇱 🇴🇲 🇴🇳 🇴🇴 🇴🇵 🇴🇶 🇴🇷 🇴🇸 🇴🇹 🇴🇺 🇴🇻 🇴🇼 🇴🇽 🇴🇾 🇴🇿
🇵🇦 🇵🇧 🇵🇨 🇵🇩 🇵🇪 🇵🇫 🇵🇬 🇵🇭 🇵🇮 🇵🇯 🇵🇰 🇵🇱 🇵🇲 🇵🇳 🇵🇴 🇵🇵 🇵🇶 🇵🇷 🇵🇸 🇵🇹 🇵🇺 🇵🇻 🇵🇼 🇵🇽 🇵🇾 🇵🇿
🇶🇦 🇶🇧 🇶🇨 🇶🇩 🇶🇪 🇶🇫 🇶🇬 🇶🇭 🇶🇮 🇶🇯 🇶🇰 🇶🇱 🇶🇲 🇶🇳 🇶🇴 🇶🇵 🇶🇶 🇶🇷 🇶🇸 🇶🇹 🇶🇺 🇶🇻 🇶🇼 🇶🇽 🇶🇾 🇶🇿
🇷🇦 🇷🇧 🇷🇨 🇷🇩 🇷🇪 🇷🇫 🇷🇬 🇷🇭 🇷🇮 🇷🇯 🇷🇰 🇷🇱 🇷🇲 🇷🇳 🇷🇴 🇷🇵 🇷🇶 🇷🇷 🇷🇸 🇷🇹 🇷🇺 🇷🇻 🇷🇼 🇷🇽 🇷🇾 🇷🇿
🇸🇦 🇸🇧 🇸🇨 🇸🇩 🇸🇪 🇸🇫 🇸🇬 🇸🇭 🇸🇮 🇸🇯 🇸🇰 🇸🇱 🇸🇲 🇸🇳 🇸🇴 🇸🇵 🇸🇶 🇸🇷 🇸🇸 🇸🇹 🇸🇺 🇸🇻 🇸🇼 🇸🇽 🇸🇾 🇸🇿
🇹🇦 🇹🇧 🇹🇨 🇹🇩 🇹🇪 🇹🇫 🇹🇬 🇹🇭 🇹🇮 🇹🇯 🇹🇰 🇹🇱 🇹🇲 🇹🇳 🇹🇴 🇹🇵 🇹🇶 🇹🇷 🇹🇸 🇹🇹 🇹🇺 🇹🇻 🇹🇼 🇹🇽 🇹🇾 🇹🇿
🇺🇦 🇺🇧 🇺🇨 🇺🇩 🇺🇪 🇺🇫 🇺🇬 🇺🇭 🇺🇮 🇺🇯 🇺🇰 🇺🇱 🇺🇲 🇺🇳 🇺🇴 🇺🇵 🇺🇶 🇺🇷 🇺🇸 🇺🇹 🇺🇺 🇺🇻 🇺🇼 🇺🇽 🇺🇾 🇺🇿
🇻🇦 🇻🇧 🇻🇨 🇻🇩 🇻🇪 🇻🇫 🇻🇬 🇻🇭 🇻🇮 🇻🇯 🇻🇰 🇻🇱 🇻🇲 🇻🇳 🇻🇴 🇻🇵 🇻🇶 🇻🇷 🇻🇸 🇻🇹 🇻🇺 🇻🇻 🇻🇼 🇻🇽 🇻🇾 🇻🇿
🇼🇦 🇼🇧 🇼🇨 🇼🇩 🇼🇪 🇼🇫 🇼🇬 🇼🇭 🇼🇮 🇼🇯 🇼🇰 🇼🇱 🇼🇲 🇼🇳 🇼🇴 🇼🇵 🇼🇶 🇼🇷 🇼🇸 🇼🇹 🇼🇺 🇼🇻 🇼🇼 🇼🇽 🇼🇾 🇼🇿
🇽🇦 🇽🇧 🇽🇨 🇽🇩 🇽🇪 🇽🇫 🇽🇬 🇽🇭 🇽🇮 🇽🇯 🇽🇰 🇽🇱 🇽🇲 🇽🇳 🇽🇴 🇽🇵 🇽🇶 🇽🇷 🇽🇸 🇽🇹 🇽🇺 🇽🇻 🇽🇼 🇽🇽 🇽🇾 🇽🇿
🇾🇦 🇾🇧 🇾🇨 🇾🇩 🇾🇪 🇾🇫 🇾🇬 🇾🇭 🇾🇮 🇾🇯 🇾🇰 🇾🇱 🇾🇲 🇾🇳 🇾🇴 🇾🇵 🇾🇶 🇾🇷 🇾🇸 🇾🇹 🇾🇺 🇾🇻 🇾🇼 🇾🇽 🇾🇾 🇾🇿
🇿🇦 🇿🇧 🇿🇨 🇿🇩 🇿🇪 🇿🇫 🇿🇬 🇿🇭 🇿🇮 🇿🇯 🇿🇰 🇿🇱 🇿🇲 🇿🇳 🇿🇴 🇿🇵 🇿🇶 🇿🇷 🇿🇸 🇿🇹 🇿🇺 🇿🇻 🇿🇼 🇿🇽 🇿🇾 🇿🇿

Enjoy flags!

📆 2025-W44-7📆 2025-11-02