TokenProcessor - If there is a `locale`, then use it
authorTim Otten <totten@civicrm.org>
Tue, 10 Aug 2021 03:55:44 +0000 (20:55 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 10 Aug 2021 23:46:46 +0000 (16:46 -0700)
There are two likely ways in which a tokenized email winds up with localized strings - either
the Smarty `{ts}...{/ts}` tags define it, or a custom/hook-based tag uses `ts()`.

This ensures that the locale is set in both cases.

It's hypothetically possible that some other `civi.token.eval` listeners
need to use the `row->context['locale']`.  However, I grepped universe and
couldn't find anything that would be affected.  (There were two contrib
listeners for `civi.token.eval` and neither seemed to be affected.)

Civi/Token/TokenCompatSubscriber.php
Civi/Token/TokenProcessor.php
tests/phpunit/Civi/Token/TokenProcessorTest.php

index d957c3cb9d946f07067b7ee65d44ec48ec82cc72..c2d6c5b6e123e02abe3098683448a6c9304af8fd 100644 (file)
@@ -51,6 +51,10 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
       if (empty($row->context['contactId'])) {
         continue;
       }
+
+      unset($swapLocale);
+      $swapLocale = empty($row->context['locale']) ? NULL : \CRM_Utils_AutoClean::swapLocale($row->context['locale']);
+
       /** @var int $contactId */
       $contactId = $row->context['contactId'];
       if (empty($row->context['contact'])) {
index e8ee1b8f73f5d65f8d331284ac67eff50b168512..7ad2ec7b58429f5235be9202145b886f1fa33dc6 100644 (file)
@@ -55,6 +55,7 @@ class TokenProcessor {
    *     automatically from contactId.)
    *   - actionSchedule: DAO, the rule which triggered the mailing
    *     [for CRM_Core_BAO_ActionScheduler].
+   *   - locale: string, the name of a locale (eg 'fr_CA') to use for {ts} strings in the view.
    *   - schema: array, a list of fields that will be provided for each row.
    *     This is automatically populated with any general context
    *     keys, but you may need to add extra keys for token-row data.
@@ -351,6 +352,8 @@ class TokenProcessor {
       $row = $this->getRow($row);
     }
 
+    $swapLocale = empty($row->context['locale']) ? NULL : \CRM_Utils_AutoClean::swapLocale($row->context['locale']);
+
     $message = $this->getMessage($name);
     $row->fill($message['format']);
     $useSmarty = !empty($row->context['smarty']);
index cd8dba1cf23b9e44af6fc6843543740d28d42c5a..ce6037d071f67f563c7ef647b5a14dc4dd118a58 100644 (file)
@@ -139,6 +139,75 @@ class TokenProcessorTest extends \CiviUnitTestCase {
     }
   }
 
+  public function testRenderLocalizedSmarty() {
+    $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
+    $p = new TokenProcessor($this->dispatcher, [
+      'controller' => __CLASS__,
+      'smarty' => TRUE,
+    ]);
+    $p->addMessage('text', '{ts}Yes{/ts} {ts}No{/ts}', 'text/plain');
+    $p->addRow([]);
+    $p->addRow(['locale' => 'fr_FR']);
+    $p->addRow(['locale' => 'es_MX']);
+
+    $expectText = [
+      'Yes No',
+      'Oui Non',
+      'Sí No',
+    ];
+
+    $rowCount = 0;
+    foreach ($p->evaluate()->getRows() as $key => $row) {
+      /** @var TokenRow */
+      $this->assertTrue($row instanceof TokenRow);
+      $this->assertEquals($expectText[$key], $row->render('text'));
+      $rowCount++;
+    }
+    $this->assertEquals(3, $rowCount);
+  }
+
+  public function testRenderLocalizedHookToken() {
+    $cid = $this->individualCreate();
+
+    $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
+    \Civi::dispatcher()->addListener('hook_civicrm_tokens', function($e) {
+      $e->tokens['trans'] = [
+        'trans.affirm' => ts('Translated affirmation'),
+      ];
+    });
+    \Civi::dispatcher()->addListener('hook_civicrm_tokenValues', function($e) {
+      if (in_array('affirm', $e->tokens['trans'])) {
+        foreach ($e->contactIDs as $cid) {
+          $e->details[$cid]['trans.affirm'] = ts('Yes');
+        }
+      }
+    });
+
+    $p = new TokenProcessor($this->dispatcher, [
+      'controller' => __CLASS__,
+      'smarty' => FALSE,
+    ]);
+    $p->addMessage('text', '!!{trans.affirm}!!', 'text/plain');
+    $p->addRow(['contactId' => $cid]);
+    $p->addRow(['contactId' => $cid, 'locale' => 'fr_FR']);
+    $p->addRow(['contactId' => $cid, 'locale' => 'es_MX']);
+
+    $expectText = [
+      '!!Yes!!',
+      '!!Oui!!',
+      '!!Sí!!',
+    ];
+
+    $rowCount = 0;
+    foreach ($p->evaluate()->getRows() as $key => $row) {
+      /** @var TokenRow */
+      $this->assertTrue($row instanceof TokenRow);
+      $this->assertEquals($expectText[$key], $row->render('text'));
+      $rowCount++;
+    }
+    $this->assertEquals(3, $rowCount);
+  }
+
   public function testGetMessageTokens() {
     $p = new TokenProcessor($this->dispatcher, [
       'controller' => __CLASS__,