PDFPipe

Formatting and locale / Language in generated text

Units on a document, and the quantity with no unit at all

A measured quantity printed without its unit, in the wrong system for the reader, or separated from its unit by a line break.

The defect

Three failures, in increasing order of how often they happen. A quantity printed with no unit at all, which is common in a column headed with a unit that then gets abbreviated away or repeated inconsistently. A quantity printed in a system the reader does not use, which they will either convert wrongly or not at all. And a quantity separated from its unit by a wrap, which turns one value into two tokens and is the reason a figure gets read as the wrong magnitude. All three are worse in a document than on a screen, because a document is often the record used to check a physical thing against.

What it does to the document

The unit is what makes a narrow numeric column overflow, which is why it gets dropped from the rows first. Putting it in the header solves the width problem and the consistency problem at once, and it is the reason the quantity-and-unit column page recommends separate cells rather than a concatenated string.

The decisions

Reasons rather than a description of the code. Formatting rules are data that changes, so most of these are about taking the rule from a maintained source rather than encoding today's answer.

  • Print the unit once in the column header and never on the rows, or on every row and never in the header. Both work; mixing them is what produces rows with no unit.
  • Bind the value to its unit with a non-breaking space so they cannot be separated by a wrap.
  • Format with a library that takes a locale and a unit, so the abbreviation and its placement come from data rather than from a template.
  • Where the document crosses systems, give the converted value its own column rather than putting it in brackets, and round it to the precision of the source rather than to the machine's.
  • Use the unit the measurement was taken in as the primary, and treat anything else as derived, so the authoritative figure is the one that was measured.
  • Do not abbreviate to a symbol that is ambiguous in the destination. A unit that means two things is worse than a longer name.

In code

The formatting happens before the markup exists, so this is mostly code rather than CSS. The wrong version is kept in a comment where the wrong version is the thing people write.

js
const qty = 1250;

// Locale-aware unit formatting: the abbreviation and its placement come
// from data rather than from a template.
new Intl.NumberFormat("en-GB", {
  style: "unit",
  unit: "kilogram",
  unitDisplay: "short",
}).format(qty);

/* Bind the value to its unit so a wrap cannot separate them:

   <td class="qty">1,250&nbsp;kg</td>

   or, preferred in a column, the unit in the header only:

   <th class="num">Weight (kg)</th>
   <td class="num">1,250</td>

   .num { white-space: nowrap; font-variant-numeric: tabular-nums; }   */

What people do instead

Putting the converted value in brackets after the original. The column now contains two numbers of different lengths per row, the alignment is gone, and the bracketed figure reads as an aside rather than as a measurement.

How to find out rather than assume

Look for any quantity on the rendered page that has no unit within reading distance of it. Then check the widest row in the column and confirm the value and unit did not wrap apart. Then confirm the converted values, if any, are rounded to the precision of the source.

Where the API sits in this

Nowhere, and that is worth saying plainly. This API renders HTML to PDF. By the time markup reaches it, every value in it is already a string that somebody's code produced, so there is no locale option on the render because there would be nothing for one to do. What the render controls is the page: the size, the margins, and whether the formatted value still fits the column it landed in. That is why each of these pages has a paragraph about the layout consequence, because that is the half a document adds to the general problem.

Frequently asked

Should the document use the sender's conventions or the reader's?

That is a decision rather than a fact, and the only wrong answer is not making it. The issuer's conventions are defensible for a document that is a record of what the issuer did. The recipient's are defensible for a document meant to be acted on. What fails is a document that mixes them, because a reader who sees two conventions on one page cannot tell which one any given figure is in.

Is it enough to set the document language?

No. A language attribute helps with hyphenation, with the assistive technology reading order and with anything that needs to know what language the text is in, and it changes none of the values. A date formatted as 03/04/2026 is exactly as ambiguous in a document declared as English as in one declared as anything else. The values are formatted upstream.

Why does this belong in a document reference rather than a general one?

Because a document cannot be re-rendered by its reader. On a screen a wrong format is annoying and a user can often change a setting. On paper the format is final, the layout was tuned to the string lengths of one language, and a value that grew by a few characters can push a table onto another page. The formatting problem and the pagination problem are the same problem here.

Related formatting topics

The defects that travel together, then the rest of the same group.

Values are formatted by your code before they reach the markup, so every fix on this page is upstream of the render. What the render decides is whether the result still fits.