From ed2412ca526037848456b8e95abc784d0b8d1b0d Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Wed, 15 Jul 2015 14:16:01 -0500
Subject: [PATCH] i18n: Add translation to the field hint

---
 css/redactor.css                              |   5 +
 include/ajax.i18n.php                         |  27 +++
 include/class.forms.php                       |   2 +-
 .../templates/dynamic-field-config.tmpl.php   |   2 +-
 js/redactor-osticket.js                       |   2 +
 js/redactor-plugins.js                        | 160 ++++++++++++++++++
 scp/ajax.php                                  |   9 +-
 scp/js/scp.js                                 |   2 +-
 8 files changed, 205 insertions(+), 4 deletions(-)

diff --git a/css/redactor.css b/css/redactor.css
index a3dc97844..16d0d7c41 100644
--- a/css/redactor.css
+++ b/css/redactor.css
@@ -926,3 +926,8 @@ body .redactor-box-fullscreen {
   font-size: 12px;
   text-transform: uppercase;
 }
+.redactor-editor[dir=rtl] {
+  direction: rtl;
+  text-align: right;
+  unicode-bidi: embed;
+}
diff --git a/include/ajax.i18n.php b/include/ajax.i18n.php
index 9d3e76b60..4665f3ab2 100644
--- a/include/ajax.i18n.php
+++ b/include/ajax.i18n.php
@@ -100,6 +100,33 @@ class i18nAjaxAPI extends AjaxController {
                 Http::response(500, sprintf("%s: Unable to commit language"));
     }
 
+    function getConfiguredLanguages() {
+        global $cfg;
+
+        $primary = $cfg->getPrimaryLanguage();
+        $info = Internationalization::getLanguageInfo($primary);
+        $langs = array(
+            $primary => array(
+                'name' => Internationalization::getLanguageDescription($primary),
+                'flag' => strtolower($info['flag']),
+                'direction' => $info['direction'] ?: 'ltr',
+            ),
+        );
+
+        foreach ($cfg->getSecondaryLanguages() as $l) {
+            $info = Internationalization::getLanguageInfo($l);
+            $langs[$l] = array(
+                'name' => Internationalization::getLanguageDescription($l),
+                'flag' => strtolower($info['flag']),
+                'direction' => $info['direction'] ?: 'ltr',
+            );
+        }
+        $json = JsonDataEncoder::encode($langs);
+        Http::cacheable(md5($json), $cfg->lastModified());
+
+        return $json;
+    }
+
     function getSecondaryLanguages() {
         global $cfg;
 
diff --git a/include/class.forms.php b/include/class.forms.php
index 135916269..d472d3de3 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -2016,7 +2016,7 @@ class ThreadEntryField extends FormField {
     }
 
     function getWidget($widgetClass=false) {
-        if ($hint = $this->get('hint'))
+        if ($hint = $this->getLocal('hint'))
             $this->set('placeholder', $hint);
         $this->set('hint', null);
         $widget = parent::getWidget($widgetClass);
diff --git a/include/staff/templates/dynamic-field-config.tmpl.php b/include/staff/templates/dynamic-field-config.tmpl.php
index 24bf9a79f..732ec709b 100644
--- a/include/staff/templates/dynamic-field-config.tmpl.php
+++ b/include/staff/templates/dynamic-field-config.tmpl.php
@@ -181,7 +181,7 @@
 
 <script type="text/javascript">
    // Make translatable fields translatable
-   $('input[data-translate-tag], textarea[data-translate-tag]').translatable();
+   $('input[data-translate-tag]').translatable();
 </script>
 
 <style type="text/css">
diff --git a/js/redactor-osticket.js b/js/redactor-osticket.js
index d9a26e932..c39352812 100644
--- a/js/redactor-osticket.js
+++ b/js/redactor-osticket.js
@@ -311,6 +311,8 @@ $(function() {
         }
         if (el.hasClass('fullscreen'))
             options['plugins'].push('fullscreen');
+        if (el.data('translateTag'))
+            options['plugins'].push('translatable');
         if ($('#ticket_thread[data-thread-id]').length)
             options['imageManagerJson'] += '?threadId=' + $('#ticket_thread').data('threadId');
         getConfig().then(function(c) {
diff --git a/js/redactor-plugins.js b/js/redactor-plugins.js
index 8f01f5f6c..d20519f21 100644
--- a/js/redactor-plugins.js
+++ b/js/redactor-plugins.js
@@ -1842,3 +1842,163 @@ RedactorPlugins.contexttypeahead = function() {
     }
   };
 };
+
+RedactorPlugins.translatable = function() {
+  return {
+    langs: undefined,
+    config: undefined,
+    textareas: {},
+    current: undefined,
+    primary: undefined,
+    button: undefined,
+
+    init: function() {
+      $.ajax({
+        url: 'ajax.php/i18n/langs/all',
+        success: this.translatable.setLangs.bind(this)
+      });
+      getConfig().then(this.translatable.setConfig.bind(this));
+      this.opts.keydownCallback = this.translatable.showCommit.bind(this);
+      this.translatable.translateTag = this.$textarea.data('translateTag');
+    },
+
+    setLangs: function(langs) {
+      this.translatable.langs = langs;
+      this.translatable.buildDropdown();
+    },
+
+    setConfig: function(config) {
+      this.translatable.config = config;
+      this.translatable.buildDropdown();
+    },
+
+    buildDropdown: function() {
+      if (!this.translatable.config || !this.translatable.langs)
+        return;
+
+      var plugin = this.translatable,
+          primary = this.$textarea,
+          primary_lang = plugin.config.primary_language.replace('-','_'),
+          primary_info = plugin.langs[primary_lang],
+          dropdown = {},
+          items = {};
+
+      langs = plugin.langs;
+      plugin.textareas[primary_lang] = primary;
+      plugin.primary = plugin.current = primary_lang;
+
+      dropdown[primary_lang] = {
+        title: '<i class="flag flag-'+primary_info.flag+'"></i> '+primary_info.name,
+        func: function() { plugin.switchTo(primary_lang); }
+      }
+
+      $.each(langs, function(lang, info) {
+        if (lang == primary_lang)
+          return;
+        dropdown[lang] = {
+          title: '<i class="flag flag-'+info.flag+'"></i> '+info.name,
+          func: function() { plugin.switchTo(lang); }
+        };
+        plugin.textareas[lang] = primary.clone(false).attr({
+          lang: lang,
+          dir: info['direction'],
+          'class': '',
+        })
+        .removeAttr('name').removeAttr('data-translate-tag')
+        .text('')
+        .insertAfter(primary);
+      });
+
+      // Add the button to the toolbar
+      plugin.button = this.button.add('translate', __('Translate')),
+      this.button.setAwesome('translate', 'flag flag-' + plugin.config.primary_lang_flag);
+      plugin.button.parent().addClass('pull-right');
+      this.button.addDropdown(plugin.button, dropdown);
+
+      // Flip back to primary language before submitting
+      this.$textarea.closest('form').submit(function() {
+        plugin.switchTo(primary_lang);
+      });
+    },
+
+    switchTo: function(lang) {
+      var that = this;
+
+      if (lang == this.translatable.current)
+        return;
+
+      if (this.translatable.translations === undefined) {
+        this.translatable.fetch('ajax.php/i18n/translate/' + this.translatable.translateTag)
+        .then(function(json) {
+          that.translatable.translations = json;
+          $.each(json, function(l, text) {
+            that.translatable.textareas[l].val(text);
+          });
+          // Now switch to the language
+          that.translatable.switchTo(lang);
+        });
+        return;
+      }
+
+      var html = this.$editor.html();
+      this.$textarea.val(this.clean.onSync(html));
+      this.$textarea = this.translatable.textareas[lang];
+      this.code.set(this.$textarea.val());
+      this.translatable.current = lang;
+
+      this.button.setAwesome('translate', 'flag flag-' + this.translatable.langs[lang].flag);
+      this.$editor.attr({lang: lang, dir: this.translatable.langs[lang].direction});
+    },
+
+    showCommit: function() {
+      var plugin = this.translatable;
+
+      if (this.translatable.current == this.translatable.primary) {
+        if (this.translatable.$commit)
+          this.translatable.$commit
+          .slideUp(function() { $(this).remove(); plugin.$commit = undefined; });
+        return true;
+      }
+
+      if (this.translatable.$commit)
+        return true;
+
+      this.translatable.$commit = $('<div class="language-commit"></div>')
+      .hide()
+      .appendTo(this.$box)
+      .append($('<button type="button" class="white button commit"><i class="fa fa-save icon-save"></i> '+__('Save')+'</button>')
+        .on('click', $.proxy(this.translatable.commit, this))
+      )
+      .slideDown();
+    },
+
+    commit: function() {
+      var changes = {}, self = this,
+          plugin = this.translatable,
+          $commit = plugin.$commit;
+      $commit.find('button').empty().text(' '+__('Saving'))
+          .prop('disabled', true)
+          .prepend($('<i>').addClass('fa icon-spin icon-spinner'));
+      changes[plugin.current] = this.code.get();
+      $.ajax('ajax.php/i18n/translate/' + plugin.translateTag, {
+        type: 'post',
+        data: changes,
+        success: function() {
+          $commit.slideUp(function() { $(this).remove(); plugin.$commit = undefined; });
+        }
+      });
+    },
+
+    urlcache: {},
+    fetch: function( url, data, callback ) {
+      var urlcache = this.translatable.urlcache;
+      if ( !urlcache[ url ] ) {
+        urlcache[ url ] = $.Deferred(function( defer ) {
+          $.ajax( url, { data: data, dataType: 'json' } )
+            .then( defer.resolve, defer.reject );
+        }).promise();
+      }
+      return urlcache[ url ].done( callback );
+    },
+  };
+};
diff --git a/scp/ajax.php b/scp/ajax.php
index 36d9a847a..6971008a6 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -222,6 +222,7 @@ $dispatcher = patterns('',
         url_get('^(?P<lang>[\w_]+)?/tips/(?P<namespace>[\w_.]+)$', 'getTipsJsonForLang')
     )),
     url('^/i18n/', patterns('ajax.i18n.php:i18nAjaxAPI',
+        url_get('^langs/all$', 'getConfiguredLanguages'),
         url_get('^langs$', 'getSecondaryLanguages'),
         url_get('^translate/(?P<tag>\w+)$', 'getTranslations'),
         url_post('^translate/(?P<tag>\w+)$', 'updateTranslations'),
@@ -248,5 +249,11 @@ $dispatcher = patterns('',
 Signal::send('ajax.scp', $dispatcher);
 
 # Call the respective function
-print $dispatcher->resolve($ost->get_path_info());
+$rv = $dispatcher->resolve($ost->get_path_info());
+
+// Indicate JSON response content-type
+if (is_string($rv) && $rv[0] == '{')
+    Http::response(200, $rv, 'application/json');
+
+print $rv;
 ?>
diff --git a/scp/js/scp.js b/scp/js/scp.js
index a15b0e64f..19a0e1376 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -386,7 +386,7 @@ var scp_prep = function() {
 
 
    // Make translatable fields translatable
-   $('input[data-translate-tag], textarea[data-translate-tag]').translatable();
+   $('input[data-translate-tag]').translatable();
 
    if (window.location.hash) {
      $('ul.tabs li a[href="' + window.location.hash + '"]').trigger('click');
-- 
GitLab