FS FormScript

Text and languages

Every label in a form is text somebody has to read, and every text a person reads might have to be in more than one language. FormScript keeps them out of the logic and in one place.

The text block

forms
form loan version = "1.0" {
  text {
    "loan.title":   "Loan application"
    "loan.amount":  "Loan amount"
    "loan.monthly": { "default": "Monthly payment", "ar": "القسط الشهري" }
    "loan.errors.too_small": "The amount is too small"
  }

  field amount  { type = number  label = t("loan.amount") }
  field monthly { type = number  label = t("loan.monthly") }
}

A key is either a string or a map of locales:

forms-fragment
"loan.monthly": { "default": "Monthly payment", "ar": "القسط الشهري" }

"default" is the text used when the current language has no entry of its own. The locale codes are the usual ones — "ar", "en", "fr", and so on.

Reading a label

t("loan.amount") looks the key up in the current language.

forms-fragment
field amount {
  type  = number
  label = t("loan.amount")
  hint  = t("loan.amount_hint")
}

Text lives in a text block, never inline in a label. Writing label = "Loan amount" works, and the specification warns about it — W-TEXT-01 — because it is a habit worth breaking early. A label written inline cannot be translated without rewriting the document.

Where else text appears

Three more places, all of them the same idea:

forms-fragment
field birth_date {
  type = date
  label = t("applicant.dob")
  hint  = t("applicant.dob_hint")
  validate {
    custom = age(self) is between 18 and 75
              ? true
              : t("applicant.dob_required")
  }
}
  • the label — the name of the question;
  • the hint — a line of help under it;
  • a custom validation message — the text of the complaint.

And t() works in a page or a section label, and in a choice:

forms-fragment
choice employment {
  label = t("applicant.employment")
  type  = enum("employed" = t("employment.employed"), "self" = t("employment.self"))
}

Formatting

Two builtins write values out for a person to read:

forms-fragment
field amount   { type = number value = formatMoney(pmt(0.05 / 12, term, principal), "GBP") }
field due      { type = string value = formatDate(start_date, "d MMMM yyyy") }
  • formatMoney(n, currency) — an amount, with the currency written out;
  • formatDate(d, pattern) — a date, in the pattern you give.

Both are presentation: what they produce is a value for a person to read, and the data plane keeps the number and the date.

The display type

There is one more thing worth knowing, and it is the reason formatMoney exists at all.

The specification has a display value: a value that exists only to be shown to a person, and never to be stored. A label built with t() is a display value; a formatted amount is a display value.

The distinction matters because a display value can be re-read: t("loan.monthly") gives the text in the current language, and the runtime knows it is a presentation of the number and not the number itself. So a form can be shown in Arabic without the data being Arabic.

A whole form, in two languages

forms
form loan version = "1.0" {
  text {
    "loan.title":   { "default": "Loan application", "ar": "طلب قرض" }
    "loan.amount":  { "default": "Loan amount", "ar": "مبلغ القرض" }
    "loan.monthly": { "default": "Monthly payment", "ar": "القسط الشهري" }
  }

  field amount  { type = number  label = t("loan.amount") }
  field monthly { type = number  label = t("loan.monthly") }

  output {
    amount: number
    monthly: number
  }
}

The logic did not change. The output document did not change. The data is the same in every language, and the text is the only thing that changed — which is the whole point of keeping the text in one block.