Internationalization
Regional Settings

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

  1. Navigate to SettingsCompanyLocale & Currency Settings
  2. Select your Country/Region from the dropdown
  3. The locale is automatically suggested based on your country:
    • United States → en-US
    • United Kingdom → en-GB
    • Germany → de-DE
    • Japan → ja-JP
  4. Review the Format Preview section to verify:
    • Date format
    • Number format
    • Currency format
  5. Click Save Changes

Manual Override

If you need a different locale than the auto-detected one:

  1. Select your preferred Default Locale from the dropdown
  2. The available locales are listed with their date and number formats
  3. Preview updates instantly to show how data will display

Supported Locales

TimberCloud supports 27 locales with full date, number, and currency formatting:

Americas

CodeNameDate FormatNumber Format
en-USEnglish (United States)MM/DD/YYYY1,234.56
en-CAEnglish (Canada)DD/MM/YYYY1,234.56
fr-CAFrench (Canada)DD/MM/YYYY1 234,56
es-MXSpanish (Mexico)DD/MM/YYYY1,234.56
pt-BRPortuguese (Brazil)DD/MM/YYYY1.234,56
pt-PTPortuguese (Portugal)DD/MM/YYYY1.234,56

Europe

CodeNameDate FormatNumber Format
en-GBEnglish (United Kingdom)DD/MM/YYYY1,234.56
de-DEGerman (Germany)DD.MM.YYYY1.234,56
fr-FRFrench (France)DD/MM/YYYY1 234,56
es-ESSpanish (Spain)DD/MM/YYYY1.234,56
it-ITItalian (Italy)DD/MM/YYYY1.234,56
nl-NLDutch (Netherlands)DD-MM-YYYY1.234,56
sv-SESwedish (Sweden)YYYY-MM-DD1 234,56
no-NONorwegian (Norway)DD.MM.YYYY1 234,56
da-DKDanish (Denmark)DD-MM-YYYY1.234,56
fi-FIFinnish (Finland)DD.MM.YYYY1 234,56
pl-PLPolish (Poland)DD.MM.YYYY1 234,56
ru-RURussian (Russia)DD.MM.YYYY1 234,56
tr-TRTurkish (Turkey)DD.MM.YYYY1.234,56

Asia-Pacific

CodeNameDate FormatNumber Format
en-AUEnglish (Australia)DD/MM/YYYY1,234.56
ja-JPJapanese (Japan)YYYY/MM/DD1,234
zh-CNChinese (China)YYYY/MM/DD1,234.56
ko-KRKorean (South Korea)YYYY.MM.DD1,234

Middle East & Asia

CodeNameDate FormatNumber Format
ar-SAArabic (Saudi Arabia)DD/MM/YYYY١٬٢٣٤٫٥٦
he-ILHebrew (Israel)DD/MM/YYYY1,234.56
hi-INHindi (India)DD/MM/YYYY1,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

FormatPatternExample (en-US)Example (de-DE)
date.shortL12/31/202431.12.2024
date.mediumllDec 31, 202431. Dez. 2024
date.longLLDecember 31, 202431. Dezember 2024
date.fullLLLLTuesday, Dec 31, 2024Dienstag, 31. Dez. 2024
date.isoYYYY-MM-DD2024-12-312024-12-31
time.shortLT3:30 PM15:30
time.mediumLTS3:30:45 PM15:30:45
dateTime.shortL LT12/31/2024 3:30 PM31.12.2024 15:30
dateTime.longLLLDec 31, 2024 3:30 PM31. Dez. 2024 15:30

Time Formatting

12-hour vs 24-hour clock is determined by locale:

LocaleTime FormatExample
en-US12-hour3:30 PM
en-GB24-hour15:30
de-DE24-hour15:30
ja-JP24-hour15:30
fr-FR24-hour15:30

Number Formatting

Decimal and Thousands Separators

Different locales use different separators:

LocaleThousandsDecimalExample
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-6789

United Kingdom

Street Address
Apartment, flat, etc. (optional)
City
County (optional)
Postcode                            SW1A 1AA

Japan

〒 Postal Code                      123-4567
Prefecture
City
Street Address, Building

Germany

Straße und Hausnummer
Adresszusatz (optional)
PLZ             Stadt               10115 Berlin

Postal 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');   // true

Getting 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
// }
CountryLabelFormatMax Length
USZIP Code12345 or 12345-678910
UKPostcodeSW1A 1AA8
CanadaPostal CodeK1A 0B17
GermanyPostleitzahl101155
FranceCode Postal750015
JapanPostal Code100-00018
NetherlandsPostcode1234 AB7
AustraliaPostcode20004

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');  // true

Phone Format by Country

CountryFormatExample
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)
RegionTax Label
United StatesSales Tax
CanadaGST/PST
United KingdomVAT
European UnionVAT
AustraliaGST
New ZealandGST
JapanConsumption 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' }
CountrySystemLength UnitWeight Unit
United StatesImperialinchlb
United KingdomMixedmmkg
CanadaMetriccmkg
AustraliaMetriccmkg
GermanyMetriccmkg
JapanMetriccmkg

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"
CountryRegistration Label
United StatesEIN
United KingdomCompany Number
CanadaBusiness Number
AustraliaABN
GermanyUSt-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_locale is 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.NumberFormat or 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