PDFPipe

Formatting and locale / Language in generated text

Translated labels that no longer fit the column they were sized for

The same label in another language is frequently longer, and a layout tuned to the original wraps, clips or reflows once the translation goes in.

The defect

Labels are the shortest strings in a document and therefore the ones given the least room: a column header, a field name, a button, a total row. Translations of short strings are often proportionally much longer than translations of long ones, because a short label has no slack to absorb a difference in how a language expresses the idea. So the strings with the least space are the ones that grow the most. In a document this does not merely look untidy: a header that wraps to two lines changes the height of the header row, which changes how many rows fit on the page, which changes the pagination of the whole document.

What it does to the document

This is the localisation problem with the largest layout consequence, because it changes the height of a repeating element. A header row one line taller removes a row from every page, which on a long document adds pages, which moves everything that was positioned relative to a page.

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.

  • Size columns for the longest translation you actually ship, not for the source language, and measure rather than estimating. Any single expansion figure you have read is a rule of thumb, not a measurement of your strings.
  • Give every label the room to wrap to two lines and reserve that height on every language, so the layout does not change shape between them.
  • Do not solve it by shrinking the type for one language. A document set two points smaller in one market is a different document.
  • Avoid abbreviating in the translation to make it fit. The abbreviation conventions are not yours to invent and the result is frequently unreadable to a native reader.
  • Re-check pagination after a new language is added, not just layout. The interesting failures are downstream of the header row growing.
  • Where a label genuinely cannot fit, change the design rather than the string: move the label above the column, or use a legend.

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.

css
/* Reserve the height a two-line label needs on every language, so the
   header row is the same height in all of them and the pagination does
   not change between markets. */

.items thead th {
  min-height: 2.6em;          /* room for two lines, always */
  vertical-align: bottom;
  line-height: 1.2;
  hyphens: none;              /* do not invent hyphenation in a label */
}

/* Size the columns for the longest translation shipped, measured rather
   than estimated. Keep the numeric columns fixed so only the text column
   absorbs the difference. */
.items .desc { width: 52%; }
.items .num  { width: 16%; }

/* Not this: a per-language type size, which makes one market's document
   a different document.
   :root[lang="de"] .items { font-size: 8pt; }                          */

What people do instead

Testing the layout in the source language and treating translation as a content task that happens afterwards. By then the column widths are settled and the only lever left is shrinking type or abbreviating, which are both bad answers to a problem that was cheap to avoid.

How to find out rather than assume

Render the full document in every language you ship, and compare the page counts. If they differ, something is reflowing. Then look at every column header in the longest language and confirm none has wrapped past the space reserved for it.

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.