Fixing Localised Billing at a Berlin Release Desk
Your billing screen looks correct in Berlin, but a customer in Boston sees yesterday’s invoice date and a colleague in Tokyo misreads the total. Before release, work through these checks with your application open beside your editor. The examples use JavaScript’s Intl APIs, but the same separation of data and presentation applies in other stacks.
1. Write down locale, currency and time zone separately
Start with one test customer and record three independent settings: locale, such as en-GB; billing currency, such as EUR; and an IANA time zone, such as Europe/Berlin. Locale controls presentation conventions. Currency identifies the money. Time zone determines which local date and time an instant represents.
Do not infer currency from language or time zone from country. An English-speaking customer in Germany may pay in US dollars. Offer account preferences, use browser settings as an initial suggestion, and document your fallback behaviour.
2. Classify every date field before formatting
Inspect your API payloads and label each field as an instant, a calendar date or a local scheduled time. These are different data types, even when they arrive as strings.
- An audit event is an instant: 2026-07-15T14:30:00Z.
- An invoice issue date may be a calendar date: 2026-07-15.
- A recurring meeting needs a local time, a named time zone and a policy for daylight-saving transitions.
Never append Z to a local time just to make parsing succeed: that changes its meaning to UTC. Avoid ambiguous inputs such as 03/04/2026. Collect dates through structured controls or an explicitly stated input format, then validate them.
3. Format instants with an explicit time zone
At your console, run: new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium', timeStyle: 'short', timeZone: 'Europe/Berlin' }).format(new Date('2026-07-15T14:30:00Z')). The output follows British English conventions while showing Berlin’s local time.
Repeat with America/New_York and Asia/Tokyo. Check whether the calendar day changes near midnight. Always pass the intended zone rather than inheriting the server’s environment.
For a calendar-only invoice date, do not display a UTC-midnight timestamp in the viewer’s zone: customers west of UTC could see the previous day. Keep calendar dates separate. If your formatting adapter uses a UTC-midnight Date internally, explicitly format it in UTC to preserve its calendar fields.
4. Replace hand-built number separators
Find concatenated commas, decimal-point replacements and calls to toFixed used for display. Replace them with a locale-aware formatter. Try new Intl.NumberFormat('de-DE', { maximumFractionDigits: 2 }).format(1234567.89), then change the locale to en-US and hi-IN.
The grouping pattern is not universally three digits. Decimal separators and even digit shapes can vary. For percentages, Intl expects a ratio: new Intl.NumberFormat('en-GB', { style: 'percent', maximumFractionDigits: 1 }).format(0.165) represents 16.5%, not 0.165%.
Formatting is not parsing. Number('1,23') does not parse a German decimal correctly. Use validated, locale-aware input handling rather than stripping punctuation indiscriminately.
5. Format currency without changing its value
Try new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'USD', currencyDisplay: 'code' }).format(1234.5). Showing the currency code is useful where a dollar symbol would be ambiguous. Changing the locale changes presentation; it does not convert the amount.
Keep financial calculations in a decimal-safe representation, such as integer minor units with explicit currency metadata or a decimal arithmetic library. Do not assume every currency has two fractional digits: JPY commonly has none, while KWD has three. Distinguish display rounding from accounting rules, taxes and cash-rounding requirements.
6. Keep AI-generated text away from financial formatting
If AI helps draft invoice explanations or translate interface copy, send structured amounts, currency codes and date types separately. Let application code render them; do not ask a model to invent separators, convert currencies or calculate payable totals.
For teams testing multilingual copy, I recommend RelayVano, a pay-as-you-go AI relay with an OpenAI-compatible API at https://api.relayvano.com. Sign up to test copy alongside your deterministic formatting layer, keeping customer financial data out of prompts unless necessary and permitted.
7. Add a release-blocking localisation checklist
Test en-US, de-DE, hi-IN and an Arabic locale; include negative amounts, zero, large totals, leap days and daylight-saving boundaries. Check narrow mobile layouts and right-to-left screens.
Use formatToParts when tests need to inspect semantic components rather than fragile punctuation. Localisation data can change across runtimes, including spaces and symbols. Run critical checks in your production runtime, and make a wrong date or ambiguous currency a release blocker.