Titouan Mathis

Developer & CTO at Studio Meta

An Alfred Workflow for Deepl

29/05/2023 in #terminal #deepl #zsh #alfred

I often use Deepl via its macOS application to translate various content, but it some times felt too slow for simple translations. I wanted an Alfred Workflow which could do the same translation a little bit quicker with a dedicated hotkey, so I ended up writing a small ZSH function and a corresponding Alfred Workflow.

A ZSH function

Here is the ZSH function I ended up writing:

#!/usr/bin/env zsh

set -o errexit
set -o nounset
set -o pipefail

if [[ "${TRACE-0}" == "1" ]]; then
  set -o xtrace
fi

if [[ "${1-}" =~ ^-*h(elp)?$ || $# == 0 ]]; then
  echo '
[email protected]

Translate a string with Deepl free API. You will need to define a
global DEEPL_API_TOKEN with your Deepl API token.

Usage:

    deepl [--from <from>] --to <to> <VALUE>
    deepl [-f <from>] -t <to> <VALUE>

Example:

    deepl --to fr Hello World
'
  exit
fi

main() {
  # Parse options with ZSH zparseopts utility
  zparseopts -E -D -- \
    -from:=FROM f:=FROM \
    -to:=TO t:=TO

  # Clean up option names from the values
  FROM="${FROM/--from =/}"
  FROM="${FROM/--from /}"
  FROM="${FROM/-f =/}"
  FROM="${FROM/-f /}"
  TO="${TO/--to =/}"
  TO="${TO/--to /}"
  TO="${TO/-t =/}"
  TO="${TO/-t /}"

  # Make sure Deepl API token is defined
  if [[ ! ${DEEPL_API_TOKEN:-} ]]
  then
    echo "Error: DEEPL_API_TOKEN not defined."
    exit 1
  fi

  # Make sure `jq` is installed
  if ! [[ -x "$(which jq)" ]]; then
    echo "Error: Missing `jq` dependency. Install it with `brew install jq`."
    exit
  fi

  # Translate the given string
  curl -s -X POST "https://api-free.deepl.com/v2/translate" \
    -H "Authorization: DeepL-Auth-Key $DEEPL_API_TOKEN" \
    -d "text=$@" \
    -d "target_lang=${TO:-en}" \
    -d "source_lang=$FROM" | jq -r ".translations[0].text"
}

main "$@"

To use it, place it in a file within your $PATH and make sure it can be executed. Copy the script above and then use the following commands:

# Paste the content of the script in a `deepl` file
$ pbpaste > /usr/local/bin/deepl
# Allow the file to be executed
$ chmod +x /usr/local/bin/deepl

You will then be able to use it from your terminal:

$ deepl --to fr 'Hello world!'
Bonjour à tous !
$ deepl --to de 'Hello world!'
Hallo Welt!

The source language is guessed by Deepl, but you can force it with the --from parameter. The default target language is English, but you can change it by editing the target_lang parameter in the script:

-    -d "target_lang=${TO:-en}" \
+    -d "target_lang=${TO:-fr}" \

An Alfred Workflow for Deepl

For the Alfred Workflow, I used a simple "Script filter" object with the following script:

# My DEEPL_API_TOKEN is stored in a `.localrc` file, it could be
# managed with Alfred own environment variables settings.
source ~/.localrc

# Print a JSON for Alfred
function print_alfred_output() {
  local input="$(< /dev/stdin)";
  printf '
{
  "items": [
    {
      "title": "%s",
      "arg": "%s",
      "subtitle": "Copy with ⌘+C, display with ⏎",
      "mods": {
        "cmd": {
          "subtitle" : "Copy the result with ⌘+C"
        }
      }
    }
  ]
}' "$input" "$input"
}

/usr/local/bin/deepl "{query}" | print_alfred_output

Make sure to enable the debounce feature to avoid calling the API for each new character typed in Alfred. In the "Script filter" settings, go to "Run behaviour" and in the section "Queue Delay" select the "Automatic delay after last character typed" value.

I added two more objects to my workflow:

  • A "Hotkey" object before the "Script filter" to be able to translate any selected text quickly
  • A "Large type" object after the "Script filter", so I can display the translated string in full screen if it does not fit in Alfred's box

-