Answers.Body: <p>I would say the most cleanest way is to handle all translations via Yii2 <a href="https://www.yiiframework.com/doc/api/2.0/yii-i18n-dbmessagesource" rel="nofollow noreferrer">DbMessageSource</a>.</p>
  
  <p>You could say you want to store all translations into the database instead of files.
  There is an <code>MessageSource::EVENT_MISSING_TRANSLATION</code> event in which you can store all translations into your new database table. As soon as Craft recognizes a new translation you'll have the chance to insert the new record</p>
  
  <p>Here is a code snipped from our translation plugin</p>
  
  <pre><code>Event::on(
      MessageSource::class,
      MessageSource::EVENT_MISSING_TRANSLATION,
      function(MissingTranslationEvent $event) {
          if ($event-&gt;message) {
              $message = Translate::getInstance()-&gt;getTranslation()-&gt;addMissingTranslation($event-&gt;category, $event-&gt;message, $event-&gt;language);
              if ($message instanceof Message) {
                  $event-&gt;translatedMessage = $message-&gt;translation;
              }
          }
      }
  );
  </code></pre>
  
  <h3>Your DbMessageSource class could look like this</h3>
  
  <pre><code>class DbMessageSource extends \yii\i18n\DbMessageSource
  {
      public $fallback = [];
      public $useMultiSiteTranslationFeature = false;
      protected $fallbackClass;
  
      /**
       * Return something for everything which is requested
       *
       * @param string $name
       *
       * @return mixed|null
       */
      public function __get($name)
      {
          try {
              return parent::__get($name);
          } catch (\Exception $e) {
              return null;
          }
      }
  
      /**
       * @param string $name
       * @param mixed  $value
       */
      public function __set($name, $value)
      {
          try {
              parent::__set($name, $value);
          } catch (\Exception $e) {
              $this-&gt;$name = $value;
          }
      }
  
      public function translateFallback($category, $message, $language)
      {
          if ($this-&gt;useMultiSiteTranslationFeature &amp;&amp; Craft::$app-&gt;getRequest()-&gt;getIsSiteRequest()) {
              $this-&gt;useMultiSiteTranslationFeature = false;
              $translated = $this-&gt;translate($category, $message, $language);
              $this-&gt;useMultiSiteTranslationFeature = true;
              if ($translated) {
                  return $translated;
              }
          }
          if (count($this-&gt;fallback) === 0) {
              return false;
          }
          if (!$this-&gt;fallbackClass) {
              $this-&gt;fallbackClass = \Yii::createObject($this-&gt;fallback);
          }
  
          return $this-&gt;fallbackClass-&gt;translate($category, $message, $language);
      }
  
      protected function loadMessages($category, $language)
      {
          if ($this-&gt;useMultiSiteTranslationFeature &amp;&amp; Craft::$app-&gt;getRequest()-&gt;getIsSiteRequest()) {
              $category .= '_' . Craft::$app-&gt;getSites()-&gt;currentSite-&gt;handle;
          }
  
          if ($this-&gt;enableCaching) {
              $key = [
                  __CLASS__,
                  $category,
                  $language,
              ];
              $messages = $this-&gt;cache-&gt;get($key);
              if ($messages === false) {
                  $messages = $this-&gt;loadMessagesFromDb($category, $language);
                  $this-&gt;cache-&gt;set($key, $messages, $this-&gt;cachingDuration);
              }
  
              return $messages;
          }
  
          return $this-&gt;loadMessagesFromDb($category, $language);
      }
  }
  </code></pre>
  
  <p>The rest is common Craft work to create create a custom form/list for your new CP section or you create an element type and let Craft handle everything.</p>
  
  <p>As you can see in the picture below: all translatable strings from all sources/categories/plugins are recognized and our customers are able to change all translations in all plugins into all languages. Even if the creator of the plugin never wanted to include certain translations into certain languages</p>
  
  <p><a href="https://i.sstatic.net/RO3zz.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/RO3zz.png" alt="Screenshot"></a></p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2018-05-02T09:27:36.643Z
Answers.Text: Thanks Robin! Any chance you could share the plugin or a simple version of it? I'm not a developer eeeek
Answers.UserDisplayName: James Greig
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2018-05-03T11:55:50.913Z
Answers.Text: Thanks @robin, got this working (yay) but it only seems to translate the backend dashboard/CP terms, how would you translate the text in a Dropdown field?
Answers.UserDisplayName: James Greig
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2018-05-03T12:01:27.207Z
Answers.Text: @3stripe `DropDown` fields are translated in the category 'site' (code `'label' => Craft::t('site', $option['label'])`) so you'll have to include the component with setting `site` like stated in the `readme.md`
Answers.UserDisplayName: Robin Schambach
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2018-04-30T14:17:34.610Z
Answers.LastActivityDate: 2018-04-30T14:28:13.690Z
Answers.LastEditDate: 2018-04-30T14:28:13.690Z
Answers.UserDisplayName: Robin Schambach
Answers.Body: <p>check out Enupal Translate, you can enable an option to allow Enupal Translate handle the translations folder of the plugins.</p>
  
  <p><a href="https://enupal.com/en/craft-plugins/translate" rel="nofollow noreferrer">Enupal Translate</a></p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2018-05-02T21:46:53.827Z
Answers.Text: Interesting. But does is overwrite plugins translation files or does it really create a new element for each translation? If so it would be a huge overhead
Answers.UserDisplayName: Robin Schambach
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2018-05-02T18:17:58.433Z
Answers.LastActivityDate: 2018-05-02T18:17:58.433Z
Answers.UserDisplayName: Andre L.
Body: <p>We have some navigation links being translated via the /translations folder but the client needs the ability to change them too...</p>
  
  <p>Is there a way to make the translations editable from the control panel?</p>
  
Tags: <localization>
Title: Way for clients to edit translation file(s) in control panel?
ThreadText: # Way for clients to edit translation file(s) in control panel?
  
  (Asked by: username_0 on 2018-04-30)
  
  We have some navigation links being translated via the /translations folder but the client needs the ability to change them too...
  
  Is there a way to make the translations editable from the control panel?
  
  **Tags:** localization
  
  
  ---
  
  ## 2 answers
  
  ### Answer by username_1 on 2018-04-30. Score: 3
  
  I would say the most cleanest way is to handle all translations via Yii2 [DbMessageSource](https://www.yiiframework.com/doc/api/2.0/yii-i18n-dbmessagesource).
  
  You could say you want to store all translations into the database instead of files.
  There is an `MessageSource::EVENT_MISSING_TRANSLATION` event in which you can store all translations into your new database table. As soon as Craft recognizes a new translation you'll have the chance to insert the new record
  
  Here is a code snipped from our translation plugin
  
  ```
  Event::on(
      MessageSource::class,
      MessageSource::EVENT_MISSING_TRANSLATION,
      function(MissingTranslationEvent $event) {
          if ($event->message) {
              $message = Translate::getInstance()->getTranslation()->addMissingTranslation($event->category, $event->message, $event->language);
              if ($message instanceof Message) {
                  $event->translatedMessage = $message->translation;
              }
          }
      }
  );
  
  ```
  
  ### Your DbMessageSource class could look like this
  
  ```
  class DbMessageSource extends \yii\i18n\DbMessageSource
  {
      public $fallback = [];
      public $useMultiSiteTranslationFeature = false;
      protected $fallbackClass;
  
      /**
       * Return something for everything which is requested
       *
       * @param string $name
       *
       * @return mixed|null
       */
      public function __get($name)
      {
          try {
              return parent::__get($name);
          } catch (\Exception $e) {
              return null;
          }
      }
  
      /**
       * @param string $name
       * @param mixed  $value
       */
      public function __set($name, $value)
      {
          try {
              parent::__set($name, $value);
          } catch (\Exception $e) {
              $this->$name = $value;
          }
      }
  
      public function translateFallback($category, $message, $language)
      {
          if ($this->useMultiSiteTranslationFeature && Craft::$app->getRequest()->getIsSiteRequest()) {
              $this->useMultiSiteTranslationFeature = false;
              $translated = $this->translate($category, $message, $language);
              $this->useMultiSiteTranslationFeature = true;
              if ($translated) {
                  return $translated;
              }
          }
          if (count($this->fallback) === 0) {
              return false;
          }
          if (!$this->fallbackClass) {
              $this->fallbackClass = \Yii::createObject($this->fallback);
          }
  
          return $this->fallbackClass->translate($category, $message, $language);
      }
  
      protected function loadMessages($category, $language)
      {
          if ($this->useMultiSiteTranslationFeature && Craft::$app->getRequest()->getIsSiteRequest()) {
              $category .= '_' . Craft::$app->getSites()->currentSite->handle;
          }
  
          if ($this->enableCaching) {
              $key = [
                  __CLASS__,
                  $category,
                  $language,
              ];
              $messages = $this->cache->get($key);
              if ($messages === false) {
                  $messages = $this->loadMessagesFromDb($category, $language);
                  $this->cache->set($key, $messages, $this->cachingDuration);
              }
  
              return $messages;
          }
  
          return $this->loadMessagesFromDb($category, $language);
      }
  }
  
  ```
  
  The rest is common Craft work to create create a custom form/list for your new CP section or you create an element type and let Craft handle everything.
  
  As you can see in the picture below: all translatable strings from all sources/categories/plugins are recognized and our customers are able to change all translations in all plugins into all languages. Even if the creator of the plugin never wanted to include certain translations into certain languages
  
  [![Screenshot](https://i.sstatic.net/RO3zz.png)](https://i.sstatic.net/RO3zz.png)
  
  > **Comments:**
  > **username_0**: Thanks username_1! Any chance you could share the plugin or a simple version of it? I'm not a developer eeeek  
  > **username_0**: Thanks @username_1, got this working (yay) but it only seems to translate the backend dashboard/CP terms, how would you translate the text in a Dropdown field?  
  > **username_1**: @3stripe `DropDown` fields are translated in the category 'site' (code `'label' => Craft::t('site', $option['label'])`) so you'll have to include the component with setting `site` like stated in the `readme.md`  
  
  ---
  
  ### Answer by username_2 on 2018-05-02. Score: 0
  
  check out Enupal Translate, you can enable an option to allow Enupal Translate handle the translations folder of the plugins.
  
  [Enupal Translate](https://enupal.com/en/craft-plugins/translate)
  
  > **Comments:**
  > **username_1**: Interesting. But does is overwrite plugins translation files or does it really create a new element for each translation? If so it would be a huge overhead  
  
  ---
  
  