Resources for parsing the Plural-Forms header from a translation file and determining which of multiple pluralities to use for a translated string. | (ns metabase.util.i18n.plural (:require [clojure.core.memoize :as memoize] [instaparse.core :as insta])) |
This is a parser for the C-like syntax used to express pluralization rules in the Plural-Forms header in translation files. For example, the Plural-Forms header for Czech is: See the original gettext docs for more details on how pluralization rules work: https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html Operators with LOWER precedence are defined HIGHER in the grammar, and vice versa. A The | (def ^:private plural-form-parser (insta/parser "expr = <s> maybe-ternary <s> <';'>? <s> <maybe-ternary> = ternary | maybe-or ternary = maybe-or <s> <'?'> <s> maybe-ternary <s> <':'> <s> maybe-ternary <maybe-or> = or-expr | maybe-and or-expr = maybe-or <s> <'||'> <s> maybe-and <maybe-and> = and-expr | maybe-eq and-expr = maybe-and <s> <'&&'> <s> maybe-eq <maybe-eq> = eq-expr | neq-expr | maybe-comp eq-expr = maybe-eq <s> <'=='> <s> maybe-comp neq-expr = maybe-eq <s> <'!='> <s> maybe-comp <maybe-comp> = lt-expr | lte-expr | gt-expr | gte-expr | maybe-add lt-expr = maybe-comp <s> <'<'> <s> maybe-add lte-expr = maybe-comp <s> <'<='> <s> maybe-add gt-expr = maybe-comp <s> <'>'> <s> maybe-add gte-expr = maybe-comp <s> <'>='> <s> maybe-add <maybe-add> = add-expr | sub-expr | maybe-mult add-expr = maybe-add <s> <'+'> <s> maybe-mult sub-expr = maybe-add <s> <'-'> <s> maybe-mult <maybe-mult> = mult-expr | div-expr | mod-expr | operand mult-expr = maybe-mult <s> <'*'> <s> operand div-expr = maybe-mult <s> <'/'> <s> operand mod-expr = maybe-mult <s> <'%'> <s> operand <operand> = integer | variable | parens <parens> = <'('> <s> expr <s> <')'> <s> = <#'\\s+'>* integer = #'[0-9]+' variable = 'n'")) |
Converts an integer or Boolean to a Boolean to use in a C-style logical operator. | (defn- to-bool [x] (if (integer? x) (if (= x 0) false true) x)) |
Converts an integer or Boolean to an integer to use in a C-style arithmetic operator. | (defn- to-int [x] (if (boolean? x) (if x 1 0) x)) |
Converts a Clojure binary function f to a C-style operator that treats Booleans as integers, and returns an integer. | (defn- op [f] (fn [x y] (to-int (f (to-int x) (to-int y))))) |
Functions to use for each tag in the parse tree, when transforming the tree into a single value. | (defn- tag-fns [n] {:add-expr (op +) :sub-expr (op -) :mult-expr (op *) :div-expr (op /) :mod-expr (op mod) :eq-expr (op =) :neq-expr (op not=) :gt-expr (op >) :gte-expr (op >=) :lt-expr (op <) :lte-expr (op <=) :and-expr #(to-int (and (to-bool %1) (to-bool %2))) :or-expr #(to-int (or (to-bool %1) (to-bool %2))) :ternary #(to-int (if (to-bool %1) %2 %3)) :integer #(Integer. ^String %) :variable (constantly n) :expr identity}) |
Returns the index of the correct translated string for a given value n, based on the value of the Plural-Forms header for a locale. Memoized to improve performance for cases where a single string is translated with a limited range of possible
values of | (def index (memoize/lu (fn [plural-forms-header n] (let [formula (second (re-find #"plural=(.*)" plural-forms-header)) tree (insta/parse plural-form-parser formula)] (insta/transform (tag-fns n) tree))) {} ;; This cache size is pretty arbitrary; can be tweaked if necessary :lu/threshold 500)) |