diff --git a/CRM/Contribute/Form/Task/PDFLetter.php b/CRM/Contribute/Form/Task/PDFLetter.php
index e6b639f09e7c241809da57b7654658c45c233599..23df8d1a916041bcae669750b18629cf6f4d9f09 100644
--- a/CRM/Contribute/Form/Task/PDFLetter.php
+++ b/CRM/Contribute/Form/Task/PDFLetter.php
@@ -584,9 +584,7 @@ class CRM_Contribute_Form_Task_PDFLetter extends CRM_Contribute_Form_Task {
         foreach ($tokenProcessor->getRows() as $row) {
           $resolvedTokens[$token][$row->context['contributionId']] = $row->render($token);
         }
-        // We've resolved the value for each row - resorting to swapping them out
-        // with the old function.
-        $html_message = CRM_Utils_Token::token_replace('contribution', $token, implode($separator, $resolvedTokens[$token]), $html_message);
+        $html_message = str_replace('{contribution.' . $token . '}', implode($separator, $resolvedTokens[$token]), $html_message);
       }
     }
     $tokenContext['contributionId'] = $contributionID;
diff --git a/CRM/Contribute/WorkflowMessage/RecurringEdit/AlexCancelled.ex.php b/CRM/Contribute/WorkflowMessage/RecurringEdit/AlexCancelled.ex.php
index 057244938a4fd0a4a56a501777f070a49f4edc1e..846f99b1b0d6c31370c64fec26895b297e570caf 100644
--- a/CRM/Contribute/WorkflowMessage/RecurringEdit/AlexCancelled.ex.php
+++ b/CRM/Contribute/WorkflowMessage/RecurringEdit/AlexCancelled.ex.php
@@ -20,7 +20,7 @@ class CRM_Contribute_WorkflowMessage_RecurringEdit_AlexCancelled extends \Civi\W
     $example['asserts'] = [
       'default' => [
         ['for' => 'subject', 'regex' => '/Recurring Contribution Update.*Alex/'],
-        ['for' => 'text', 'regex' => '/Recurring contribution is for € 5,990.99, every 2 year.s. for 24 installments/'],
+        ['for' => 'text', 'regex' => '/Recurring contribution is for €5,990.99, every 2 year.s. for 24 installments/'],
       ],
     ];
   }
diff --git a/CRM/Contribute/WorkflowMessage/RecurringEdit/BarbPending.ex.php b/CRM/Contribute/WorkflowMessage/RecurringEdit/BarbPending.ex.php
index d6df8cb69ac9460390dabaec2dddfafd1ef9311e..6c19f770acadf44c1686600a837c160c7e8e3981 100644
--- a/CRM/Contribute/WorkflowMessage/RecurringEdit/BarbPending.ex.php
+++ b/CRM/Contribute/WorkflowMessage/RecurringEdit/BarbPending.ex.php
@@ -21,7 +21,7 @@ class CRM_Contribute_WorkflowMessage_RecurringEdit_BarbPending extends \Civi\Wor
     $example['asserts'] = [
       'default' => [
         ['for' => 'subject', 'regex' => '/Recurring Contribution Update.*Barb/'],
-        ['for' => 'text', 'regex' => '/Recurring contribution is for € 5,990.99, every 2 year.s. for 24 installments/'],
+        ['for' => 'text', 'regex' => '/Recurring contribution is for €5,990.99, every 2 year.s. for 24 installments/'],
       ],
     ];
   }
diff --git a/CRM/Core/EntityTokens.php b/CRM/Core/EntityTokens.php
index c1cb2fcbc414ac69a8eed4aa3b8981d5cea2a0f3..f995bc3650502bb3a0ec28706e0b99fb4066b1ec 100644
--- a/CRM/Core/EntityTokens.php
+++ b/CRM/Core/EntityTokens.php
@@ -16,6 +16,7 @@ use Civi\Token\Event\TokenValueEvent;
 use Civi\Token\TokenRow;
 use Civi\ActionSchedule\Event\MailingQueryEvent;
 use Civi\Token\TokenProcessor;
+use Brick\Money\Money;
 
 /**
  * Class CRM_Core_EntityTokens
@@ -123,8 +124,15 @@ class CRM_Core_EntityTokens extends AbstractTokenSubscriber {
       return $row->customToken($entity, \CRM_Core_BAO_CustomField::getKeyID($field), $this->getFieldValue($row, 'id'));
     }
     if ($this->isMoneyField($field)) {
+      $currency = $this->getCurrency($row);
+      if (!$currency) {
+        // too hard basket for now - just do what we always did.
+        return $row->format('text/plain')->tokens($entity, $field,
+          \CRM_Utils_Money::format($fieldValue, $currency));
+      }
       return $row->format('text/plain')->tokens($entity, $field,
-        \CRM_Utils_Money::format($fieldValue, $this->getCurrency($row)));
+        Money::of($fieldValue, $currency));
+
     }
     if ($this->isDateField($field)) {
       try {
diff --git a/Civi/Token/TokenCompatSubscriber.php b/Civi/Token/TokenCompatSubscriber.php
index 33e394f3cdfca0522db59454b222ab153c4bddd1..6274d8f29c399e72ee6d9bfe17ee78b0559f8c1e 100644
--- a/Civi/Token/TokenCompatSubscriber.php
+++ b/Civi/Token/TokenCompatSubscriber.php
@@ -3,6 +3,7 @@ namespace Civi\Token;
 
 use Civi\Token\Event\TokenRenderEvent;
 use Civi\Token\Event\TokenValueEvent;
+use Money\Money;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
@@ -69,7 +70,10 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
     if ($useSmarty) {
       $smartyVars = [];
       foreach ($e->context['smartyTokenAlias'] ?? [] as $smartyName => $tokenName) {
-        $smartyVars[$smartyName] = \CRM_Utils_Array::pathGet($e->row->tokens, explode('.', $tokenName));
+        $smartyVars[$smartyName] = \CRM_Utils_Array::pathGet($e->row->tokens, explode('.', $tokenName), $e->context['locale'] ?? NULL);
+        if ($smartyVars[$smartyName] instanceof \Brick\Money\Money) {
+          $smartyVars[$smartyName] = \Civi::format()->money($smartyVars[$smartyName]->getAmount(), $smartyVars[$smartyName]->getCurrency());
+        }
       }
       \CRM_Core_Smarty::singleton()->pushScope($smartyVars);
       try {
diff --git a/Civi/Token/TokenProcessor.php b/Civi/Token/TokenProcessor.php
index f2e32add916835563472714784440adbd5ee2d2a..0451b2ae531ea55e71abb5b8623bd357e97d5a37 100644
--- a/Civi/Token/TokenProcessor.php
+++ b/Civi/Token/TokenProcessor.php
@@ -1,6 +1,7 @@
 <?php
 namespace Civi\Token;
 
+use Brick\Money\Money;
 use Civi\Token\Event\TokenRegisterEvent;
 use Civi\Token\Event\TokenRenderEvent;
 use Civi\Token\Event\TokenValueEvent;
@@ -429,7 +430,7 @@ class TokenProcessor {
           '/:"([^"]+)"/' => $enqueue,
         ], $m[3]);
         if ($unmatched) {
-          throw new \CRM_Core_Exception("Malformed token parameters (" . $m[0] . ")");
+          throw new \CRM_Core_Exception('Malformed token parameters (' . $m[0] . ')');
         }
       }
       return $callback($m[0] ?? NULL, $m[1] ?? NULL, $m[2] ?? NULL, $filterParts);
@@ -458,6 +459,10 @@ class TokenProcessor {
       }
     }
 
+    if ($value instanceof Money && $filter === NULL) {
+      $filter = ['crmMoney'];
+    }
+
     switch ($filter[0] ?? NULL) {
       case NULL:
         return $value;
@@ -468,6 +473,11 @@ class TokenProcessor {
       case 'lower':
         return mb_strtolower($value);
 
+      case 'crmMoney':
+        if ($value instanceof Money) {
+          return \Civi::format()->money($value->getAmount(), $value->getCurrency());
+        }
+
       case 'crmDate':
         if ($value instanceof \DateTime) {
           // @todo cludgey.
diff --git a/Civi/Token/TokenRow.php b/Civi/Token/TokenRow.php
index 680e7c0e36936e375a087bb08c0cc967f8489b8b..11be3a3ba1ffdd6eda8b5752de93490a21f070e3 100644
--- a/Civi/Token/TokenRow.php
+++ b/Civi/Token/TokenRow.php
@@ -1,8 +1,11 @@
 <?php
 namespace Civi\Token;
 
+use Brick\Money\Money;
+
 /**
  * Class TokenRow
+ *
  * @package Civi\Token
  *
  * A TokenRow is a helper/stub providing simplified access to the TokenProcessor.
@@ -283,7 +286,7 @@ class TokenRow {
         // HTML => Plain.
         foreach ($htmlTokens as $entity => $values) {
           foreach ($values as $field => $value) {
-            if (!$value instanceof \DateTime) {
+            if (!$value instanceof \DateTime && !$value instanceof Money) {
               $value = html_entity_decode(strip_tags($value));
             }
             if (!isset($textTokens[$entity][$field])) {
diff --git a/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php b/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php
index fa2a50b8dbc95ca0293b8c97ba6e51c6937a6800..44c47552be1686495ef12a9bbb6b7a5c922f2791 100644
--- a/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php
+++ b/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php
@@ -311,10 +311,10 @@ class CRM_Contribute_ActionMapping_ByTypeTest extends \Civi\ActionSchedule\Abstr
       'payment instrument id = 4',
       'payment instrument name = Check',
       'payment instrument label = Check',
-      'non_deductible_amount = € 10.00',
-      'total_amount = € 100.00',
-      'net_amount = € 95.00',
-      'fee_amount = € 5.00',
+      'non_deductible_amount = €10.00',
+      'total_amount = €100.00',
+      'net_amount = €95.00',
+      'fee_amount = €5.00',
       'campaign_id = 1',
       'campaign name = big_campaign',
       'campaign label = Campaign',
diff --git a/tests/phpunit/CRM/Contribute/Form/Task/EmailTest.php b/tests/phpunit/CRM/Contribute/Form/Task/EmailTest.php
index e3cdc9ffc7efe5b7e5ad8dc1230a3ce4ec073fb3..8211eae5a628b46235efc02843042ac394d903d6 100644
--- a/tests/phpunit/CRM/Contribute/Form/Task/EmailTest.php
+++ b/tests/phpunit/CRM/Contribute/Form/Task/EmailTest.php
@@ -70,10 +70,10 @@ class CRM_Contribute_Form_Task_EmailTest extends CiviUnitTestCase {
     $form->buildForm();
     $this->assertEquals('<br/><br/>--Benny, Benny', $form->_defaultValues['html_message']);
     $form->postProcess();
-    $mut->assertSubjects(['Mr. Anthony Anderson II $ 999.00', 'Mr. Elton Anderson II $ 100.00']);
+    $mut->assertSubjects(['Mr. Anthony Anderson II $999.00', 'Mr. Elton Anderson II $100.00']);
     $mut->checkAllMailLog([
       'Subject: Mr. Anthony Anderson II',
-      '$ 999.0',
+      '$999.0',
       'Default Domain Name',
       'Donation soy',
       'Donation ranch',
diff --git a/tests/phpunit/CRM/Contribute/Form/Task/PDFLetterCommonTest.php b/tests/phpunit/CRM/Contribute/Form/Task/PDFLetterCommonTest.php
index 54250b806d731f3e268642b7cea8f4b8b0725934..91e06ed8fb4b8e70ba60a644257b891f90119c21 100644
--- a/tests/phpunit/CRM/Contribute/Form/Task/PDFLetterCommonTest.php
+++ b/tests/phpunit/CRM/Contribute/Form/Task/PDFLetterCommonTest.php
@@ -105,7 +105,7 @@ class CRM_Contribute_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase {
       $form->postProcess();
     }
     catch (CRM_Core_Exception_PrematureExitException $e) {
-      $this->assertStringContainsString('USD, USD * $ 60.00, $ 70.00 * January 1st, 2021  1:21 PM, February 1st, 2021  2:21 AM', $e->errorData['html']);
+      $this->assertStringContainsString('USD, USD * $60.00, $70.00 * January 1st, 2021  1:21 PM, February 1st, 2021  2:21 AM', $e->errorData['html']);
     }
   }
 
@@ -198,7 +198,6 @@ class CRM_Contribute_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase {
     $this->createLoggedInUser();;
     foreach (['docx', 'odt'] as $docType) {
       $formValues = [
-        'is_unit_test' => TRUE,
         'group_by' => NULL,
         'document_file' => [
           'name' => __DIR__ . "/sample_documents/Template.$docType",
@@ -297,10 +296,10 @@ class CRM_Contribute_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase {
   <body>
     <div id="crm-container">
 id : 1
-total_amount : &euro; 9,999.99
-fee_amount : &euro; 1,111.11
-net_amount : &euro; 7,777.78
-non_deductible_amount : &euro; 2,222.22
+total_amount : €9,999.99
+fee_amount : €1,111.11
+net_amount : €7,777.78
+non_deductible_amount : €2,222.22
 receive_date : July 20th, 2018
 payment_instrument_id:label : Check
 trxn_id : 1234
diff --git a/tests/phpunit/CRM/Contribute/Form/UpdateSubscriptionTest.php b/tests/phpunit/CRM/Contribute/Form/UpdateSubscriptionTest.php
index d758acedfe470ea72b7be96d0eac7fc74fb5d2fd..7cb3d3ee529faf26c8b0b0abb86fb78f97fd1ae7 100644
--- a/tests/phpunit/CRM/Contribute/Form/UpdateSubscriptionTest.php
+++ b/tests/phpunit/CRM/Contribute/Form/UpdateSubscriptionTest.php
@@ -50,7 +50,7 @@ class CRM_Contribute_Form_UpdateSubscriptionTest extends CRM_Contribute_Form_Rec
       'Return-Path: bob@example.org',
       'Dear Anthony,',
       'Your recurring contribution has been updated as requested:',
-      'Recurring contribution is for $ 10.00, every 1 month(s) for 12 installments.',
+      'Recurring contribution is for $10.00, every 1 month(s) for 12 installments.',
       'If you have questions please contact us at "Bob" <bob@example.org>.',
     ];
   }
diff --git a/tests/phpunit/CRM/Utils/TokenConsistencyTest.php b/tests/phpunit/CRM/Utils/TokenConsistencyTest.php
index e5878ab7848e99c2ddd13388c07c5821a3f8f964..1fe92bae983a11c290bf96dd72c682fe0747ef86 100644
--- a/tests/phpunit/CRM/Utils/TokenConsistencyTest.php
+++ b/tests/phpunit/CRM/Utils/TokenConsistencyTest.php
@@ -47,6 +47,7 @@ class CRM_Utils_TokenConsistencyTest extends CiviUnitTestCase {
    */
   public function tearDown(): void {
     $this->quickCleanup(['civicrm_case', 'civicrm_case_type', 'civicrm_participant', 'civicrm_event'], TRUE);
+    $this->quickCleanUpFinancialEntities();
     parent::tearDown();
   }
 
@@ -206,6 +207,28 @@ case.custom_1 :' . '
     $this->assertEquals($this->getExpectedContributionRecurTokenOutPut(), $tokenProcessor->getRow(0)->render('html'));
   }
 
+  /**
+   * Test money format tokens can respect passed in locale.
+   */
+  public function testMoneyFormat(): void {
+    // Our 'migration' off configured thousand separators at the moment is a define.
+    putenv('IGNORE_SEPARATOR_CONFIG=1');
+    $this->createLoggedInUser();
+    $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
+      'controller' => __CLASS__,
+      'smarty' => FALSE,
+      'schema' => ['contribution_recurId'],
+    ]);
+    $tokenString = '{contribution_recur.amount}';
+    $tokenProcessor->addMessage('html', $tokenString, 'text/plain');
+    $tokenProcessor->addRow([
+      'contribution_recurId' => $this->getContributionRecurID(),
+      'locale' => 'nb_NO',
+    ]);
+    $tokenProcessor->evaluate();
+    $this->assertEquals('€ 5 990,99', $tokenProcessor->getRow(0)->render('html'));
+  }
+
   /**
    * Get tokens that are not advertised via listTokens.
    *
@@ -379,7 +402,7 @@ case.custom_1 :' . '
    */
   protected function getExpectedContributionRecurTokenOutPut(): string {
     return 'contribution_recur.id :' . $this->getContributionRecurID() . '
-contribution_recur.amount :€ 5,990.99
+contribution_recur.amount :€5,990.99
 contribution_recur.currency :EUR
 contribution_recur.frequency_unit :year
 contribution_recur.frequency_interval :2
@@ -533,7 +556,7 @@ participant.role_id :1
 participant.register_date :February 19th, 2007
 participant.source :Wimbeldon
 participant.fee_level :steep
-participant.fee_amount :$ 50.00
+participant.fee_amount :$50.00
 participant.registered_by_id :
 participant.transferred_to_contact_id :
 participant.role_id:label :Attendee