[common-words]livev0.1.1

Common words

Print-form snippets that write an amount of money out in words, in Ukrainian, English or German: «одна тисяча двісті тридцять чотири гривні 56 копійок». Other modules' print templates include them; the languages and the currency names live here, so a fix is a module update, not a platform release.

Try it in your workspacefree workspace · you confirm before anything installs

Common Words

Print-form snippets that write an amount of money, or a plain quantity, out in words, in Ukrainian, English or German.

The platform has no number-to-words filter on purpose: the languages and the currency names live in this module, so a fix or a new currency is a module update, not a platform release. A worked template is in common-words-demo.

Amount, currency Language Result
1234.56 UAH uk одна тисяча двісті тридцять чотири гривні 56 копійок
1234.56 USD en one thousand two hundred thirty-four US dollars 56 cents
1234.56 EUR de eintausendzweihundertvierunddreißig Euro 56 Cent

Use it in a print template

Set the inputs, then include the snippet. Both lines go into the template that prints the document (an invoice, a receipt, ...), not into this module:

{% assign words_amount = total %}
{% assign words_currency = currency_cd %}
{% assign words_lang = 'en' %}
{% include 'common-words.money' %}
Input Required Meaning
words_amount yes The amount. A numeric field, not text: the server's Liquid engine does not turn a string into a number. A negative amount starts with «мінус» / «minus».
words_currency yes An ISO 4217 code, any case (UAH, usd).
words_lang no uk (default), en or de. Any other value prints [common-words: unknown language xx] instead of silently writing another language.
words_cap no true starts the text with a capital letter (One thousand ...). Liquid's own capitalize lowercases the rest of the text, so use this.

The snippet prints the text and nothing else, so it can sit inside a paragraph or a table cell: <p>Total: {% include 'common-words.money' %}</p>.

Where the values come from

  • Amount and currency are fields of the document you print. Use their column codes.
  • Language is whatever your document knows: a fixed word ('de'), a dropdown on the document, a setting. A dropdown prints as its label ("Deutsch"), not its code, so read the code from _raw: {% assign words_lang = _raw.lang %}.
  • A reference column (a currency picked from a list) prints as its display text. That is the code only if the referenced entity's toString is the code; otherwise keep the code in a plain column of the document.

One fixed language

When a form is always in one language, include that snippet directly and skip words_lang:

{% include 'common-words.money_uk' %}

money_uk, money_en and money_de take the same words_amount, words_currency and words_cap.

A quantity in words

For a count of things rather than money: «двадцять одна штука», «twenty-one pieces», «einundzwanzig Stück».

{% assign words_number = qty %}
{% assign words_unit = 'штука|штуки|штук' %}
{% assign words_gender = 'f' %}
{% assign words_lang = 'uk' %}
{% include 'common-words.quantity' %}
Input Required Meaning
words_number yes The quantity, a numeric field. A fraction is rounded half up to a whole number; a negative one starts with «мінус» / «minus».
words_unit no The unit's forms joined by |: three for Ukrainian (1, 2–4, 5+), two for English and German (1, other). One form is used for every number. Without it only the number is written.
words_gender no m (default), f or n: «один / одна / одне», «ein / eine / ein». English ignores it.
words_lang, words_cap no As for money.
Quantity Language Result
21, штука|штуки|штук, f uk двадцять одна штука
2, кілограм|кілограми|кілограмів, m uk два кілограми
21, piece|pieces en twenty-one pieces
21, Stück de einundzwanzig Stück

quantity_uk, quantity_en and quantity_de can be included directly, like the money ones. A thousand is always feminine in Ukrainian («дві тисячі»), whatever words_gender says.

What it does

  • Minor units are written as two digits with their name («56 копійок», «56 cents», «56 Cent»). A currency with no minor unit (JPY) has none, and the amount is rounded half up to a whole number: 1234.56 JPY is «one thousand two hundred thirty-five yen».
  • Rounding is half up to the minor unit. Amounts are money, so two decimals; a third decimal that is exactly a tie can round differently in print and in the preview.
  • Zero is spelled («нуль гривень 00 копійок», «zero ...», «null ...»).
  • Grammar follows each language: Ukrainian picks the singular, the 2–4 form or the plural by the last digits and the noun's gender («одна гривня», «дві гривні», «п'ять гривень», «дві тисячі»); English pluralises except for 1; German writes below a million as one word, uses «eine Million», and says «ein Euro» / «eine Hrywnja» for 1.
  • Up to 999 trillion.
  • A currency the module does not list prints the number and its code, minor units in digits: «three SEK 00».

Currencies

UAH, USD, EUR, GBP, PLN, CHF, CAD, AUD, JPY, RUB.

Add a currency

Each language file starts with a case on the currency code. Add a branch to each of print_templates/money_uk.html, money_en.html and money_de.html:

{%- when 'CZK' -%}
	{%- assign w_unit = 'koruna|koruny|korun' -%}
	{%- assign w_minor = 'halíř|haléře|haléřů' -%}
  • w_unit / w_minor are the noun forms joined by |. Ukrainian takes three (one / few / many: 1, 2–4, 5+), English and German two (one / other). A noun that never changes takes one form («євро», «Euro»).
  • w_fem = true (Ukrainian) or w_gender = 'f' / 'n' (German) when the unit is feminine / neuter: it decides «одна» / «eine» and «ein».
  • w_dec = 0 when the currency has no minor unit.
  • Leave w_minor out when there is no minor-unit word to print.

Bump the module version in manifest.json and add a changelog/{version}.md. Tenants get the new text on the next module upgrade.

Add a language

  1. Copy print_templates/money_en.html (two noun forms) or money_uk.html (three) to money_xx.html and rewrite the number words, the group assembly and the currency case for that language. The skeleton (splitting the amount into whole and minor units, the sign, the loop over the thousands groups, the capital letter) stays; the grammar is what changes.
  2. Do the same for quantity_xx.html, from quantity_en.html or quantity_uk.html.
  3. Declare both in ui/print_templates.json as a snippet with "file": "money_xx.html" / "quantity_xx.html".
  4. Add a when 'xx' line to print_templates/money.html and quantity.html.
  5. Add cases for it to clients/packages/data-ui/test/common-words.cases.json (lang, v, cur, expected) and common-words.quantity-cases.json (lang, v, unit, gender, expected) and run both test suites below. Check every expected against the language's grammar by hand: a wrong value in the file is a wrong value in both engines.

Write snippets for both engines

A snippet runs in two Liquid engines: Scriban on the server (print) and LiquidJS in the browser (preview and print from a card). Output must be identical, so:

  • No floor / round on big numbers. Scriban's floor returns a 32-bit int. Split with modulo instead (see the top of any money_xx.html).
  • No number from split or slice. Scriban keeps it a string and "1000" | plus: 0 concatenates. Branch with case / if, or use forloop and (0..n) ranges.
  • No {%- comment -%} with trim dashes. Scriban prints the comment. Use {% comment %} ... {% endcomment -%}.
  • No parentheses in conditions; Liquid has none. Use nested if.
  • Trim whitespace with {%- ... -%} so the snippet prints only the text.

Tests

The same case list runs against both engines:

# server: Scriban
cd server && dotnet test tests/dForge.Api.Tests --filter "FullyQualifiedName~CommonWords"

# client: LiquidJS
cd clients/packages/data-ui && pnpm exec vitest run src/lib/print/common-words.test.ts

The server run also installs the module into a fresh tenant and checks that every stored snippet is the file shipped here.

/ explore more

Browse the full
module catalog.

Every module is metadata. Install in one click, customize freely, or fork it into Studio and reshape it for your business.