AshReports.Cldr (ash_reports v0.1.0)

CLDR backend module for AshReports internationalization.

This module provides comprehensive locale support for AshReports, including number formatting, currency formatting, date/time formatting, and locale management. It serves as the central CLDR backend for all internationalization functionality within the reporting system.

Features

  • Locale Management: Support for 10+ major world locales with fallback
  • Number Formatting: Locale-aware decimal, percentage, and scientific notation
  • Currency Formatting: Multi-currency support with proper symbols and formatting
  • Date/Time Formatting: Locale-specific date and time representations
  • Performance Optimization: Efficient locale switching with format caching
  • Fallback Support: Graceful degradation to default locale when needed

Supported Locales

The system supports the following locales with comprehensive formatting rules:

  • English: en (default)
  • English (UK): en-GB
  • Spanish: es
  • French: fr
  • German: de
  • Italian: it
  • Portuguese: pt
  • Japanese: ja
  • Chinese (Simplified): zh
  • Chinese (Traditional): zh-Hant
  • Korean: ko
  • Russian: ru
  • Arabic: ar
  • Hindi: hi

Configuration

Configure the CLDR backend in your application config:

config :ash_reports, AshReports.Cldr,
  default_locale: "en",
  locales: ["en", "en-GB", "es", "fr", "de"],
  providers: [
    Cldr.Number,
    Cldr.Currency,
    Cldr.DateTime,
    Cldr.Calendar
  ]

Usage Examples

Basic Locale Operations

# Set current locale
AshReports.Cldr.set_locale("fr")

# Get current locale
locale = AshReports.Cldr.current_locale()

# Check if locale is supported
AshReports.Cldr.locale_supported?("de")

Number Formatting

# Format numbers with locale-specific formatting
AshReports.Cldr.format_number(1234.56, locale: "fr")
# => "1 234,56"

AshReports.Cldr.format_number(1234.56, locale: "en")
# => "1,234.56"

Currency Formatting

# Format currency amounts
AshReports.Cldr.format_currency(1234.56, :USD, locale: "en")
# => "$1,234.56"

AshReports.Cldr.format_currency(1234.56, :EUR, locale: "fr")
# => "1 234,56 €"

Date/Time Formatting

# Format dates with locale-specific patterns
date = ~D[2024-03-15]
AshReports.Cldr.format_date(date, locale: "en", format: :long)
# => "March 15, 2024"

AshReports.Cldr.format_date(date, locale: "fr", format: :long)
# => "15 mars 2024"

Integration with Renderers

The CLDR backend integrates seamlessly with all Phase 3 renderers:

  • HTML Renderer: Locale-aware CSS direction and number formatting
  • PDF Renderer: Locale-specific typography and formatting
  • JSON Renderer: Locale metadata and formatted data output
  • HEEX Renderer: Phoenix LiveView i18n integration

Performance Considerations

  • Format Caching: Compiled formatters are cached for performance
  • Lazy Loading: Locale data is loaded on-demand
  • Memory Efficiency: Only requested locales are kept in memory
  • Process Safety: Locale state is process-isolated

Error Handling

The module provides graceful error handling with fallback mechanisms:

  • Unsupported locales fall back to the default locale
  • Invalid format options use sensible defaults
  • Formatting errors return the original value with a warning

Summary

Functions

Gets the current process locale.

Gets the decimal separator for a locale.

Detects the locale from various sources.

Formats a currency amount according to the specified locale.

Formats a date according to the specified locale and format style.

Formats a datetime according to the specified locale and format style.

Formats a number according to the specified locale and options.

Formats a time according to the specified locale and format style.

Checks if a locale is supported by the CLDR backend.

Sets the locale for the current process.

Gets locale-specific text direction (ltr or rtl).

Gets the thousands separator for a locale.

Functions

current_locale()

@spec current_locale() :: String.t()

Gets the current process locale.

Returns the locale set for the current process, or the default locale if no locale has been explicitly set.

Examples

iex> AshReports.Cldr.current_locale()
"en"

decimal_separator(locale)

@spec decimal_separator(String.t()) :: String.t()

Gets the decimal separator for a locale.

Examples

iex> AshReports.Cldr.decimal_separator("en")
"."

iex> AshReports.Cldr.decimal_separator("fr")
","

default_locale()

@spec default_locale() :: String.t()

detect_locale(sources \\ %{})

@spec detect_locale(map()) :: {:ok, String.t()} | {:error, term()}

Detects the locale from various sources.

Attempts to detect the locale from:

  1. Explicit locale parameter
  2. Process locale setting
  3. HTTP Accept-Language header
  4. Application configuration
  5. System locale
  6. Default fallback locale

Parameters

  • sources - Optional map containing locale detection sources:
    • :locale - Explicit locale
    • :accept_language - HTTP Accept-Language header
    • :user_preference - User's saved locale preference

Examples

iex> AshReports.Cldr.detect_locale(%{locale: "fr"})
{:ok, "fr"}

iex> AshReports.Cldr.detect_locale(%{accept_language: "en-GB,en;q=0.9"})
{:ok, "en-GB"}

format_currency(amount, currency, options \\ [])

@spec format_currency(number(), atom() | String.t(), keyword()) ::
  {:ok, String.t()} | {:error, term()}

Formats a currency amount according to the specified locale.

Parameters

  • amount - The monetary amount to format
  • currency - The currency code (atom or string)
  • options - Formatting options including:
    • :locale - The locale to use for formatting (default: current locale)
    • :format - The currency format style (:standard, :accounting, :short)

Examples

iex> AshReports.Cldr.format_currency(1234.56, :USD)
{:ok, "$1,234.56"}

iex> AshReports.Cldr.format_currency(1234.56, :EUR, locale: "fr")
{:ok, "1 234,56 €"}

iex> AshReports.Cldr.format_currency(-500, :USD, format: :accounting)
{:ok, "($500.00)"}

format_date(date, options \\ [])

@spec format_date(
  Date.t() | DateTime.t() | NaiveDateTime.t(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}

Formats a date according to the specified locale and format style.

Parameters

  • date - The date to format (Date, DateTime, or NaiveDateTime)
  • options - Formatting options including:
    • :locale - The locale to use for formatting (default: current locale)
    • :format - The format style (:short, :medium, :long, :full) or custom pattern

Examples

iex> date = ~D[2024-03-15]
iex> AshReports.Cldr.format_date(date, format: :long)
{:ok, "March 15, 2024"}

iex> AshReports.Cldr.format_date(date, locale: "fr", format: :long)
{:ok, "15 mars 2024"}

format_datetime(datetime, options \\ [])

@spec format_datetime(
  DateTime.t() | NaiveDateTime.t(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}

Formats a datetime according to the specified locale and format style.

Parameters

  • datetime - The datetime to format
  • options - Formatting options including:
    • :locale - The locale to use for formatting (default: current locale)
    • :date_format - The date format style
    • :time_format - The time format style
    • :format - Combined datetime format style

Examples

iex> datetime = ~U[2024-03-15 14:30:00Z]
iex> AshReports.Cldr.format_datetime(datetime)
{:ok, "Mar 15, 2024, 2:30:00 PM"}

format_number(number, options \\ [])

@spec format_number(
  number(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}

Formats a number according to the specified locale and options.

Parameters

  • number - The number to format
  • options - Formatting options including:
    • :locale - The locale to use for formatting (default: current locale)
    • :format - The format style (:decimal, :currency, :percent, :scientific)
    • :precision - Number of decimal places
    • :currency - Currency code when using :currency format

Examples

iex> AshReports.Cldr.format_number(1234.56)
{:ok, "1,234.56"}

iex> AshReports.Cldr.format_number(1234.56, locale: "fr")
{:ok, "1 234,56"}

iex> AshReports.Cldr.format_number(0.1234, format: :percent)
{:ok, "12.34%"}

format_time(time, options \\ [])

@spec format_time(
  Time.t() | DateTime.t() | NaiveDateTime.t(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}

Formats a time according to the specified locale and format style.

Parameters

  • time - The time to format (Time, DateTime, or NaiveDateTime)
  • options - Formatting options including:
    • :locale - The locale to use for formatting (default: current locale)
    • :format - The format style (:short, :medium, :long, :full) or custom pattern

Examples

iex> time = ~T[14:30:00]
iex> AshReports.Cldr.format_time(time)
{:ok, "2:30:00 PM"}

iex> AshReports.Cldr.format_time(time, locale: "fr")
{:ok, "14:30:00"}

locale_supported?(locale)

@spec locale_supported?(String.t()) :: boolean()

Checks if a locale is supported by the CLDR backend.

Examples

iex> AshReports.Cldr.locale_supported?("en")
true

iex> AshReports.Cldr.locale_supported?("xx-XX")
false

set_locale(locale)

@spec set_locale(String.t()) :: :ok | {:error, String.t()}

Sets the locale for the current process.

Parameters

  • locale - The locale to set (must be supported)

Examples

iex> AshReports.Cldr.set_locale("fr")
:ok

iex> AshReports.Cldr.set_locale("xx-XX")
{:error, "Unsupported locale: xx-XX"}

text_direction(locale)

@spec text_direction(String.t()) :: String.t()

Gets locale-specific text direction (ltr or rtl).

Examples

iex> AshReports.Cldr.text_direction("en")
"ltr"

iex> AshReports.Cldr.text_direction("ar")
"rtl"

thousands_separator(locale)

@spec thousands_separator(String.t()) :: String.t()

Gets the thousands separator for a locale.

Examples

iex> AshReports.Cldr.thousands_separator("en")
","

iex> AshReports.Cldr.thousands_separator("fr")
" "