Translations - if a message_template has been translated then get/render the translat...
authorEileen McNaughton <emcnaughton@wikimedia.org>
Tue, 28 Jun 2022 04:39:07 +0000 (21:39 -0700)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Thu, 1 Sep 2022 06:51:02 +0000 (18:51 +1200)
CRM/Core/BAO/MessageTemplate.php
tests/events/hook_civicrm_alterMailParams.evch.php
tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php

index 5f63cb1520ffca379abfdbad6fb66690f54c1fd5..10d357e86b2c9728bb63884d03b2390e4a9f2b88 100644 (file)
@@ -341,9 +341,18 @@ class CRM_Core_BAO_MessageTemplate extends CRM_Core_DAO_MessageTemplate implemen
 
     self::synchronizeLegacyParameters($params);
     $params = array_merge($modelDefaults, $viewDefaults, $envelopeDefaults, $params);
-
+    $language = $params['language'] ?? (!empty($params['contactId']) ? Civi\Api4\Contact::get(FALSE)->addWhere('id', '=', $params['contactId'])->addSelect('preferred_language')->execute()->first()['preferred_language'] : NULL);
     CRM_Utils_Hook::alterMailParams($params, 'messageTemplate');
-    $mailContent = self::loadTemplate((string) $params['workflow'], $params['isTest'], $params['messageTemplateID'] ?? NULL, $params['groupName'] ?? '', $params['messageTemplate'], $params['subject'] ?? NULL);
+    [$mailContent, $translatedLanguage] = self::loadTemplate((string) $params['workflow'], $params['isTest'], $params['messageTemplateID'] ?? NULL, $params['groupName'] ?? '', $params['messageTemplate'], $params['subject'] ?? NULL, $language);
+    global $moneyFormatLocale;
+    $originalValue = $moneyFormatLocale;
+    if ($translatedLanguage) {
+      // If the template has been translated then set the moneyFormatLocale to match the translation.
+      // Note that in future if we do the same for dates we are likely to want to set it to match
+      // the preferred_language rather than the translation language - a long discussion is on the
+      // property in AbstractAction
+      $moneyFormatLocale = $translatedLanguage;
+    }
 
     self::synchronizeLegacyParameters($params);
     $rendered = CRM_Core_TokenSmarty::render(CRM_Utils_Array::subset($mailContent, ['text', 'html', 'subject']), $params['tokenContext'], $params['tplParams']);
@@ -352,6 +361,7 @@ class CRM_Core_BAO_MessageTemplate extends CRM_Core_DAO_MessageTemplate implemen
     }
     $nullSet = ['subject' => NULL, 'text' => NULL, 'html' => NULL];
     $mailContent = array_merge($nullSet, $mailContent, $rendered);
+    $moneyFormatLocale = $originalValue;
     return [$mailContent, $params];
   }
 
@@ -459,18 +469,20 @@ class CRM_Core_BAO_MessageTemplate extends CRM_Core_DAO_MessageTemplate implemen
    *   If omitted, the record will be loaded from workflowName/messageTemplateID.
    * @param string|null $subjectOverride
    *   This option is the older, wonkier version of $messageTemplate['msg_subject']...
+   * @param string|null $language
    *
    * @return array
    * @throws \API_Exception
    * @throws \CRM_Core_Exception
    */
-  protected static function loadTemplate(string $workflowName, bool $isTest, int $messageTemplateID = NULL, $groupName = NULL, ?array $messageTemplateOverride = NULL, ?string $subjectOverride = NULL): array {
+  protected static function loadTemplate(string $workflowName, bool $isTest, int $messageTemplateID = NULL, $groupName = NULL, ?array $messageTemplateOverride = NULL, ?string $subjectOverride = NULL, ?string $language = NULL): array {
     $base = ['msg_subject' => NULL, 'msg_text' => NULL, 'msg_html' => NULL, 'pdf_format_id' => NULL];
     if (!$workflowName && !$messageTemplateID) {
       throw new CRM_Core_Exception(ts("Message template's option value or ID missing."));
     }
 
     $apiCall = MessageTemplate::get(FALSE)
+      ->setLanguage($language)
       ->addSelect('msg_subject', 'msg_text', 'msg_html', 'pdf_format_id', 'id')
       ->addWhere('is_default', '=', 1);
 
@@ -480,7 +492,8 @@ class CRM_Core_BAO_MessageTemplate extends CRM_Core_DAO_MessageTemplate implemen
     else {
       $apiCall->addWhere('workflow_name', '=', $workflowName);
     }
-    $messageTemplate = array_merge($base, $apiCall->execute()->first() ?: [], $messageTemplateOverride ?: []);
+    $result = $apiCall->execute();
+    $messageTemplate = array_merge($base, $result->first() ?: [], $messageTemplateOverride ?: []);
     if (empty($messageTemplate['id']) && empty($messageTemplateOverride)) {
       if ($messageTemplateID) {
         throw new CRM_Core_Exception(ts('No such message template: id=%1.', [1 => $messageTemplateID]));
index cc331f1aa254fd35201e3f2f1c835271304d037b..c41e9ee78aac6f81af6e1c730fce1f98cd86cd98 100644 (file)
@@ -40,6 +40,9 @@ return new class() extends EventCheck implements HookInterface {
     'Precedence' => ['type' => 'string|NULL', 'for' => ['civimail', 'flexmailer'], 'regex' => '/(bulk|first-class|list)/'],
     'job_id' => ['type' => 'int|NULL', 'for' => ['civimail', 'flexmailer']],
 
+    // ## Language
+    'language' => ['type' => 'string|NULL', 'for' => ['messageTemplate']],
+
     // ## Content
 
     'subject' => ['for' => ['messageTemplate', 'singleEmail'], 'type' => 'string'],
index 7b87397f885ddf63752f7f5b4dd5a17aab8b1659..4172795d990cdc5fc696a10dc399bcaf0974297d 100644 (file)
@@ -46,6 +46,94 @@ class CRM_Core_BAO_MessageTemplateTest extends CiviUnitTestCase {
     $this->assertStringContainsString('<p>Hello testRenderTemplate Abba Baab!</p>', $rendered['html']);
   }
 
+  /**
+   * Test that translated strings are rendered for templates where they exist.
+   *
+   * @throws \API_Exception|\CRM_Core_Exception
+   */
+  public function testRenderTranslatedTemplate(): void {
+    $this->individualCreate(['preferred_language' => 'fr_FR']);
+    $contributionID = $this->contributionCreate(['contact_id' => $this->ids['Contact']['individual_0']]);
+    $messageTemplateID = MessageTemplate::get()
+      ->addWhere('is_default', '=', 1)
+      ->addWhere('workflow_name', '=', 'contribution_online_receipt')
+      ->addSelect('id')
+      ->execute()->first()['id'];
+
+    Translation::save()->setRecords([
+      ['entity_field' => 'msg_subject', 'string' => 'Bonjour'],
+      ['entity_field' => 'msg_html', 'string' => 'Voila!'],
+      ['entity_field' => 'msg_text', 'string' => '{contribution.total_amount}'],
+    ])->setDefaults([
+      'entity_table' => 'civicrm_msg_template',
+      'entity_id' => $messageTemplateID,
+      'status_id:name' => 'active',
+      'language' => 'fr_FR',
+    ])->execute();
+
+    $messageTemplateFrench = MessageTemplate::get()
+      ->addWhere('is_default', '=', 1)
+      ->addWhere('workflow_name', '=', 'contribution_online_receipt')
+      ->addSelect('id', 'msg_subject', 'msg_html')
+      ->setLanguage('fr_FR')
+      ->execute()->first();
+
+    $this->assertEquals('Bonjour', $messageTemplateFrench['msg_subject']);
+    $this->assertEquals('Voila!', $messageTemplateFrench['msg_html']);
+
+    $rendered = CRM_Core_BAO_MessageTemplate::renderTemplate([
+      'workflow' => 'contribution_online_receipt',
+      'tokenContext' => [
+        'contactId' => $this->ids['Contact']['individual_0'],
+        'contributionId' => $contributionID,
+      ],
+    ]);
+    $this->assertEquals('Bonjour', $rendered['subject']);
+    $this->assertEquals('Voila!', $rendered['html']);
+    $this->assertEquals('100,00 $US', $rendered['text']);
+
+    // French Canadian should ALSO pick up French if there
+    //is no specific French Canadian.
+    $rendered = CRM_Core_BAO_MessageTemplate::renderTemplate([
+      'workflow' => 'contribution_online_receipt',
+      'tokenContext' => [
+        'contactId' => $this->ids['Contact']['individual_0'],
+        'contributionId' => $contributionID,
+      ],
+      'language' => 'fr_CA',
+    ]);
+    $this->assertEquals('Bonjour', $rendered['subject']);
+    $this->assertEquals('Voila!', $rendered['html']);
+    // Money is formatted per fr_FR locale as that is the found-template-locale.
+    $this->assertEquals('100,00 $US', $rendered['text']);
+
+    Translation::save()->setRecords([
+      ['entity_field' => 'msg_subject', 'string' => 'Bonjour Canada'],
+      ['entity_field' => 'msg_html', 'string' => 'Voila! Canada'],
+      ['entity_field' => 'msg_text', 'string' => '{contribution.total_amount}'],
+    ])->setDefaults([
+      'entity_table' => 'civicrm_msg_template',
+      'entity_id' => $messageTemplateID,
+      'status_id:name' => 'active',
+      'language' => 'fr_CA',
+    ])->execute();
+
+    // But, prefer French Canadian where both exist.
+    $rendered = CRM_Core_BAO_MessageTemplate::renderTemplate([
+      'workflow' => 'contribution_online_receipt',
+      'tokenContext' => [
+        'contactId' => $this->ids['Contact']['individual_0'],
+        'contributionId' => $contributionID,
+      ],
+      'language' => 'fr_CA',
+    ]);
+    $this->assertEquals('Bonjour Canada', $rendered['subject']);
+    $this->assertEquals('Voila! Canada', $rendered['html']);
+    // Note that as there was a native-Canada format the money-formatting is
+    // also subtly different.
+    $this->assertEquals('100,00 $ US', $rendered['text']);
+  }
+
   /**
    * @throws \API_Exception
    * @throws \CRM_Core_Exception