Languages and accessibility
Supported languages, how translations are structured and tested, and the accessibility bar both apps must meet.
Updated: 18 September 2026
Languages
| Surface | Launch | Later |
|---|---|---|
| Apps | English, Dutch | German (Saymail already ships de, so the tooling exists) |
| Website marketing and legal pages | English, Dutch | German |
| Documentation (this site) | English only | stays English |
| Transactional e-mails | English, Dutch: the Auth e-mails by {{ .Data.language }} in the user’s metadata, the export and deletion e-mails by accounts.preferred_language (falling back to that metadata, then English) (A92) | |
| Push notifications | rendered server-side in the recipient’s devices.locale |
v2 declared English, Spanish and French but the Spanish and French tables were English text; v2 later switched to English + Dutch. v3 ships only languages with complete, reviewed tables.
Structure
Following Saymail, both apps keep translations as typed tables in code (i18n/translations/En.kt, Nl.kt; I18n/TranslationsEn.swift, TranslationsNl.swift) behind a Translator that:
- looks a key up in the active language, falls back to English, and never renders a raw key;
- supports plural forms through the platform’s
PluralRules; - formats dates, phone numbers and addresses with the platform locale APIs (
libphonenumber,DateTimeFormatter,Contactsformatting on iOS).
The language enum, matching rules (exact tag, then base language, then English) and the SUPPORTED list are copied from Saymail’s Language.kt / Language.swift. The in-app language picker lists “Device language” and SUPPORTED; Device language is the default and is stored as accounts.preferred_language = null, a chosen language as its code, so a second device follows it (A81).
Keys are screen.element.variant, for example contacts.section.received_requests, circles.delete.blocked_has_members. Both platforms use the same key set; a script tools/i18n-diff.mjs compares the Kotlin and Swift tables and fails CI on any drift, and KeyParity_ForEveryLanguage_ShouldCoverEveryEnglishKey style tests run on each platform.
Copy rules: sentence case, no exclamation marks, “you/your” (“je/jouw” in Dutch, informal), the product terms from the Glossary used consistently (sharing circle = deelkring, connection = connectie, detail = gegeven, connected service = gekoppelde dienst).
Right-to-left layout is supported structurally (supportsRtl, leading/trailing rather than left/right) although no RTL language ships at launch.
Accessibility
The bar is the same as Saymail’s specification, which both apps already meet:
- Colour is never the only signal. Circle chips carry the circle name; verification state has an icon and a label; sync buckets have text.
- Contrast: WCAG AA for all text, including muted text on cards, verified against the token palette in Visual design.
- Dynamic type: all text scales with the OS setting; lists reflow, rows grow, nothing truncates a value the user must read (a phone number, an address).
- Touch targets ≥ 48 dp / 44 pt, including circle chips, list-row action buttons and the “searchable” toggles.
- Screen readers: every icon-only button labelled (add, edit, delete, favourite, refresh, back); contact rows expose “Name, circle Family, 2 details updated” style descriptions; the circle-dots visualisation exposes the list of circle names as text.
- Reduce motion / transparency honoured: sheet and tab animations dropped, scrims solid.
- Keyboard and focus order on tablets with hardware keyboards: panes are distinct regions, focus ring distinct from selection.
- Acceptance test in each platform’s UI test suite: every row has a non-visual description; largest text size does not truncate a phone number or address; every interactive element ≥ 44/48.
Formatting rules
| Data | Rule |
|---|---|
| Phone numbers | Stored E.164, displayed in national format for the viewer’s region if same country, otherwise international. Parsing and formatting come from libphonenumber-android on Android and a small parser over mobile/design/phone-metadata.json on iOS; docs/vectors/phone-format.json pins both to the same 50 outputs. |
| Dates | Day and month always; year only when shared; displayed with the viewer’s locale, age never computed or shown |
| Addresses | Formatted per the address country’s convention (house number position, postal code order) using mobile/design/address-formats.json, which gives the field order, the label key per field and the one-line display template for NL, BE, DE, GB, US and a default; docs/vectors/address-format.json pins the rendering. |
| Names | Displayed as entered; sorting uses locale-aware collation on the default name |