Skip to main content
added 3 characters in body
Source Link
(defvar 'autoauto-translate-region-mode nil
  "Automatically translate the selection.")
(make-variable-buffer-local 'auto-translate-region-mode)
(defvar 'autoauto-translate--last-text nil)
(make-variable-buffer-local 'auto-translate--last-text)
(defvar 'autoauto-translate--last-translation nil)
(make-variable-buffer-local 'auto-translate--last-translation)

(defun auto-translate-call-engine (text)
  "Translate TEXT and return the result."
  TODO)

(defun auto-translate-region-redisplay ()
  "Redisplay the last cached region translation."
  ;; Display auto-translate--last-translation somewhere
  TODO)
(defun auto-translate-region-now ()
  "Display the region's translation."
  ;; Get the selected text and normalize it a bit
  (let* ((raw-text (buffer-substring-no-properties (region-beginning) (region-end)))
        (text (replace-regexp-in-string "\\`[^[:alpha:]]+" ""
                                        (replace-regexp-in-string "[^[:alpha:]]+\\'" "" raw-text))))
    (unless (string= text auto-translate--last-text)
      ;; The selected text has changed, so update the translation
      (setq auto-translate--last-translation (auto-translate-call-engine text)
            auto-translate--last-text text))
    ;; (Re)display the translated text
    (auto-translate-region-redisplay)))
(defun auto-translate-region-maybe ()
  "Display the region's translation if automatic region translation is active
and the mark is active."
  (if (and auto-translate-region-mode mark-active)
      (auto-translate-region-translate)))
(defvar 'auto-translate-region-mode nil
  "Automatically translate the selection.")
(make-variable-buffer-local 'auto-translate-region-mode)
(defvar 'auto-translate--last-text nil)
(make-variable-buffer-local 'auto-translate--last-text)
(defvar 'auto-translate--last-translation nil)
(make-variable-buffer-local 'auto-translate--last-translation)

(defun auto-translate-call-engine (text)
  "Translate TEXT and return the result."
  TODO)

(defun auto-translate-region-redisplay ()
  "Redisplay the last cached region translation."
  ;; Display auto-translate--last-translation somewhere
  TODO)
(defun auto-translate-region-now ()
  "Display the region's translation."
  ;; Get the selected text and normalize it a bit
  (let* ((raw-text (buffer-substring-no-properties (region-beginning) (region-end)))
        (text (replace-regexp-in-string "\\`[^[:alpha:]]+" ""
                                        (replace-regexp-in-string "[^[:alpha:]]+\\'" "" raw-text))))
    (unless (string= text auto-translate--last-text)
      ;; The selected text has changed, so update the translation
      (setq auto-translate--last-translation (auto-translate-call-engine text)
            auto-translate--last-text text))
    ;; (Re)display the translated text
    (auto-translate-region-redisplay)))
(auto-translate-region-maybe ()
  "Display the region's translation if automatic region translation is active
and the mark is active."
  (if (and auto-translate-region-mode mark-active)
      (auto-translate-region-translate)))
(defvar auto-translate-region-mode nil
  "Automatically translate the selection.")
(make-variable-buffer-local 'auto-translate-region-mode)
(defvar auto-translate--last-text nil)
(make-variable-buffer-local 'auto-translate--last-text)
(defvar auto-translate--last-translation nil)
(make-variable-buffer-local 'auto-translate--last-translation)

(defun auto-translate-call-engine (text)
  "Translate TEXT and return the result."
  TODO)

(defun auto-translate-region-redisplay ()
  "Redisplay the last cached region translation."
  ;; Display auto-translate--last-translation somewhere
  TODO)
(defun auto-translate-region-now ()
  "Display the region's translation."
  ;; Get the selected text and normalize it a bit
  (let* ((raw-text (buffer-substring-no-properties (region-beginning) (region-end)))
        (text (replace-regexp-in-string "\\`[^[:alpha:]]+" ""
                                        (replace-regexp-in-string "[^[:alpha:]]+\\'" "" raw-text))))
    (unless (string= text auto-translate--last-text)
      ;; The selected text has changed, so update the translation
      (setq auto-translate--last-translation (auto-translate-call-engine text)
            auto-translate--last-text text))
    ;; (Re)display the translated text
    (auto-translate-region-redisplay)))
(defun auto-translate-region-maybe ()
  "Display the region's translation if automatic region translation is active
and the mark is active."
  (if (and auto-translate-region-mode mark-active)
      (auto-translate-region-translate)))
Source Link

Emacs provides a number of hook variables, each of which contains a list of functions that are executed when a specific event happens. But there isn't a hook that indicates that the selection has changed.

The selection, called “region” in Emacs terminology, is the part of the buffer between the point (the cursor) and the mark, and the selection is highlighted when the mark is active. There are hooks for mark (de)activation, but there's no hook triggered when the point (or the mark changes). Furthermore the selection can change because the text changes — in Emacs this can happen even when the selection is active.

So if you want to detect all selection changes, you have to add your code to post-command-hook.

(add-hook 'post-command-hook 'auto-translate-region-maybe)

If running your code after each command is too expensive, you could run it on a delay instead, by setting an idle timer.

(run-with-idle-timer 1 nil 'auto-translate-region-maybe)

The reason I called the function “maybe” is that you probably want to run it in certain buffers, but post-command-hook and timers are global. Detecting which buffer may need a translation is not straightforward since the active buffer may have just changed, especially with the timer method. Doing this correctly and efficiently turns out to be pretty complicated. Unfortunately I didn't find a package that monitors region changes and provides a proper region-changed-hook. In my answer I'll keep things (relatively) simple and show how to display a translation only for the active window's buffer.

Translation is pretty expensive so you should avoid running it again with the same arguments. Remember the last string you sent and don't update the translation result if that hasn't changed.

(defvar 'auto-translate-region-mode nil
  "Automatically translate the selection.")
(make-variable-buffer-local 'auto-translate-region-mode)
(defvar 'auto-translate--last-text nil)
(make-variable-buffer-local 'auto-translate--last-text)
(defvar 'auto-translate--last-translation nil)
(make-variable-buffer-local 'auto-translate--last-translation)

(defun auto-translate-call-engine (text)
  "Translate TEXT and return the result."
  TODO)

(defun auto-translate-region-redisplay ()
  "Redisplay the last cached region translation."
  ;; Display auto-translate--last-translation somewhere
  TODO)
(defun auto-translate-region-now ()
  "Display the region's translation."
  ;; Get the selected text and normalize it a bit
  (let* ((raw-text (buffer-substring-no-properties (region-beginning) (region-end)))
        (text (replace-regexp-in-string "\\`[^[:alpha:]]+" ""
                                        (replace-regexp-in-string "[^[:alpha:]]+\\'" "" raw-text))))
    (unless (string= text auto-translate--last-text)
      ;; The selected text has changed, so update the translation
      (setq auto-translate--last-translation (auto-translate-call-engine text)
            auto-translate--last-text text))
    ;; (Re)display the translated text
    (auto-translate-region-redisplay)))
(auto-translate-region-maybe ()
  "Display the region's translation if automatic region translation is active
and the mark is active."
  (if (and auto-translate-region-mode mark-active)
      (auto-translate-region-translate)))