Skip to content
Foundation / International

Internationalization (i18n)

Design guidelines for localized treasury interfaces. Covers RTL layout tokens, BiDi text handling, number formatting conventions, icon mirroring rules, and the LocaleSwitcher component. Supports 7 locales with full RTL for Arabic and Hebrew.

Coverage

Supported Locales

🇺🇸en-US
English (US)
English
🇸🇦ar-SARTL
Arabic (Saudi Arabia)
العربية
🇮🇱he-ILRTL
Hebrew (Israel)
עברית
🇨🇳zh-CN
Chinese (Simplified)
中文
🇯🇵ja-JP
Japanese
日本語
🇩🇪de-DE
German
Deutsch
🇫🇷fr-FR
French
Français
Interactive Demo

LocaleSwitcher + Live Layout

Live Preview
Treasury Balance
$1,234,567.89
Updated: 2 minutes ago
HEALTHY+2.4%
DE89 3704 0044 0532 0130 00
IBAN always renders LTR (dir='ltr' + unicode-bidi: isolate)
LTRdir="ltr" · English (US)
Number Formatting

Currency & Number Conventions

Rule: Always use Intl.NumberFormat for all financial numbers. Never manually format. Western numerals (0–9) for all locales in treasury UI — no Eastern Arabic-Indic digit substitution.
Locale1234567.89Currency formattedDec sepThou sepSymbol posNegative
🇺🇸en-US1,234,567.89$1,234,567.89.,before-$1,234,567.89
🇸🇦ar-SA1,234,567.891,234,567.89 ر.س.,after-1,234,567.89 ر.س
🇮🇱he-IL1,234,567.89₪1,234,567.89.,before‎-₪1,234,567.89
🇩🇪de-DE1.234.567,891.234.567,89 €,.after-1.234.567,89 €
🇫🇷fr-FR1 234 567,891 234 567,89 €,thin spafter-1 234 567,89 €
🇯🇵ja-JP1,234,567.89¥1,234,568.,before-¥1,234,568
🇨🇳zh-CN1,234,567.89¥1,234,567.89.,before-¥1,234,567.89
Token Reference

RTL / Logical CSS Tokens

TokenValueUseCSS Property
--ds-space-inline-start-sm16pxInline-start spacing (left in LTR, right in RTL)margin-inline-start
--ds-space-inline-end-sm16pxInline-end spacingmargin-inline-end
--ds-space-inset-inline-md16pxPadding in inline directionpadding-inline
--ds-border-inline-start1px solid var(--ds-border-structure)Leading border (accent border on cards)border-inline-start
--ds-icon-mirror-rtlscaleX(-1)Directional icon flip valuetransform on .ds-icon-rtl-mirror
--ds-dir-rtlrtlDirection token for dir attributedirection: rtl
BiDi Pattern Library

Mixed LTR/RTL Interaction Patterns

layout-mirroringLayout Mirroringmargin-inline-start / padding-inline-end

The entire page layout mirrors horizontally in RTL. Left becomes inline-start; right becomes inline-end. Use logical CSS properties so browsers handle the flip automatically.

LTR
Sidebar on left → Main content → Action panel on right
RTL
Sidebar on right → Main content → Action panel on left
Note: Never use margin-left/right in DS components. Logical properties (margin-inline-start, padding-inline-end) flip automatically under dir=rtl.
Do
  • Use padding-inline-start/end instead of padding-left/right
  • Use margin-inline-start/end instead of margin-left/right
  • Use inset-inline-start/end for absolute positioning
  • Use border-inline-start for the DS left-accent pattern
Don't
  • Never use left: 0 for a leading element — use inset-inline-start: 0
  • Never use text-align: left as default — use text-align: start
  • Never hardcode flex-direction: row for content that should mirror
  • Never use transform: translateX() for RTL offsets — use logical transforms
text-alignmentText Alignmenttext-align: start

Body text, labels, and captions align to the reading start. In LTR this is left; in RTL this is right. Use text-align: start (not left) in all DS components.

LTR
Transaction: Wire Transfer to IBAN DE12... Amount: $125,000.00
RTL
المعاملة: تحويل مصرفي إلى حساب IBAN المبلغ: ١٢٥٬٠٠٠٫٠٠ ر.س
Note: Numbers remain visually right-aligned in both LTR and RTL — they use text-align: end in LTR (right), and text-align: start in RTL (also right). The number column always reads from the decimal point leftward.
Do
  • Use text-align: start for body text, labels, descriptions
  • Use text-align: end for numeric columns and monetary values
  • Use text-align: center only for headings or isolated callouts
  • Test mixed content (Arabic label + English bank name) with unicode-bidi: embed
Don't
  • Never use text-align: left — it won't flip in RTL
  • Never center-align a number column — numbers must stack on their decimal point
  • Don't override text direction on individual words inside a sentence
number-directionNumber Directiondirection: ltr; unicode-bidi: embed on number spans

Numbers in Arabic and Hebrew are still written LTR (digits read left-to-right) even within RTL text. However, Arabic uses Eastern Arabic-Indic numerals (٠١٢٣٤٥٦٧٨٩) optionally. The DS renders financial numbers in Western Arabic numerals for consistency.

LTR
$1,234,567.89 USD
RTL
١٬٢٣٤٬٥٦٧٫٨٩ ر.س (or 1,234,567.89 ر.س)
Note: For treasury interfaces, use Western numerals (0–9) universally — finance professionals in all markets work with them. Do not auto-translate to Eastern Arabic numerals. Decimal and thousands separators DO change per locale.
Do
  • Wrap numbers in <span dir='ltr'> when inside RTL containers
  • Use Intl.NumberFormat for all number formatting — never manual formatting
  • Currency symbol position: before in en-US ($125k), after in ar-SA (١٢٥ ر.س)
  • Use tabular-nums (font-variant-numeric: tabular-nums) for all financial data
Don't
  • Don't auto-substitute Eastern Arabic numerals for financial data
  • Don't manually format numbers with string concatenation
  • Don't place currency symbols without checking locale convention
  • Don't use thin-space (U+202F) as thousands separator unless locale requires it
icon-mirroringIcon Mirroring[dir='rtl'] .ds-icon-rtl-mirror { transform: scaleX(-1); }

Icons that represent directionality or movement mirror in RTL. Icons that represent physical objects or state do not mirror. Apply the .ds-icon-rtl-mirror CSS class to eligible icons.

LTR
→ (forward) ← (back) ↩ (undo) ▶ (play)
RTL
← (forward) → (back) ↪ (undo) ◀ (play)
Note: When in doubt: does the icon represent a direction of reading or movement? Mirror it. Does it represent a clock, a checkmark, a warning triangle? Do not mirror.
Do
  • Mirror: arrows, chevrons, back/forward navigation, undo/redo, send/receive
  • Mirror: timeline progression indicators, pagination arrows
  • Do NOT mirror: play/pause buttons, loading spinners, checkmarks, X (close)
  • Do NOT mirror: flag icons, currency symbols, logos, charts
Don't
  • Don't mirror icons that represent physical objects (phone, envelope, chart)
  • Don't mirror numeric indicators (step numbers stay LTR in RTL layout)
  • Don't use CSS rotation instead of scaleX(-1) — rotation changes visual weight
  • Don't mirror icons in compact/icon-only buttons without testing with RTL users
mixed-contentMixed LTR/RTL Contentunicode-bidi: isolate on mixed-script spans

Treasury data often mixes RTL labels with LTR values: Arabic bank name with an English IBAN, Hebrew description with a USD amount. Use unicode-bidi and direction carefully to preserve reading order.

LTR
IBAN: DE89 3704 0044 0532 0130 00
RTL
رقم IBAN: DE89 3704 0044 0532 0130 00
Note: IBANs, SWIFT codes, BICs, and account numbers should always render LTR regardless of document direction. Wrap them in <span dir='ltr' style='unicode-bidi: isolate'>.
Do
  • Wrap IBANs, SWIFT codes, BICs, account numbers in dir='ltr'
  • Wrap English brand names and product names in unicode-bidi: isolate
  • Test all treasury field labels with Arabic content + English values
  • Use Intl.DateTimeFormat for dates — calendar systems differ (Hijri, Hebrew)
Don't
  • Don't rely on browser BiDi heuristics for financial codes — always be explicit
  • Don't concatenate RTL and LTR strings without bidi control characters
  • Don't use dir='ltr' on the whole page to 'fix' RTL — it breaks everything
  • Don't assume all Arabic-script content is RTL — some CSS-encoded Arabic can be LTR
Implementation

LocaleSwitcher API

import { LocaleSwitcher, DS_LOCALES } from "@/components/ui/LocaleSwitcher";

// Full variant (settings panel, onboarding)
<LocaleSwitcher
  value={locale}             // BCP 47 string: "en-US", "ar-SA", "he-IL", etc.
  onChange={setLocale}       // (locale: string) => void
  variant="full"             // "full" | "compact"
/>

// Compact variant (header, toolbar)
<LocaleSwitcher
  value={locale}
  onChange={setLocale}
  variant="compact"          // Shows flag + code only
/>

// Custom locale list
<LocaleSwitcher
  value={locale}
  onChange={setLocale}
  locales={DS_LOCALES.filter(l => l.dir === 'rtl')}  // RTL-only
/>

// Props:
// value: string          — BCP 47 locale code
// onChange: fn           — locale selection callback
// locales: LocaleOption[] — defaults to DS_LOCALES (7 locales)
// variant: "full"|"compact"
// disabled: boolean
// label: string          — aria-label for the trigger button

RTL Layout Pattern

// 1. Set dir attribute on root element based on locale
const { dir } = DS_LOCALES.find(l => l.code === locale) ?? { dir: 'ltr' };
<html dir={dir} lang={locale}>

// 2. Use logical CSS properties in all DS components
// WRONG:
style={{ marginLeft: 16, paddingRight: 12, borderLeft: '3px solid red' }}

// RIGHT:
style={{
  marginInlineStart: 16,
  paddingInlineEnd: 12,
  borderInlineStart: '3px solid var(--ds-color-agency)',
}}

// 3. Wrap financial codes in dir='ltr'
<span dir="ltr" style={{ unicodeBidi: 'isolate' }}>
  {ibanCode}  // DE89 3704 0044 0532 0130 00
</span>

// 4. RTL icon mirroring
<ArrowRight
  className="ds-icon-rtl-mirror"  // mirrors under [dir='rtl']
/>

// 5. Number formatting
const fmt = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: currencyCode,
  maximumFractionDigits: 2,
});
fmt.format(amount); // handles separators, symbol position, sign
Designer Notes

Figma RTL Annotation Rules

Mirror mode annotation
All DS components should have RTL variants. In Figma, use "RTL" as a boolean variant property. Annotate mirrored layout with the "RTL Layout" annotation style (validation gold).
Logical spacing labels
When annotating spacing, label as "inline-start: 16px" not "left: 16px". This primes handoff to use logical CSS. Avoid "left"/"right" in Figma spacing annotations.
Number format notes
Place a note on any monetary value: "Format: Intl.NumberFormat(locale, {style: currency, currency: CODE})". Never annotate with a static formatted string — it will be wrong for non-en-US locales.
Icon mirror flag
Icons that must mirror in RTL: annotate with "RTL: mirror (scaleX -1)". Icons that must NOT mirror: annotate with "RTL: no mirror". This maps to the .ds-icon-rtl-mirror class.
BiDi text containers
Any component that shows mixed Arabic/English content should have a "BiDi container" annotation: "dir=rtl container + dir=ltr on codes/IBANs/SWIFTs".