This library implements BCP 47 in Clojure and ClojureScript: the language tags of RFC 5646 and their matching by RFC 4647. Use it to pick the language of a response from an Accept-Language header, or to check the language of a document.
This library was developed with assistance from frontier LLMs.
The library needs Clojure 1.11+ and Java 11+. Add the latest release from
Clojars to the :deps of
your deps.edn:
dk.simongray/langtag-clj {:mvn/version "0.1.0"}
For changes that aren't released yet, use the SHA of the latest commit on
master instead. In shadow-cljs a Git dependency only works through
deps.edn, so set :deps true in your shadow-cljs.edn too.
Parse a tag with langtag/parse:
(require '[dk.simongray.langtag :as langtag])
(langtag/parse "zh-yue-Hant-HK-u-co-pinyin-x-private")
;; => {:language "zh" :extlangs ["yue"] :script "Hant" :region "HK"
;; :extensions {"u" ["co" "pinyin"]} :private-use ["private"]}
The map only has the subtags that the tag has, in canonical case. A
grandfathered tag such as i-klingon parses as
{:grandfathered "i-klingon"}. To write a map back as text, or to put a
tag in canonical case, use langtag/tag:
(langtag/tag "EN-gb")
;; => "en-GB"
(langtag/tag {:language "sr" :script "latn" :region "rs"})
;; => "sr-Latn-RS"
For a tag that isn't well formed, both functions return nil. To find out
why, use langtag/error:
(langtag/error "de-419-DE")
;; => {:reason :unexpected-subtag :subtag "DE" :index 2}
To check a tag, use langtag/well-formed?. Besides the grammar, it checks
the two rules of RFC 5646 that need no registry: no variant or extension
may appear twice.
Lookup picks the one tag of yours that best matches the user's preferences, e.g. for the language of a page:
(langtag/lookup "da-DK, en;q=0.8" ["da" "en"])
;; => "da"
(langtag/lookup "fr-CA" ["da" "en"])
;; => nil
The first argument is a language priority list: an Accept-Language value,
or a coll of ranges in order of priority such as ["da" "en"]. Lookup
falls back from da-DK to da before it tries the next range. When nothing
matches, the default is up to you, e.g. (or (langtag/lookup ...) "en").
Filtering returns every tag that matches, in order of priority.
Basic filtering
matches the start of a tag, while
extended filtering
also skips subtags in between, such as a script. Its ranges can start
with * for any language, e.g. *-CH.
(langtag/basic-filter "de-DE" ["de-DE-1996" "de-Deva" "de-Latn-DE"])
;; => ["de-DE-1996"]
(langtag/extended-filter "de-DE" ["de-DE-1996" "de-Deva" "de-Latn-DE"])
;; => ["de-DE-1996" "de-Latn-DE"]
The matching functions return the tags as you gave them. To match other things than tags, e.g. the labels of a word, pass a function that returns the tag of each (or nil when it has none):
(langtag/lookup "da-DK, en;q=0.8" :lang [{:text "dog" :lang "en"}
{:text "hund" :lang "da"}])
;; => {:text "hund" :lang "da"}
To see the priority list itself, use langtag/priority-list:
(langtag/priority-list "da, en-gb;q=0.8, en;q=0.7")
;; => [{:range "da" :weight 1.0} {:range "en-gb" :weight 0.8}
;; {:range "en" :weight 0.7}]
A weight of 0 rules a language out. A tag has the weight of the most
specific range that matches it, as in HTTP/1.1
(RFC 2616), so
"en, en-GB;q=0" accepts all English but British English.
The library works from the syntax of a tag alone. The full standard also needs the IANA Language Subtag Registry for these:
clojure -X:test # the tests on the JVM
npm install # once, for the Node tests
clojure -M:cljs compile test # the tests in Node
clojure -M:bench -m dk.simongray.langtag.bench # benchmarks on the JVM
clojure -M:cljs release bench && node target/bench.js # benchmarks in Node
Besides the examples of RFC 5646 and RFC 4647, the tests compare random tags and priority lists with the JDK. The docstring of jdk_test.clj lists where the JDK differs.
The public namespace is the root. The internal ones are under impl:
impl.syntax, which parses and writes tagsimpl.matching, which reads priority lists and matches tagsThe langtag-clj project is licensed under the MIT licence.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |