diff --git a/CRM/Contact/Tokens.php b/CRM/Contact/Tokens.php
index bed30b86f737bc14d81e64789940ba5fea4b14a5..01c1e358a30c5b1774f4227e28b09aa224f462b4 100644
--- a/CRM/Contact/Tokens.php
+++ b/CRM/Contact/Tokens.php
@@ -415,7 +415,7 @@ class CRM_Contact_Tokens extends CRM_Core_EntityTokens {
     // Manually add in the abbreviated state province as that maps to
     // what has traditionally been delivered.
     $tokensMetadata['address_primary.state_province_id:abbr'] = $tokensMetadata['address_primary.state_province_id:label'];
-    $tokensMetadata['address_primary.state_province_id:abbr']['name'] = 'state_province_id:abbr';
+    $tokensMetadata['address_primary.state_province_id:abbr']['name'] = 'address_primary.state_province_id:abbr';
     $tokensMetadata['address_primary.state_province_id:abbr']['audience'] = 'user';
     // Hide the label for now because we are not sure if there are paths
     // where legacy token resolution is in play where this could not be resolved.
@@ -654,7 +654,7 @@ class CRM_Contact_Tokens extends CRM_Core_EntityTokens {
       ],
       'address_primary.country_id.region_id:name' => [
         'title' => ts('World Region'),
-        'name' => 'country_id.region_id.name',
+        'name' => 'address_primary.country_id.region_id:name',
         'type' => 'mapped',
         'api_v3' => 'world_region',
         'options' => NULL,
diff --git a/CRM/Core/DAO/AllCoreTables.data.php b/CRM/Core/DAO/AllCoreTables.data.php
index 456041dd3f3b279dc0d385f1aee7fd4b6f40e6f5..e043d1c0cbdff5e836b7382bf91c8d7a0f9f6178 100644
--- a/CRM/Core/DAO/AllCoreTables.data.php
+++ b/CRM/Core/DAO/AllCoreTables.data.php
@@ -53,7 +53,7 @@ return [
     'table' => 'civicrm_translation',
   ],
   'CRM_Core_DAO_Worldregion' => [
-    'name' => 'Worldregion',
+    'name' => 'WorldRegion',
     'class' => 'CRM_Core_DAO_Worldregion',
     'table' => 'civicrm_worldregion',
   ],
diff --git a/CRM/Core/DAO/Worldregion.php b/CRM/Core/DAO/Worldregion.php
index 3131b0e9408f3fd29f63ba376ce2424dc36b65c1..375beb1d00f917f16279b65893fa5848926fdcce 100644
--- a/CRM/Core/DAO/Worldregion.php
+++ b/CRM/Core/DAO/Worldregion.php
@@ -6,11 +6,11 @@
  *
  * Generated from xml/schema/CRM/Core/Worldregion.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:9f82f7db4b9ea76b609244a81635d1f1)
+ * (GenCodeChecksum:2604f3d61e573a996ee6f5d7bf4d04dc)
  */
 
 /**
- * Database access object for the Worldregion entity.
+ * Database access object for the WorldRegion entity.
  */
 class CRM_Core_DAO_Worldregion extends CRM_Core_DAO {
   const EXT = 'civicrm';
@@ -82,7 +82,7 @@ class CRM_Core_DAO_Worldregion extends CRM_Core_DAO {
           'required' => TRUE,
           'where' => 'civicrm_worldregion.id',
           'table_name' => 'civicrm_worldregion',
-          'entity' => 'Worldregion',
+          'entity' => 'WorldRegion',
           'bao' => 'CRM_Core_DAO_Worldregion',
           'localizable' => 0,
           'html' => [
@@ -101,7 +101,7 @@ class CRM_Core_DAO_Worldregion extends CRM_Core_DAO {
           'where' => 'civicrm_worldregion.name',
           'export' => TRUE,
           'table_name' => 'civicrm_worldregion',
-          'entity' => 'Worldregion',
+          'entity' => 'WorldRegion',
           'bao' => 'CRM_Core_DAO_Worldregion',
           'localizable' => 0,
           'add' => '1.8',
diff --git a/CRM/Core/EntityTokens.php b/CRM/Core/EntityTokens.php
index 9672f08c1407003dd05564242698f893a31d3d36..9d33cf13599b1ee82740aa4f88f573e14d23586f 100644
--- a/CRM/Core/EntityTokens.php
+++ b/CRM/Core/EntityTokens.php
@@ -290,10 +290,19 @@ class CRM_Core_EntityTokens extends AbstractTokenSubscriber {
   protected function getPseudoValue(string $realField, string $pseudoKey, $fieldValue): string {
     $bao = CRM_Core_DAO_AllCoreTables::getFullName($this->getMetadataForField($realField)['entity']);
     if ($pseudoKey === 'name') {
+      // There is a theoretical possibility fieldValue could be an array but
+      // specifically for preferred communication type - but real world usage
+      // hitting this is unlikely & the unexpectation is unclear so commenting,
+      // rather than adding handling.
       $fieldValue = (string) CRM_Core_PseudoConstant::getName($bao, $realField, $fieldValue);
     }
     if ($pseudoKey === 'label') {
-      $fieldValue = (string) CRM_Core_PseudoConstant::getLabel($bao, $realField, $fieldValue);
+      $newValue = [];
+      // Preferred communication method is an array that would resolve to (e.g) 'Phone, Email'
+      foreach ((array) $fieldValue as $individualValue) {
+        $newValue[] = CRM_Core_PseudoConstant::getLabel($bao, $realField, $individualValue);
+      }
+      $fieldValue = implode(', ', $newValue);
     }
     if ($pseudoKey === 'abbr' && $realField === 'state_province_id') {
       // hack alert - currently only supported for state.
diff --git a/Civi/Api4/WorldRegion.php b/Civi/Api4/WorldRegion.php
new file mode 100644
index 0000000000000000000000000000000000000000..a62c1c94abd73602af7141b8d5a07ecd46cfa453
--- /dev/null
+++ b/Civi/Api4/WorldRegion.php
@@ -0,0 +1,24 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+namespace Civi\Api4;
+
+/**
+ * WordRegion entity.
+ *
+ * This entity stores world regions.
+ *
+ * @searchable none
+ * @since 5.59
+ * @package Civi\Api4
+ */
+class WorldRegion extends Generic\DAOEntity {
+
+}
diff --git a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
index 095dd65b1b62a70e1c4b57e368cc4e52fcd69e63..0377bbd046b11701d3f36092d93fe009c9305e94 100644
--- a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
+++ b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
@@ -425,8 +425,8 @@ London, 90210
    */
   public function testContactTokens(): void {
     // Freeze the time at the start of the test, so checksums don't suffer from second rollovers.
-    putenv('TIME_FUNC=frozen');
-    CRM_Utils_Time::setTime(date('Y-m-d H:i:s'));
+    $restoreTime = $this->useFrozenTime();
+
     $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']);
     $this->hookClass->setHook('civicrm_tokens', [$this, 'hookTokens']);
 
@@ -483,10 +483,63 @@ emo
     $this->assertEquals($expected_parts[0], $returned_parts[0]);
     $this->assertApproxEquals($expected_parts[1], $returned_parts[1], 2);
     $this->assertEquals($expected_parts[2], $returned_parts[2]);
+  }
+
+  /**
+   * Test tokens resolve when parsed one at a time.
+   *
+   * Assuming that `testContactTokens()` has asserted tokens work en masse, we have another
+   * question -- do the tokens work the same when evaluated en-masse and individually?
+   */
+  public function testTokensIndividually(): void {
+    // Freeze the time at the start of the test, so checksums don't suffer from second rollovers.
+    // This variable releases the time on destruct so needs to be assigned for the
+    // duration of the test.
+    /** @noinspection PhpUnusedLocalVariableInspection */
+    $restoreTime = $this->useFrozenTime();
+
+    $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']);
+    $this->hookClass->setHook('civicrm_tokens', [$this, 'hookTokens']);
+
+    $this->createCustomGroupWithFieldsOfAllTypes([]);
+    $tokenData = $this->getOldContactTokens();
+    $this->setupContactFromTokeData($tokenData);
+
+    $context = ['contactId' => $tokenData['contact_id']];
+    $render = static function (string $templateText) use ($context, $tokenData) {
+      try {
+        return CRM_Core_TokenSmarty::render(['text' => $templateText], $context)['text'];
+      }
+      catch (\Throwable $t) {
+        return 'EXCEPTION:' . $t->getMessage();
+      }
+    };
 
-    // reset time
-    putenv('TIME_FUNC');
-    CRM_Utils_Time::resetTime();
+    // Build $tokenLines, a list of expressions like 'contact.display_name:{contact.display_name}'
+    $tokenLines = [];
+    $tokenNames = array_keys($this->getAdvertisedTokens());
+    foreach ($tokenNames as $tokenName) {
+      $tokenLines[] = trim($tokenName, '{}') . ':' . $tokenName;
+    }
+    foreach (array_keys($tokenData) as $key) {
+      $tokenLines[] .= "contact.$key:{contact.$key}";
+    }
+    $tokenLines = array_unique($tokenLines);
+    sort($tokenLines);
+
+    // Evaluate all these token lines
+    $oneByOne = array_map($render, $tokenLines);
+    $allAtOnce = $render(implode("\n", $tokenLines));
+    $this->assertEquals($allAtOnce, implode("\n", $oneByOne));
+
+    $emptyLines = preg_grep('/:$/', $oneByOne);
+    $this->assertEquals([
+      'contact.address_primary.county_id:label:',
+      'contact.contact_is_deleted:',
+      'contact.county:',
+      'contact.custom_6:',
+      'contact.do_not_phone:',
+    ], array_values($emptyLines), 'Most tokens should have data.');
   }
 
   /**
@@ -721,8 +774,7 @@ emo
    * Note it will render additional custom fields if they exist.
    *
    * @return array
-   *
-   * @throws \CRM_Core_Exception
+   * @noinspection PhpDocMissingThrowsInspection
    */
   public function getOldContactTokens(): array {
     return [
@@ -868,6 +920,21 @@ primary_phone:123-456
 ', $messageHtml);
   }
 
+  /**
+   * Temporarily freeze time, as perceived through `CRM_Utils_Time`.
+   *
+   * @return \CRM_Utils_AutoClean
+   */
+  protected function useFrozenTime(): CRM_Utils_AutoClean {
+    $oldTimeFunc = getenv('TIME_FUNC');
+    putenv('TIME_FUNC=frozen');
+    CRM_Utils_Time::setTime(date('Y-m-d H:i:s'));
+    return CRM_Utils_AutoClean::with(function () use ($oldTimeFunc) {
+      putenv($oldTimeFunc === NULL ? 'TIME_FUNC' : "TIME_FUNC=$oldTimeFunc");
+      CRM_Utils_Time::resetTime();
+    });
+  }
+
   /**
    * @param array $tokenData
    *
diff --git a/xml/schema/Core/Worldregion.xml b/xml/schema/Core/Worldregion.xml
index 9e2b2a9b29550837a38aa60a71ac471aa28b3fd1..f24a8cc02672f7af3fe2e210c9268a28fa0bc6ff 100644
--- a/xml/schema/Core/Worldregion.xml
+++ b/xml/schema/Core/Worldregion.xml
@@ -4,6 +4,7 @@
   <base>CRM/Core</base>
   <class>Worldregion</class>
   <name>civicrm_worldregion</name>
+  <entity>WorldRegion</entity>
   <add>1.8</add>
   <field>
     <name>id</name>