Simsalasim Formatting & Locale Standards
Purpose: Single source of truth for how Simsalasim displays numbers, currency, percentages, dates, times, data units, and relative time across every locale we serve. Governs all user-facing surfaces: app UI, emails, SMS, push, receipts, marketing.
Status: v1.0 — Active from 2026-04-16 Owner: Design System Related ticket: Define Global Country/Locale Formats
0. Design-Time vs Runtime (Critical)
This spec governs two contexts. Do not confuse them.
Runtime (Production)
Each user sees formatted values rendered per their system locale. A de-DE user sees 45,78 € and 16.04.2026. An en-US user sees $45.78 and 4/16/2026. The profiles in §5 are the Runtime reference.
Design-Time (Figma)
All Figma design files render formatted values in de-DE format only — regardless of UI text language. This convention exists because:
- UI text in Figma is kept in English so the international team can read every screen
- Germany is our v1 launch market, so Euro prices are expected in stakeholder reviews
- Rendering multiple locales per frame is unmaintainable and C-Level-confusing
Consequences:
- Audits against Figma check only de-DE formatting
- English text is not a violation
- Figma ≠ what a non-German user will actually see at runtime
- When localizing at runtime, the locale profiles in §5 override the Figma de-DE values
Single rule for designers: if your design shows a number, price, percentage, date, time, or data unit, format it per the de-DE profile in §5. Never mix locales within a Figma file.
1. Scope & Principles
In scope
- Number formatting (integers, decimals)
- Currency formatting (EUR, USD, GBP primary; non-Euro EU currencies documented for display)
- Percentage formatting
- Date formatting (primary, secondary, numeric)
- Time formatting (24h / 12h)
- Data-unit formatting (GB, MB, MIN, SMS)
- Relative time ("Yesterday", "Wednesday", time-only)
- Date ranges
Out of scope
- UI translation (handled by copywriter skills)
- RTL language layout
- Implementation code (separate dev ticket)
Guiding principles
- Strict locale conformance. We follow CLDR/ICU conventions. Any deviation must be explicitly listed under House Style Overrides (§6) with a written reason.
- Two date variants, not three in UI contexts. Primary (written out) by default. Secondary (abbreviated) only when space is tight. Numeric (DD.MM.YYYY) reserved for data/technical surfaces (invoices, CSV exports, API-rendered data).
- Relative time is contextual. Applies only to activity/calls/notifications — never to billing, plans, passes, or legal artefacts.
- Consistency over cleverness. If in doubt, match the CLDR default via
Intl.NumberFormat/Intl.DateTimeFormat. - Currency always shows 2 decimals. No dropping of
.00or,00— ever.
2. Supported Locales
Three tiers based on product readiness.
Tier 1 — Detailed spec (primary markets)
| Locale | Region | Language | Currency |
|---|---|---|---|
en-US | United States | English | USD |
en-GB | United Kingdom | English | GBP |
de-DE | Germany | German | EUR |
Tier 2 — Major EU markets (Euro, well-defined patterns)
| Locale | Region | Language | Currency |
|---|---|---|---|
de-AT | Austria | German | EUR |
fr-FR | France | French | EUR |
fr-BE | Belgium | French | EUR |
nl-NL | Netherlands | Dutch | EUR |
nl-BE | Belgium | Dutch | EUR |
it-IT | Italy | Italian | EUR |
es-ES | Spain | Spanish | EUR |
pt-PT | Portugal | Portuguese | EUR |
en-IE | Ireland | English | EUR |
Tier 3 — Other EU (CLDR fallback, display-only until activated)
fi-FI, sv-SE (SEK), da-DK (DKK), pl-PL (PLN), cs-CZ (CZK), sk-SK, hu-HU (HUF), ro-RO (RON), bg-BG (BGN), hr-HR, sl-SI, el-GR, el-CY, lt-LT, lv-LV, et-EE, en-MT, mt-MT, de-LU, fr-LU.
Tier 3 locales follow their CLDR defaults. Display bugs in Tier 3 → fix against CLDR, do not invent house variants.
3. Core Formatting Rules (Tier 1)
3.1 Numbers
| Rule | en-US | en-GB | de-DE |
|---|---|---|---|
| Decimal separator | . | . | , |
| Thousands separator | , | , | . |
| Integer example | 1,234 | 1,234 | 1.234 |
| Decimal example | 12,345.67 | 12,345.67 | 12.345,67 |
Rule: Always render the thousands separator, even for 4-digit integers. CLDR permits omitting below 10,000 in some locales; we always include for reading consistency.
3.2 Currency
| Rule | en-US | en-GB | de-DE |
|---|---|---|---|
| Symbol | $ | £ | € |
| Position | Before amount, no space | Before amount, no space | After amount, NBSP (U+00A0) before symbol |
| Decimal places | Always 2 | Always 2 | Always 2 |
| Small example | $9.99 | £9.99 | 9,99 € |
| Large example | $1,234.56 | £1,234.56 | 1.234,56 € |
| Whole example | $10.00 | £10.00 | 10,00 € |
Never strip trailing zeros. 10 € is incorrect; 10,00 € is correct.
3.3 Percentage
| Rule | en-US | en-GB | de-DE |
|---|---|---|---|
| Symbol position | After, no space | After, no space | After, NBSP before symbol |
| Default decimals | 1 for precision contexts, 0 for counts | Same | Same |
| Precision example | 56.7% | 56.7% | 56,7 % |
| Whole example | 50% | 50% | 50 % |
Rule: 1 decimal when precision matters (data-usage bars, accuracy readouts). 0 decimals for rounded quota displays.
3.4 Dates
Three forms. Choose by context, not by medium.
Primary (default — written out): Use everywhere unless space is constrained.
| en-US | en-GB | de-DE |
|---|---|---|
March 7, 2026 | 7 March 2026 | 7. März 2026 |
Secondary (abbreviated): Only when space is tight (narrow list column, compact card, activity log older entries).
| en-US | en-GB | de-DE |
|---|---|---|
Mar 7, 2026 | 7 Mar 2026 | 7. Mär 2026 |
Tertiary (numeric, data/technical only): Tables, invoices, CSV exports, API responses rendered as-is.
| en-US | en-GB | de-DE |
|---|---|---|
3/7/2026 | 07/03/2026 | 07.03.2026 |
3.5 Time
| Rule | en-US | en-GB | de-DE |
|---|---|---|---|
| Clock | 12h | 24h | 24h |
| Separator | : | : | : |
| Example | 9:23 PM | 21:23 | 21:23 |
| AM/PM | After time, space, uppercase AM/PM | — | — |
3.6 Data units
English unit abbreviations in all locales (house-style override, §6). Spacing and position follow locale number-unit convention.
| Unit | Meaning |
|---|---|
GB | Gigabytes |
MB | Megabytes |
KB | Kilobytes |
MIN or min | Minutes (call quota) |
SMS | Text messages |
| Rule | en-US | en-GB | de-DE |
|---|---|---|---|
| Spacing number↔unit | Space | Space | NBSP |
| Example | 25.3 GB | 25.3 GB | 25,3 GB |
3.7 Date ranges
| en-US | en-GB | de-DE |
|---|---|---|
Mar 7 – 14, 2026 | 7 – 14 Mar 2026 | 7. – 14. März 2026 |
Mar 7 – Apr 3, 2026 | 7 Mar – 3 Apr 2026 | 7. März – 3. April 2026 |
Separator: en dash with NBSP on both sides (–). Never hyphen -.
4. Relative Time
4.1 Thresholds
Replace absolute timestamps with relative expressions in this tiered order. Compare by calendar day, not elapsed hours.
| Age of event | Format | en-US | de-DE |
|---|---|---|---|
| Same calendar day | Time only | 9:23 PM | 21:23 |
| Previous calendar day | Yesterday, <time> | Yesterday, 9:23 PM | Gestern, 21:23 |
| 2–6 days ago | <Weekday>, <time> | Wednesday, 9:23 PM | Mittwoch, 21:23 |
| 7+ days ago, same year | Secondary date | Mar 7 | 7. Mär |
| 7+ days ago, different year | Secondary date + year | Mar 7, 2025 | 7. Mär 2025 |
No "Last week" bucket. After 6 days → absolute date.
Day/weekday translations (Tier 1):
| en-US | en-GB | de-DE | |
|---|---|---|---|
| Today | Today | Today | Heute |
| Yesterday | Yesterday | Yesterday | Gestern |
| Monday | Monday | Monday | Montag |
| Tuesday | Tuesday | Tuesday | Dienstag |
| Wednesday | Wednesday | Wednesday | Mittwoch |
| Thursday | Thursday | Thursday | Donnerstag |
| Friday | Friday | Friday | Freitag |
| Saturday | Saturday | Saturday | Samstag |
| Sunday | Sunday | Sunday | Sonntag |
4.2 Where relative time applies
| Surface | Relative time? | Rationale |
|---|---|---|
| Call history | ✅ | Casual recency |
| Activity log | ✅ | Casual recency |
| Notifications list | ✅ | Casual recency |
| In-app messages / chat timestamps | ✅ | Casual recency |
| Billing history / invoices | ❌ Always absolute | Legal/financial precision |
| Plan expiry / validity | ❌ Always absolute primary date | User needs exact date |
| Next billing date | ❌ Always absolute | Planning context |
| Receipts | ❌ Always numeric date | Audit trail |
| Recap reports | ❌ Always absolute | Reporting context |
| Pass start/end | ❌ Always absolute | Commercial validity |
Rule of thumb: "When did this happen?" → relative. "When does this matter?" → absolute.
5. EU Locale Matrix
All Tier 2 + Tier 3 locales following CLDR strict. Examples: amount 12345.67, date 2026-03-07, time 21:23.
| Locale | Number | Currency | % | Date (primary) | Time |
|---|---|---|---|---|---|
de-DE | 12.345,67 | 12.345,67 € | 56,7 % | 7. März 2026 | 21:23 |
de-AT | 12.345,67 | € 12.345,67 | 56,7 % | 7. März 2026 | 21:23 |
de-LU | 12.345,67 | 12.345,67 € | 56,7 % | 7. März 2026 | 21:23 |
fr-FR | 12 345,67 | 12 345,67 € | 56,7 % | 7 mars 2026 | 21:23 |
fr-BE | 12.345,67 | 12.345,67 € | 56,7 % | 7 mars 2026 | 21:23 |
fr-LU | 12 345,67 | 12 345,67 € | 56,7 % | 7 mars 2026 | 21:23 |
nl-NL | 12.345,67 | € 12.345,67 | 56,7% | 7 maart 2026 | 21:23 |
nl-BE | 12.345,67 | € 12.345,67 | 56,7% | 7 maart 2026 | 21:23 |
it-IT | 12.345,67 | 12.345,67 € | 56,7% | 7 marzo 2026 | 21:23 |
es-ES | 12.345,67 | 12.345,67 € | 56,7 % | 7 de marzo de 2026 | 21:23 |
pt-PT | 12 345,67 | 12 345,67 € | 56,7% | 7 de março de 2026 | 21:23 |
en-IE | 12,345.67 | €12,345.67 | 56.7% | 7 March 2026 | 21:23 |
fi-FI | 12 345,67 | 12 345,67 € | 56,7 % | 7. maaliskuuta 2026 | 21.23 |
sv-SE | 12 345,67 | 12 345,67 kr | 56,7 % | 7 mars 2026 | 21:23 |
da-DK | 12.345,67 | 12.345,67 kr. | 56,7 % | 7. marts 2026 | 21.23 |
pl-PL | 12 345,67 | 12 345,67 zł | 56,7% | 7 marca 2026 | 21:23 |
cs-CZ | 12 345,67 | 12 345,67 Kč | 56,7 % | 7. března 2026 | 21:23 |
sk-SK | 12 345,67 | 12 345,67 € | 56,7 % | 7. marca 2026 | 21:23 |
hu-HU | 12 345,67 | 12 345,67 Ft | 56,7% | 2026. március 7. | 21:23 |
ro-RO | 12.345,67 | 12.345,67 RON | 56,7% | 7 martie 2026 | 21:23 |
bg-BG | 12 345,67 | 12 345,67 лв. | 56,7% | 7 март 2026 г. | 21:23 |
hr-HR | 12.345,67 | 12.345,67 € | 56,7% | 7. ožujka 2026. | 21:23 |
sl-SI | 12.345,67 | 12.345,67 € | 56,7 % | 7. marec 2026 | 21:23 |
el-GR | 12.345,67 | 12.345,67 € | 56,7% | 7 Μαρτίου 2026 | 21:23 |
el-CY | 12.345,67 | 12.345,67 € | 56,7% | 7 Μαρτίου 2026 | 21:23 |
lt-LT | 12 345,67 | 12 345,67 € | 56,7 % | 2026 m. kovo 7 d. | 21:23 |
lv-LV | 12 345,67 | 12 345,67 € | 56,7 % | 2026. gada 7. marts | 21:23 |
et-EE | 12 345,67 | 12 345,67 € | 56,7% | 7. märts 2026 | 21:23 |
en-MT | 12,345.67 | €12,345.67 | 56.7% | 7 March 2026 | 21:23 |
mt-MT | 12,345.67 | €12,345.67 | 56.7% | 7 ta'' Marzu 2026 | 21:23 |
Matrix notes:
nl-*andde-ATput € before the amount with space. All other Euro locales put € after with NBSP.fi-FIandda-DKuse.(period) as time separator — strictly CLDR.hu-HUandlv-LVuse year-first date order.- Non-Euro EU currencies: SEK (
kr), DKK (kr.), PLN (zł), CZK (Kč), HUF (Ft), RON (RON/lei), BGN (лв.). - Relative time in Tier 2/3 follows the same threshold logic — use
Intl.RelativeTimeFormat+ CLDR weekday names.
6. House Style Overrides
Explicit deviations from strict locale conformance. Each with written reason.
| Override | Rule | Reason |
|---|---|---|
| Data unit abbreviations | Always English (GB, MB, MIN, SMS) — never translated (not Go, not Mo, not min in French Min.) | Brand consistency; tech convention; eSIM users recognize these internationally |
| Currency decimals | Always 2, never dropped | Visual consistency; avoids mixed 10 € + 9,99 € in the same list |
| Thousands separator for 4-digit integers | Always rendered (1.234 in de-DE, not 1234) | CLDR permits either; we pick the separator variant for reading consistency |
| AM/PM casing (en-US) | Uppercase AM/PM | CLDR permits both; uppercase is more legible in small UI |
| No "Last week" relative bucket | After 6 days → absolute secondary date | Cleaner thresholds, no ambiguity of what "last week" means across weekday boundaries |
Any future override must be added to this table with a written reason and approved by Design.
7. Examples — Correct vs Incorrect
Every Recap Email inconsistency (the trigger for this spec) mapped to the fixing rule.
| Incorrect (current) | Rule violated | Correct (de-DE) |
|---|---|---|
25.3 GB in a de-DE email | §3.1 decimal separator | 25,3 GB |
98,3 € | §3.2 currency 2 decimals | 98,30 € |
45,78€ (no space) | §3.2 currency spacing | 45,78 € |
23.8 % (period in de-DE) | §3.1 + §3.3 | 23,8 % |
7 Mar in de-DE email | §3.4 date locale | 7. März 2026 (primary) or 7. Mär (secondary) |
March 7, 2026 in de-DE email | §3.4 date locale | 7. März 2026 |
10 € | §3.2 currency decimals | 10,00 € |
1234,56 € in de-DE | §3.1 thousands separator (override §6) | 1.234,56 € |
9:23pm (no space) | §3.5 AM/PM spacing | 9:23 PM |
21.23 in de-DE | §3.5 time separator | 21:23 |
last week in call history | §4.1 no "last week" bucket | Mar 7 (absolute secondary) |
Yesterday at 9:23 PM | §4.1 format | Yesterday, 9:23 PM |
8. Implementation Notes
For developers implementing this spec in frontend/backend.
- Frontend: Prefer
Intl.NumberFormat(locale, {style: ''currency'', currency: ''EUR'', minimumFractionDigits: 2, maximumFractionDigits: 2})andIntl.DateTimeFormat(locale, {dateStyle: ''long''})— both resolve to CLDR defaults that match this spec. - Secondary (abbreviated) dates:
{dateStyle: ''medium''}inIntl.DateTimeFormat. - Tertiary (numeric) dates:
{dateStyle: ''short''}. - Relative time: Use
Intl.RelativeTimeFormat(locale, {numeric: ''auto''})for the localized words. Threshold logic (§4.1) is ours — implement on top of the library. - Always set locale explicitly. Never rely on browser/system default. Pass the user''s locale preference from their profile.
- Backend / email templates: Server pre-formats every number, price, date, and time to a string using the user''s locale before rendering into the template. Templates never do formatting themselves.
- House overrides (§6) must be applied after
Intl.*:- Force 2 decimals on currency (
IntlrespectsminimumFractionDigits) - Swap data-unit translations back to English (
Intl.NumberFormatwithstyle: ''unit''returnsGoin French — override toGB) - Enforce thousands separator below 10,000 (use
useGrouping: ''always'')
- Force 2 decimals on currency (
9. Self-Check Checklist
Run before shipping any user-facing artefact (email, screen, notification, SMS, push).
- Every number uses the correct decimal and thousands separators for the locale
- Every currency amount shows exactly 2 decimal places
- Currency symbol position and spacing match the locale
- Percentage spacing follows the locale (NBSP for de-DE, none for en-US)
- Date uses primary (written) form unless space demands secondary
- Time is 12h for en-US, 24h elsewhere
- Data units use English abbreviations (
GB,MB,MIN,SMS) - Relative time is used only in activity/calls/notifications — not billing/plans/receipts
- No mixed conventions within the same surface (e.g., no
25.3 GBnext to45,78 €in a German email) - Date range uses en dash with NBSP, not hyphen
10. Maintenance & Distribution
- Canonical source of truth: This Brand Bible entry (
content_pages.slug = ''formatting-locale''), served viabrand-read?slug=formatting-locale. - Notion mirror: Auto-synced from Supabase via
simsalasim-sync-mirror. Do not edit in Notion. - Figma mirror: Manually maintained "Formatting & Locales" page in Simsalasim Design System (Figma). Update in same PR/session as Supabase.
- Change process: All edits originate in Supabase. Notion mirrors automatically; Figma is updated by Design in the same session.
- Skills that must fetch this spec when producing or reviewing content:
simsalasim-copywriter-pro-ssimsalasim-copywriter-basic-ssimsalasim-copywriter-partner-ssimsalasim-email-buildersimsalasim-qa-review-s
Change Log
- 2026-04-16 — v1.0 — Initial spec. Triggered by Recap Email audit. Covers
en-US,en-GB,de-DEat Tier 1 detail; all EU27 at Tier 2/3 reference. Relative-time rules added per scope extension.