(dev/translation#78) I18nSubscriber - Allow partial/negotiated locales
authorTim Otten <totten@civicrm.org>
Fri, 5 Aug 2022 10:02:04 +0000 (03:02 -0700)
committerTim Otten <totten@civicrm.org>
Mon, 29 Aug 2022 03:00:14 +0000 (20:00 -0700)
Definitions:

* A fully-supported locale is valid in all localization services (eg
  `ts()`, `Civi::format()`, `$dbLocale`).
* A "negotiated" or "mixed" locale can be used for communication, but
  it is not fully supported by all other layers. Consequently, it
  requires some kind of fallback or substitution.

Before:

* When an API call requests an alternate language (v3's `option.language` or
  v4's `setLanguage()`), it only activates fully-supported locales.
* Specifically, it validates against the multilingual configuration -- those
  are fully supported locales.) It otherwise ignores the alternate language.

After:

* When an API call requests an alternate language, it cacn activate
  fully-supported locales as well as mixed locales.
* Specifically, it asks `Locale::negotiate(...)` to examine the local
  configuration/resources and determine the effective locale.

Civi/API/Subscriber/I18nSubscriber.php

index dadce282baa75abe2c9d32ef43e5ea5a8f849302..5cff1073004ea0a39f62de449ab63f877f26d2e2 100644 (file)
@@ -12,6 +12,7 @@
 namespace Civi\API\Subscriber;
 
 use Civi\API\Events;
+use Civi\Core\Locale;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
@@ -24,8 +25,9 @@ class I18nSubscriber implements EventSubscriberInterface {
    * Used for rolling back language to its original setting after the api call.
    *
    * @var array
+   *   Array(string $requestId => \Civi\Core\Locale $locale).
    */
-  public $originalLang = [];
+  protected $originalLocale = [];
 
   /**
    * @return array
@@ -56,7 +58,11 @@ class I18nSubscriber implements EventSubscriberInterface {
       $language = $params['language'] ?? NULL;
     }
     if ($language) {
-      $this->setLocale($language, $apiRequest['id']);
+      $newLocale = Locale::negotiate($language);
+      if ($newLocale) {
+        $this->originalLocale[$apiRequest['id']] = Locale::detect();
+        $newLocale->apply();
+      }
     }
   }
 
@@ -70,48 +76,9 @@ class I18nSubscriber implements EventSubscriberInterface {
   public function onApiRespond(\Civi\API\Event\Event $event) {
     $apiRequest = $event->getApiRequest();
 
-    if (!empty($this->originalLang[$apiRequest['id']])) {
-      global $tsLocale;
-      global $dbLocale;
-      $tsLocale = $this->originalLang[$apiRequest['id']]['tsLocale'];
-      $dbLocale = $this->originalLang[$apiRequest['id']]['dbLocale'];
-    }
-  }
-
-  /**
-   * Sets the tsLocale and dbLocale for multi-lingual sites.
-   * Some code duplication from CRM/Core/BAO/ConfigSetting.php retrieve()
-   * to avoid regressions from refactoring.
-   * @param string $newLocale
-   * @param int $requestId
-   * @throws \API_Exception
-   */
-  public function setLocale($newLocale, $requestId) {
-    $domain = new \CRM_Core_DAO_Domain();
-    $domain->id = \CRM_Core_Config::domainID();
-    $domain->find(TRUE);
-
-    // Check if the site is multi-lingual
-    if ($domain->locales && $newLocale) {
-      // Validate language, otherwise a bad dbLocale could probably lead to sql-injection.
-      if (!array_key_exists($newLocale, \Civi::settings()->get('languageLimit'))) {
-        throw new \API_Exception(ts('Language not enabled: %1', [1 => $newLocale]));
-      }
-
-      global $dbLocale;
-      global $tsLocale;
-
-      // Store original value to be restored in $this->onApiRespond
-      $this->originalLang[$requestId] = [
-        'tsLocale' => $tsLocale,
-        'dbLocale' => $dbLocale,
-      ];
-
-      // Set suffix for table names - use views if more than one language
-      $dbLocale = "_{$newLocale}";
-
-      // Also set tsLocale - CRM-4041
-      $tsLocale = $newLocale;
+    if (!empty($this->originalLocale[$apiRequest['id']])) {
+      $this->originalLocale[$apiRequest['id']]->apply();
+      unset($this->originalLocale[$apiRequest['id']]);
     }
   }