Formatting and locale / Names, addresses and identifiers
Address line order by country, and the postcode in the wrong place
Which lines an address has and what order they go in, both of which vary by destination and neither of which is a property of your database schema.
The defect
Most systems store an address as a fixed set of fields in one order because that is the order the first market used, and then print them in that order for every destination. The result is an address that is technically complete and locally wrong: the postcode before the town where it should be after, the region present where nobody uses one, the country missing where it is required. Postal systems are tolerant, so this usually delivers, which is exactly why it persists. What it costs is not delivery, it is the impression: a business document with a locally malformed address reads as a system that does not know where it is sending things.
What it does to the document
Address line counts vary by destination, so a block sized for a four-line domestic address clips a six-line international one. Never give the block a fixed height, and check the longest address your data can hold against the space the layout gives it.
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.
- Store the address as a country plus an ordered list of lines, not as a fixed set of named fields. The fields are what varies.
- Order and format the lines for the destination country, from a maintained address-format data source rather than from a table you wrote once.
- Omit fields the destination does not use rather than printing them empty, which is the blank-line problem the address block page covers.
- Print the country in the language of the sending country, in capitals, on its own last line, and omit it for domestic post.
- Do not validate a postcode against a pattern from another country. A field that rejects a valid foreign postcode is worse than one that accepts anything.
- Where an address will be posted rather than emailed, this is also a window-envelope question, and the position is a physical measurement rather than a formatting one.
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.
// Store a country and ordered lines, not a fixed field set.
const address = {
country: "DE",
lines: ["Wynne und Hall GmbH", "Callowstraße 4", "10115 Berlin"],
};
// Order and format for the destination, from maintained address data.
// Note what changes between destinations: whether the postcode leads or
// trails the locality, whether a region line exists at all, and whether
// the country line is printed.
function renderAddress(addr, sendingCountry) {
const lines = addr.lines.filter(Boolean); // no empty lines
if (addr.country !== sendingCountry) {
lines.push(countryNameIn(sendingCountry, addr.country).toUpperCase());
}
return lines;
}
/* Do not validate a foreign postcode against a local pattern. A field
that rejects a valid address is a worse defect than one that accepts
a malformed one. */What people do instead
Printing the stored fields in schema order with labels. It produces a block that is a database record rather than an address, and on a document that will be posted it is the first thing the recipient sees.
How to find out rather than assume
Render addresses for every country you ship to and show them to somebody local to each. The errors are obvious to a local and invisible to everybody else, which is why reading them yourself does not work.
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.
Name order and the assumption that breaks a salutation
The assumption that a person has a given name followed by a family name, in that order, which is wrong often enough that a document built on it insults people regularly.
Phone numbers on a document somebody has to dial from abroad
A number printed in a national format, which cannot be dialled from outside the country it belongs to and gives the reader no way to work out what is missing.
Tax and registration identifiers, and the validator that rejects them
Registration numbers whose length, character set and check rules differ by country, printed and validated by systems that assume the shape of the first country they supported.
Address block, as a layout problem
The block this value lands in, and what it takes to make it fit.
Every formatting and locale topic
The full list, grouped by money, time, identity and generated language.
Writing systems in a PDF
The other half: whether the glyphs of a script come out shaped and joined correctly.
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.