diff --git a/currency.api.php b/currency.api.php
index 547c28e..a0581fc 100644
--- a/currency.api.php
+++ b/currency.api.php
@@ -34,3 +34,31 @@ function hook_currency_info_alter(array $currencies) {
   // Let's pretend the euro has 1000 subunits.
   $currencies['EUR']['minor_unit'] = 3;
 }
+
+
+/**
+ * Expose CLDR number patterns for locales.
+ *
+ * @return array
+ *   An array of CurrencyLocalePattern objects, keyed by their locales.
+ */
+function hook_locale_pattern_info() {
+  $locale_patterns['nl_nl'] = new CurrencyLocalePattern(array(
+    'locale' => 'nl_NL',
+    'pattern' => '¤#.##0,0#',
+  ));
+
+  return $locale_patterns;
+}
+
+/**
+ * Alter exposed currency locale patterns.
+ *
+ * @param array
+ *   An array of CurrencyLocalePattern objects, keyed by their locales.
+ *
+ * @return NULL
+ */
+function hook_locale_pattern_info_alter(array $locale_patterns) {
+  $locale_patterns['nl_NL']['weight'] = -999;
+}
diff --git a/currency.currency.inc b/currency.currency.inc
index eb1c80a..fd3f8ad 100644
--- a/currency.currency.inc
+++ b/currency.currency.inc
@@ -829,3 +829,51 @@ function currency_currency_info() {
 
   return $currencies_keyed;
 }
+
+/**
+ * Implements hook_locale_pattern_info().
+ */
+function currency_currency_locale_pattern_info() {
+  $locale_patterns = array(
+    new CurrencyLocalePattern(array(
+      'locale' => 'de_DE',
+      'pattern' => 'XXX #,##0.00;XXX -#,##0.00',
+      'symbol_decimal_separator' => ',',
+      'symbol_grouping_separator' => '.',
+    )),
+    new CurrencyLocalePattern(array(
+      'locale' => 'fr_FR',
+      'pattern' => 'XXX #,##0.00;XXX -#,##0.00',
+      'symbol_decimal_separator' => ',',
+      'symbol_grouping_separator' => '.',
+    )),
+    new CurrencyLocalePattern(array(
+      'locale' => 'nl_BE',
+      'pattern' => '¤#,##0.00;¤-#,##0.00',
+      'symbol_decimal_separator' => ',',
+      'symbol_grouping_separator' => '.',
+    )),
+    new CurrencyLocalePattern(array(
+      'locale' => 'nl_NL',
+      'pattern' => '¤#,##0.00;¤-#,##0.00',
+      'symbol_decimal_separator' => ',',
+      'symbol_grouping_separator' => '.',
+    )),
+    new CurrencyLocalePattern(array(
+      'locale' => 'en_UK',
+      'pattern' => '¤#,##0.00;¤-#,##0.00',
+    )),
+    new CurrencyLocalePattern(array(
+      'locale' => 'en_US',
+      'pattern' => '¤#,##0.00;¤-#,##0.00',
+    )),
+  );
+
+  // Key patterns by their locales, which act as their machine names.
+  $locale_patterns_keyed = array();
+  foreach ($locale_patterns as $locale_pattern) {
+    $locale_patterns_keyed[$locale_pattern->locale] = $locale_pattern;
+  }
+
+  return $locale_patterns_keyed;
+}
diff --git a/currency.info b/currency.info
index 9f6abf8..33dfaeb 100755
--- a/currency.info
+++ b/currency.info
@@ -1,8 +1,14 @@
 name = Currency
 description = Provides currency information and allows users to add custom currencies.
 core = 7.x
+PHP = 5.3
 configure = admin/config/regional/currency
 dependencies[] = ctools
 files[] = includes/Currency.inc
+files[] = includes/CurrencyLocalePattern.inc
 files[] = includes/CurrencyBaseAbstract.inc
+files[] = includes/CurrencyExportableInterface.inc
 files[] = tests/CurrencyCRUDWebTestCase.test
+files[] = tests/CurrencyLocalePatternCRUDWebTestCase.test
+files[] = tests/CurrencyLocalePatternWebTestCase.test
+files[] = tests/CurrencyWebTestCase.test
diff --git a/currency.install b/currency.install
index 2d6757f..e82eb1b 100644
--- a/currency.install
+++ b/currency.install
@@ -59,6 +59,46 @@ function currency_schema() {
   foreach (get_class_vars('Currency') as $property => $default_value) {
     $schema['currency']['fields'][$property]['object default'] = $default_value;
   }
+  $schema['locale_pattern'] = array(
+    'description' => 'Locale-specific amount formatting.',
+    'export' => array(
+      'key' => 'locale',
+      'key name' => 'Locale',
+      'default hook' => 'currency_locale_pattern_info',
+      'object' => 'CurrencyLocalePattern',
+      'save callback' => 'currency_ctools_export_object_save',
+    ),
+    'fields' => array(
+      'export_module' => array(
+        'type' => 'varchar',
+        'length' => 255,
+      ),
+      'export_type' => array(
+        'type' => 'int',
+        'length' => 'tiny',
+      ),
+      'locale' => array(
+        'type' => 'varchar',
+        'length' => 5,
+      ),
+      'pattern' => array(
+        'type' => 'varchar',
+        'length' => 255,
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'length' => 'tiny',
+      ),
+    ),
+    'primary key' => array('locale'),
+  );
+  // Set Ctools object defaults based on CurrencyLocalePattern's default
+  // values.
+  foreach (get_class_vars('CurrencyLocalePattern') as $property => $default_value) {
+    if (isset($schema['locale_pattern']['fields'][$property])) {
+      $schema['locale_pattern']['fields'][$property]['object default'] = $default_value;
+    }
+  }
 
   return $schema;
 }
diff --git a/currency.module b/currency.module
index 6165b07..44fc42c 100644
--- a/currency.module
+++ b/currency.module
@@ -18,6 +18,11 @@ define('CURRENCY_INDICATOR_BEFORE', 1);
 define('CURRENCY_INDICATOR_AFTER', 2);
 
 /**
+ * The default locale.
+ */
+define('CURRENCY_DEFAULT_LOCALE', 'en_US');
+
+/**
  * Implements hook_hook_info().
  */
 function currency_hook_info() {
@@ -27,6 +32,12 @@ function currency_hook_info() {
   $hooks['currency_info_alter'] = array(
     'group' => 'currency',
   );
+  $hooks['locale_pattern_info'] = array(
+    'group' => 'currency',
+  );
+  $hooks['locale_pattern_info_alter'] = array(
+    'group' => 'currency',
+  );
 
   return $hooks;
 }
@@ -40,3 +51,26 @@ function currency_views_api() {
     'path' => drupal_get_path('module', 'currency') . '/views',
   );
 }
+
+/**
+ * Implements Ctools exportables save callback.
+ */
+function currency_ctools_export_object_save(CurrencyExportableInterface $exportable) {
+  $exportable->validate();
+  $exportable->export_type = $exportable->export_type | EXPORT_IN_DATABASE;
+  $schema = drupal_get_schema($exportable->table);
+    $fields = array();
+  foreach ($schema['fields'] as $field => $info) {
+    if (property_exists($exportable, $field)) {
+      $fields[$field] = $exportable->$field;
+    }
+  }
+  $primary_key = $schema['primary key'][0];
+
+  return db_merge($exportable->table)
+    ->key(array(
+      $primary_key => $exportable->$primary_key,
+    ))
+    ->fields($fields)
+    ->execute();
+}
diff --git a/includes/Currency.inc b/includes/Currency.inc
index 83ef5b3..8acf31e 100644
--- a/includes/Currency.inc
+++ b/includes/Currency.inc
@@ -60,4 +60,21 @@ class Currency extends CurrencyBaseAbstract {
    * @var string
    */
   public $title = '';
+
+  /**
+   * Format an amount using this currency and the environment's default locale
+   * pattern.
+   *
+   * This is a wrapper for CurrencyLocalePattern::format() in situations where
+   * the environment's default locale pattern should be used.
+   *
+   * @param float $amount
+   *
+   * @return string
+   */
+  function format($amount) {
+    $locale_pattern = CurrencyLocalePattern::loadFromEnv();
+
+    return $locale_pattern->format($this, $amount);
+  }
 }
diff --git a/includes/CurrencyExportableInterface.inc b/includes/CurrencyExportableInterface.inc
new file mode 100644
index 0000000..a65746e
--- /dev/null
+++ b/includes/CurrencyExportableInterface.inc
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Contains interface CurrencyExportableInterface.
+ */
+
+interface CurrencyExportableInterface {
+
+  /**
+   * Validates the exportable.
+   *
+   * @throws Exception
+   */
+  function validate();
+}
\ No newline at end of file
diff --git a/includes/CurrencyLocalePattern.inc b/includes/CurrencyLocalePattern.inc
new file mode 100644
index 0000000..9b813f0
--- /dev/null
+++ b/includes/CurrencyLocalePattern.inc
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Contains class CurrencyLocalePattern.
+ */
+
+use BartFeenstra\CLDR\CurrencyFormatter;
+
+/**
+ * A currency pattern for a locale.
+ */
+class CurrencyLocalePattern extends CurrencyBaseAbstract implements CurrencyExportableInterface {
+
+  /**
+   * The locale as a combination of an ISO 639-1 language code and an ISO 3166
+   * country code, separated by an underscore.
+   *
+   * @var string
+   */
+  public $locale = '';
+
+  /**
+   * Implements Chaos tools' exportable object "export_module" property.
+   */
+  public $export_module = '';
+
+  /**
+   * Implements Chaos tools' exportable object "export_type" property.
+   */
+  public $export_type = '';
+
+  /**
+   * The Unicode CLDR number pattern.
+   *
+   * @var string
+   */
+  public $pattern = '';
+
+  /**
+   * The decimal separator's replacement.
+   *
+   * @var string
+   */
+  public $symbol_decimal_separator = '.';
+
+  /**
+   * The grouping separator's replacement.
+   *
+   * @var string
+   */
+  public $symbol_grouping_separator = ',';
+
+  /**
+   * Implements Chaos tools' exportable object "table" property.
+   */
+  public $table = 'locale_pattern';
+
+  /**
+   * The pattern's internal weight. A lower weight means a higher priority.
+   *
+   * @var integer
+   */
+  public $weight = 0;
+
+  /**
+   * Implements CurrencyExportableInterface::validate().
+   */
+  function validate() {
+    $properties = array('locale', 'pattern', 'weight');
+    foreach ($properties as $property) {
+      if (!strlen($this->$property)) {
+        throw new Exception(t('Missing value for %property.', array(
+          '%property' => $property,
+        )));
+      }
+    }
+  }
+
+  /**
+   * Formats an amount using this pattern.
+   *
+   * @param Currency $currency
+   * @param float $amount
+   *
+   * @return string
+   */
+  function format(Currency $currency, $amount) {
+    static $formatter = NULL;
+
+    if (is_null($formatter) || $formatter->pattern != $this->pattern) {
+      $formatter = new CurrencyFormatter($this->pattern, array(
+        CurrencyFormatter::SYMBOL_SPECIAL_DECIMAL_SEPARATOR => $this->symbol_decimal_separator,
+        CurrencyFormatter::SYMBOL_SPECIAL_GROUPING_SEPARATOR => $this->symbol_grouping_separator,
+      ));
+    }
+
+    $formatted = $formatter->format($amount, $currency->sign);
+    $formatted = str_replace(array('[XXX]', '[999]'), array($currency->code, $currency->number), $formatted);
+
+    return $formatted;
+  }
+
+  /**
+   * Loads a single CurrencyLocalePattern based on environment variables.
+   *
+   * If no country code is set in $language->currency_country_code, the
+   * "site_default_country" system variable will be used instead. If a
+   * CurrencyLocalePattern could not be loaded using these country sources and
+   * $language->language, the locale pattern for en_US will be loaded. This is
+   * consistent with Drupal's default language, which is US English.
+   *
+   * @throws RuntimeException
+   *
+   * @return CurrencyLocalePattern
+   */
+  static function loadFromEnv() {
+    global $language;
+
+    $locale_pattern = &drupal_static('currency_env_locale_pattern');
+
+    if (is_null($locale_pattern)) {
+      // Try this request's country code.
+      if (isset($language->currency_country_code)) {
+        $country_code = $language->currency_country_code;
+        $locale_pattern = ctools_export_crud_load('locale_pattern', $language->language . '_' . $language->currency_country_code);
+      }
+      // Try the global default country code.
+      if (!$locale_pattern && $country_code = variable_get('site_default_country', '')) {
+        $locale_pattern = ctools_export_crud_load('locale_pattern', $language->language . '_' . $country_code);
+      }
+      // Try the Currency default.
+      if (!$locale_pattern) {
+        $locale_pattern = ctools_export_crud_load('locale_pattern', CURRENCY_DEFAULT_LOCALE);
+      }
+      if (!$locale_pattern) {
+        throw new RuntimeException(t('The CurrencyLocalePattern en_US could not be loaded.'));
+      }
+    }
+
+    return $locale_pattern;
+  }
+}
diff --git a/tests/CurrencyLocalePatternCRUDWebTestCase.test b/tests/CurrencyLocalePatternCRUDWebTestCase.test
new file mode 100644
index 0000000..2bca879
--- /dev/null
+++ b/tests/CurrencyLocalePatternCRUDWebTestCase.test
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains class CurrencyLocalePatternCRUDWebTestCase.
+ */
+
+/**
+ * Tests CRUD.
+ */
+class CurrencyLocalePatternCRUDWebTestCase extends DrupalWebTestCase {
+
+  static function getInfo() {
+    return array(
+      'name' => 'Currency locale pattern CRUD',
+      'group' => 'Currency',
+    );
+  }
+
+  function setUp(array $modules = array()) {
+    parent::setUp($modules + array('currency'));
+  }
+
+  /**
+   * Test CRUD functionality.
+   */
+  function testCRUD() {
+    // Test creating a new currency locale pattern.
+    $defaults_valid = TRUE;
+    $locale_pattern = ctools_export_crud_new('locale_pattern');
+    foreach (get_class_vars('CurrencyLocalePattern') as $property => $default_value) {
+      if ($locale_pattern->$property != $default_value) {
+        $defaults_valid = FALSE;
+        break;
+      }
+    }
+    $this->assertTrue($defaults_valid, 'Chaos tools correctly creates a new currency locale pattern.');
+
+    // Test inserting a currency locale pattern.
+    $locale_pattern = new CurrencyLocalePattern(array(
+      'locale' => 'de_DE',
+      'pattern' => 'foo',
+    ));
+    ctools_export_crud_save('locale_pattern', $locale_pattern);
+    $count = db_query("SELECT COUNT(1) FROM {locale_pattern} WHERE locale = :locale AND pattern = :pattern", array(
+      ':locale' => $locale_pattern->locale,
+      ':pattern' => $locale_pattern->pattern,
+    ))->fetchField();
+    $this->assertTrue($count, 'Chaos tools correctly inserts a currency locale pattern.');
+
+    // We need the saved pattern for the rest of the test.
+    if ($count) {
+      // Test loading a currency locale pattern.
+      $locale_pattern_loaded = ctools_export_crud_load('locale_pattern', $locale_pattern->locale);
+      $this->assertFalse(array_diff_assoc(get_object_vars($locale_pattern), get_object_vars($locale_pattern_loaded)), 'Chaos tools correctly loads a currency locale pattern.');
+
+      // Test updating a currency locale pattern.
+      $locale_pattern->locale = 'en_GB';
+      $locale_pattern->pattern = 'bar';
+      ctools_export_crud_save('locale_pattern', $locale_pattern);
+      $count = db_query("SELECT COUNT(1) FROM {locale_pattern} WHERE locale = :locale AND pattern = :pattern", array(
+        ':locale' => $locale_pattern->locale,
+        ':pattern' => $locale_pattern->pattern,
+      ))->fetchField();
+      $this->assertTrue($count, 'Chaos tools correctly updates a currency locale pattern.');
+
+      // Test deleting a currency locale pattern.
+      ctools_export_crud_delete('locale_pattern', $locale_pattern->locale);
+      $count = db_query("SELECT COUNT(1) FROM {locale_pattern} WHERE pattern = :pattern", array(
+        ':pattern' => $locale_pattern->pattern,
+      ))->fetchField();
+      $this->assertFalse($count, 'Chaos tools correctly deletes a currency locale pattern.');
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/CurrencyLocalePatternWebTestCase.test b/tests/CurrencyLocalePatternWebTestCase.test
new file mode 100644
index 0000000..4f6cfcf
--- /dev/null
+++ b/tests/CurrencyLocalePatternWebTestCase.test
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains class CurrencyLocalePatternWebTestCase.
+ */
+
+/**
+ * Tests class CurrencyLocalePattern.
+ */
+class CurrencyLocalePatternWebTestCase extends DrupalWebTestCase {
+
+  static function getInfo() {
+    return array(
+      'name' => 'CurrencyLocalePattern functionality',
+      'group' => 'Currency',
+    );
+  }
+
+  function setUp(array $modules = array()) {
+    parent::setUp($modules + array('currency'));
+  }
+
+  /**
+   * Test format().
+   */
+  function testFormat() {
+    $currency = ctools_export_crud_load('currency', 'EUR');
+    $locale_pattern = new CurrencyLocalePattern(array(
+      'pattern' => '¤-#,##0.00[XXX][999]',
+      'symbol_decimal_separator' => ',',
+      'symbol_grouping_separator' => '.',
+    ));
+    $amount = 12345.6789;
+    $formatted = $locale_pattern->format($currency, $amount);
+    $formatted_expected = '€-12.345,6789EUR999';
+    $this->assertEqual($formatted, $formatted_expected, 'CurrencyLocaleFormat::format() correctly formats an amount.');
+  }
+}
diff --git a/tests/CurrencyWebTestCase.test b/tests/CurrencyWebTestCase.test
new file mode 100644
index 0000000..b6b6d2c
--- /dev/null
+++ b/tests/CurrencyWebTestCase.test
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains class CurrencyWebTestCase.
+ */
+
+/**
+ * Tests class Currency.
+ */
+class CurrencyWebTestCase extends DrupalWebTestCase {
+
+  static function getInfo() {
+    return array(
+      'name' => 'Currency functionality',
+      'group' => 'Currency',
+    );
+  }
+
+  function setUp(array $modules = array()) {
+    parent::setUp($modules + array('currency'));
+  }
+
+  /**
+   * Test format().
+   */
+  function testFormat() {
+    $currency = ctools_export_crud_load('currency', 'EUR');
+    $amount = 12345.6789;
+    $formatted = $currency->format($amount);
+    $formatted_expected = '€12,345.6789';
+    $this->assertEqual($formatted, $formatted_expected, 'Currency::format() correctly formats an amount.');
+  }
+}
