Locale Configuration
Configure regional settings to provide a localized experience for your customers, including date formats, number formats, addresses, phone numbers, and more.
Understanding Locales
A locale is a BCP 47 identifier that combines:
- Language: Interface language code (en, es, fr, de)
- Region: Country/region code (US, GB, CA, AU)
Example: en-US = English (United States), de-DE = German (Germany)
The locale determines how dates, numbers, and currency are formatted throughout the application.
Setting Company Locale
Quick Setup
- Navigate to Settings → Company → Locale & Currency Settings
- Select your Country/Region from the dropdown
- The locale is automatically suggested based on your country:
- United States → en-US
- United Kingdom → en-GB
- Germany → de-DE
- Japan → ja-JP
- Review the Format Preview section to verify:
- Date format
- Number format
- Currency format
- Click Save Changes
Manual Override
If you need a different locale than the auto-detected one:
- Select your preferred Default Locale from the dropdown
- The available locales are listed with their date and number formats
- Preview updates instantly to show how data will display
Supported Locales
TimberCloud supports 27 locales with full date, number, and currency formatting:
Americas
| Code | Name | Date Format | Number Format |
|---|---|---|---|
en-US | English (United States) | MM/DD/YYYY | 1,234.56 |
en-CA | English (Canada) | DD/MM/YYYY | 1,234.56 |
fr-CA | French (Canada) | DD/MM/YYYY | 1 234,56 |
es-MX | Spanish (Mexico) | DD/MM/YYYY | 1,234.56 |
pt-BR | Portuguese (Brazil) | DD/MM/YYYY | 1.234,56 |
pt-PT | Portuguese (Portugal) | DD/MM/YYYY | 1.234,56 |
Europe
| Code | Name | Date Format | Number Format |
|---|---|---|---|
en-GB | English (United Kingdom) | DD/MM/YYYY | 1,234.56 |
de-DE | German (Germany) | DD.MM.YYYY | 1.234,56 |
fr-FR | French (France) | DD/MM/YYYY | 1 234,56 |
es-ES | Spanish (Spain) | DD/MM/YYYY | 1.234,56 |
it-IT | Italian (Italy) | DD/MM/YYYY | 1.234,56 |
nl-NL | Dutch (Netherlands) | DD-MM-YYYY | 1.234,56 |
sv-SE | Swedish (Sweden) | YYYY-MM-DD | 1 234,56 |
no-NO | Norwegian (Norway) | DD.MM.YYYY | 1 234,56 |
da-DK | Danish (Denmark) | DD-MM-YYYY | 1.234,56 |
fi-FI | Finnish (Finland) | DD.MM.YYYY | 1 234,56 |
pl-PL | Polish (Poland) | DD.MM.YYYY | 1 234,56 |
ru-RU | Russian (Russia) | DD.MM.YYYY | 1 234,56 |
tr-TR | Turkish (Turkey) | DD.MM.YYYY | 1.234,56 |
Asia-Pacific
| Code | Name | Date Format | Number Format |
|---|---|---|---|
en-AU | English (Australia) | DD/MM/YYYY | 1,234.56 |
ja-JP | Japanese (Japan) | YYYY/MM/DD | 1,234 |
zh-CN | Chinese (China) | YYYY/MM/DD | 1,234.56 |
ko-KR | Korean (South Korea) | YYYY.MM.DD | 1,234 |
Middle East & Asia
| Code | Name | Date Format | Number Format |
|---|---|---|---|
ar-SA | Arabic (Saudi Arabia) | DD/MM/YYYY | ١٬٢٣٤٫٥٦ |
he-IL | Hebrew (Israel) | DD/MM/YYYY | 1,234.56 |
hi-IN | Hindi (India) | DD/MM/YYYY | 1,23,456.78 |
Date Formatting
Format Patterns
The useDateFormatter hook provides locale-aware date formatting:
import { useDateFormatter } from '@/hooks/useDateFormatter';
function OrderDates({ order }) {
const { formatDate, formatRelative, formatCalendar } = useDateFormatter();
return (
<div>
{/* Short date: locale-specific */}
<p>Created: {formatDate(order.createdAt, 'date.short')}</p>
{/* en-US: 12/31/2024, en-GB: 31/12/2024, de-DE: 31.12.2024 */}
{/* Medium date with month name */}
<p>Due: {formatDate(order.dueDate, 'date.medium')}</p>
{/* Dec 31, 2024 */}
{/* Long date with full month */}
<p>Completed: {formatDate(order.completedAt, 'date.long')}</p>
{/* December 31, 2024 */}
{/* Relative time */}
<p>Updated: {formatRelative(order.updatedAt)}</p>
{/* 2 days ago */}
{/* Calendar format */}
<p>Next action: {formatCalendar(order.nextAction)}</p>
{/* Tomorrow at 3:30 PM */}
</div>
);
}Available Format Types
| Format | Pattern | Example (en-US) | Example (de-DE) |
|---|---|---|---|
date.short | L | 12/31/2024 | 31.12.2024 |
date.medium | ll | Dec 31, 2024 | 31. Dez. 2024 |
date.long | LL | December 31, 2024 | 31. Dezember 2024 |
date.full | LLLL | Tuesday, Dec 31, 2024 | Dienstag, 31. Dez. 2024 |
date.iso | YYYY-MM-DD | 2024-12-31 | 2024-12-31 |
time.short | LT | 3:30 PM | 15:30 |
time.medium | LTS | 3:30:45 PM | 15:30:45 |
dateTime.short | L LT | 12/31/2024 3:30 PM | 31.12.2024 15:30 |
dateTime.long | LLL | Dec 31, 2024 3:30 PM | 31. Dez. 2024 15:30 |
Time Formatting
12-hour vs 24-hour clock is determined by locale:
| Locale | Time Format | Example |
|---|---|---|
| en-US | 12-hour | 3:30 PM |
| en-GB | 24-hour | 15:30 |
| de-DE | 24-hour | 15:30 |
| ja-JP | 24-hour | 15:30 |
| fr-FR | 24-hour | 15:30 |
Number Formatting
Decimal and Thousands Separators
Different locales use different separators:
| Locale | Thousands | Decimal | Example |
|---|---|---|---|
| en-US | , (comma) | . (period) | 1,234.56 |
| en-GB | , (comma) | . (period) | 1,234.56 |
| de-DE | . (period) | , (comma) | 1.234,56 |
| fr-FR | (space) | , (comma) | 1 234,56 |
| sv-SE | (space) | , (comma) | 1 234,56 |
| ch (Swiss) | ' (apostrophe) | . (period) | 1'234.56 |
Special Number Systems
Indian Numbering (Lakh/Crore)
For hi-IN locale, numbers use the lakh/crore grouping system:
1,23,456.78 (instead of 123,456.78)
12,34,567.89 (1.2 million = 12.3 lakhs)
1,23,45,678.90 (12.3 million = 1.2 crores)Arabic Numerals
For ar-SA locale, numbers can display with Eastern Arabic numerals:
١٬٢٣٤٫٥٦ (1,234.56 in Arabic numerals)Address Formatting
Country-Specific Address Fields
TimberCloud adapts address forms based on country:
United States
Street Address
Apartment, suite, etc. (optional)
City State ZIP Code
12345 or 12345-6789United Kingdom
Street Address
Apartment, flat, etc. (optional)
City
County (optional)
Postcode SW1A 1AAJapan
〒 Postal Code 123-4567
Prefecture
City
Street Address, BuildingGermany
Straße und Hausnummer
Adresszusatz (optional)
PLZ Stadt 10115 BerlinPostal Code Validation
Postal codes are validated according to country-specific patterns:
import i18n from '@/util/i18n';
// US ZIP codes
i18n.validatePostalCode('12345', 'US'); // true
i18n.validatePostalCode('12345-6789', 'US'); // true
i18n.validatePostalCode('1234', 'US'); // false
// UK postcodes
i18n.validatePostalCode('SW1A 1AA', 'GB'); // true
i18n.validatePostalCode('M1 1AA', 'GB'); // true
// Canadian postal codes
i18n.validatePostalCode('K1A 0B1', 'CA'); // true
// German PLZ
i18n.validatePostalCode('10115', 'DE'); // true
// Japanese postal codes
i18n.validatePostalCode('100-0001', 'JP'); // trueGetting Postal Code Configuration
import i18n from '@/util/i18n';
const config = i18n.getPostalCodeConfig('GB');
// {
// pattern: /^[A-Z]{1,2}\d{1,2}[A-Z]?\s?\d[A-Z]{2}$/i,
// placeholder: 'SW1A 1AA',
// label: 'Postcode',
// maxLength: 8
// }| Country | Label | Format | Max Length |
|---|---|---|---|
| US | ZIP Code | 12345 or 12345-6789 | 10 |
| UK | Postcode | SW1A 1AA | 8 |
| Canada | Postal Code | K1A 0B1 | 7 |
| Germany | Postleitzahl | 10115 | 5 |
| France | Code Postal | 75001 | 5 |
| Japan | Postal Code | 100-0001 | 8 |
| Netherlands | Postcode | 1234 AB | 7 |
| Australia | Postcode | 2000 | 4 |
Phone Number Formatting
International Phone Formats
import i18n from '@/util/i18n';
// Format phone numbers
i18n.formatPhoneNumber('5551234567', 'US');
// "(555) 123-4567"
i18n.formatPhoneNumber('2071234567', 'GB');
// "+44 20 7123 4567"
// Validate phone numbers
i18n.validatePhoneNumber('(555) 123-4567', 'US'); // true
i18n.validatePhoneNumber('020 7123 4567', 'GB'); // truePhone Format by Country
| Country | Format | Example |
|---|---|---|
| US/Canada | +1 (NNN) NNN-NNNN | +1 (555) 123-4567 |
| UK | +44 NN NNNN NNNN | +44 20 7123 4567 |
| Germany | +49 NN NNNNNNNN | +49 30 12345678 |
| Japan | +81 N-NNNN-NNNN | +81 3-1234-5678 |
| Australia | +61 N NNNN NNNN | +61 2 1234 5678 |
| France | +33 N NN NN NN NN | +33 1 23 45 67 89 |
Tax Labels
Tax terminology varies by country and is automatically applied:
import i18n from '@/util/i18n';
i18n.getTaxLabel('US'); // "Sales Tax"
i18n.getTaxLabel('GB'); // "VAT"
i18n.getTaxLabel('CA'); // "GST/PST"
i18n.getTaxLabel('AU'); // "GST"
i18n.getTaxLabel('DE'); // "VAT" (all EU countries)| Region | Tax Label |
|---|---|
| United States | Sales Tax |
| Canada | GST/PST |
| United Kingdom | VAT |
| European Union | VAT |
| Australia | GST |
| New Zealand | GST |
| Japan | Consumption Tax |
Measurement Units
Measurement systems are automatically selected based on country:
import i18n from '@/util/i18n';
i18n.getMeasurementUnits('US');
// { system: 'imperial', length: 'inch', weight: 'lb' }
i18n.getMeasurementUnits('GB');
// { system: 'mixed', length: 'mm', weight: 'kg' }
i18n.getMeasurementUnits('CA');
// { system: 'metric', length: 'cm', weight: 'kg' }| Country | System | Length Unit | Weight Unit |
|---|---|---|---|
| United States | Imperial | inch | lb |
| United Kingdom | Mixed | mm | kg |
| Canada | Metric | cm | kg |
| Australia | Metric | cm | kg |
| Germany | Metric | cm | kg |
| Japan | Metric | cm | kg |
Company Registration Labels
Business registration terminology varies by country:
import i18n from '@/util/i18n';
i18n.getCompanyRegLabel('US'); // "EIN"
i18n.getCompanyRegLabel('GB'); // "Company Number"
i18n.getCompanyRegLabel('CA'); // "Business Number"
i18n.getCompanyRegLabel('AU'); // "ABN"| Country | Registration Label |
|---|---|
| United States | EIN |
| United Kingdom | Company Number |
| Canada | Business Number |
| Australia | ABN |
| Germany | USt-IdNr. |
Country to Locale Mapping
TimberCloud automatically maps countries to appropriate locales:
import { getLocaleFromCountry } from '@/util/locale-map';
getLocaleFromCountry('US'); // 'en-US'
getLocaleFromCountry('GB'); // 'en-GB'
getLocaleFromCountry('DE'); // 'de-DE'
getLocaleFromCountry('FR'); // 'fr-FR'
getLocaleFromCountry('JP'); // 'ja-JP'
getLocaleFromCountry('CN'); // 'zh-CN'
getLocaleFromCountry('NZ'); // 'en-AU' (closest supported)
getLocaleFromCountry('SG'); // 'en-GB' (closest supported)Implementation Details
Date Formatter Hook
The useDateFormatter hook provides comprehensive date utilities:
import { useDateFormatter } from '@/hooks/useDateFormatter';
function DateDemo() {
const {
formatDate, // Format date with pattern
formatDateCustom, // Format with custom pattern
formatRelative, // "2 days ago"
formatCalendar, // "Yesterday at 3:30 PM"
formatDuration, // Calculate duration between dates
startOfDay, // Get start of day
endOfDay, // Get end of day
startOfMonth, // Get start of month
endOfMonth, // Get end of month
isSameDay, // Compare dates
isAfter, // Date comparison
isBefore, // Date comparison
addDays, // Add days to date
subtractDays, // Subtract days from date
locale, // Current moment locale code
localeLoaded, // Whether locale is loaded
getMomentInstance, // Get locale-aware moment factory
} = useDateFormatter();
// Use these utilities throughout your component
}Locale Loading
Locales are dynamically loaded to reduce bundle size:
// Only the required locale is loaded at runtime
// English is always available (default)
// Other locales are loaded via dynamic import
useEffect(() => {
if (momentLocale !== 'en' && !loadedLocales.has(momentLocale)) {
import(`moment/locale/${momentLocale}`).then(() => {
loadedLocales.add(momentLocale);
});
}
}, [momentLocale]);Best Practices
1. Use Hooks Consistently
Always use the provided hooks for formatting:
// ✓ Good: Use hooks
const { formatDate } = useDateFormatter();
return <span>{formatDate(date, 'date.short')}</span>;
// ✗ Bad: Manual formatting
return <span>{date.toLocaleDateString()}</span>;2. Store Dates as ISO
Store all dates in ISO format in the database:
// ✓ Good: ISO format in database
createdAt: '2024-12-31T15:30:00Z'
// Format for display only
{formatDate(order.createdAt, 'dateTime.short')}3. Handle Missing Locales
Always provide fallbacks:
const locale = company?.default_locale || 'en-US';
const currency = company?.currency || 'USD';4. Test Multiple Locales
Verify your UI works correctly with:
- Different date formats (MM/DD vs DD/MM)
- Different number separators (1,234.56 vs 1.234,56)
- Different text lengths (translations may be longer)
Troubleshooting
Date Displays in Wrong Format
- Check
default_localeis set correctly on company - Verify moment locale is loaded (check console for warnings)
- Ensure you're using the formatter hook, not raw date methods
Numbers Show Wrong Separators
- Verify locale matches expected format
- Use
Intl.NumberFormator formatter utilities - Check for hardcoded formatting in legacy code
Address Fields Don't Match Country
- Ensure country code is correctly set
- Check that address components use dynamic field labels
- Verify postal code validation pattern matches country
Phone Validation Failing
- Check country code is set correctly
- Phone patterns vary significantly by country
- Some formats accept optional country codes