How to check your grammar and spelling on Linux using LanguageTool, which is some kind of European technology.

Of all the spelling and grammar checkers available on Linux, LanguageTool is one of them. It is an alternative to Grammarly, I suppose. There is a free component of the software that has reduced functionality. It runs as a webservice on the local machine. Follow the setup instructions in the code listing and you will be rewarded with a simple command that checks your spelling and grammar, like this:
Usage: check-grammar FILENAME Where the filetype of FILENAME is one of: txt,html,md,markdown,rst,ipynb,json,xliff If the input file does not have a file extension, plain text will (possibly erroneously) be assumed.
This requires a Linux system with Python, and Docker (docker.io). The instructions have been tested on Ubuntu 22.04 . Save this code as the file "check-grammar" and follow the setup instructions. The file is also available on Github as a Gist: check-grammar.sh, MIT License.
#!/bin/bash # Check grammar using LanguageTool # Copyright (c) 2025 Vic Dellarocco # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation # files (the "Software"), to deal in the Software without # restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons # to whom the Software is furnished to do so, subject to the # following conditions: # # The above copyright notice and this permission notice shall # be included in all copies or substantial portions of the # Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS # OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # https://languagetool.org/ # https://github.com/languagetool-org/languagetool # https://pypi.org/project/pyLanguagetool # FIRST TIME SETUP: # I am not scripting this part because it requires downloading # some big files. You will have to do this manually. # Make a place for this program. I used ~/opt/languagetool . # mkdir ~/opt/languagetool # cd ~/opt/languagetool # mkdir ngrams # Save THIS file in the ~/opt/languagetool directory. # I am using languagetool version 6.5 because the instructions # from languagetool say to: # "Use ngrams-xx-2015* files for LanguageTool <= 6.5, # ngrams-xx-2024* files for LanguageTool >= 6.6." # but the ngrams-xx-2024* files aren't available for download! # Download and extract the ngrams file: trust me, you need them. # WARNING: big file: 8961853713 bytes. # wget https://languagetool.org/download/ngram-data/ngrams-en-20150817.zip # unzip ngrams-en-20150817.zip -d ngrams # Erikvl87 has the best docker image for this: # https://github.com/Erikvl87/docker-languagetool # https://hub.docker.com/r/erikvl87/languagetool # Get the docker image: # docker pull erikvl87/languagetool:6.5 # Create the container: # docker run \ # --name languagetool \ # -p 127.0.0.1:8010:8010 \ # -v `pwd`/ngrams:/ngrams:ro \ # erikvl87/languagetool:6.5 # If that worked, then the container should be installed and # running. You can stop it if you want like this: # docker stop languagetool # If that all worked, then you can use THIS file to check your # grammar as if it were a real normal command line command: # chmod+x check-grammar # ./check-grammar filetocheck # You can put a symbolic link in your ~/bin/ dir to make it even # easier to use: # cd ~/bin # ln -s ~/opt/languagetool/check-grammar # A quick reference of selected docker commands: # docker stop languagetool # stops the container. # docker start languagetool # start if not already running # docker restart languagtool # start if stopped else restart # docker rm languagetool # remove the container. # docker ps # list running containers. # docker ps -a # list all containers. USAGE='Usage: check-grammar FILENAME Where the filetype of FILENAME is one of: txt,html,md,markdown,rst,ipynb,json,xliff If the input file does not have a file extension, plain text will (possibly erroneously) be assumed. ' # Function to determine file type based on extension determine_file_type() { local file="$1" local extension="${file##*.}" # Convert extension to lowercase extension="${extension,,}" case "$extension" in txt) DOCTYPE="txt" return 0 ;; html) DOCTYPE="html" return 0 ;; md|markdown) DOCTYPE="md" return 0 ;; rst) DOCTYPE="rst" return 0 ;; ipynb) DOCTYPE="ipynb" return 0 ;; json) DOCTYPE="json" return 0 ;; xliff) DOCTYPE="xliff" return 0 ;; *) DOCTYPE="txt" # Use "txt" for unrecognized types return 1 ;; esac } FULL_PATH_TO_SCRIPT="$(realpath "$0")" SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")" # VENV detection and activation. function detect-venv () { python -c 'if 1:#for indentation import sys ss=sys.prefix != sys.base_prefix # print("%s\n%s\n%s" % (sys.prefix,sys.base_prefix,ss) ) if ss: sys.exit(0) sys.exit(1) ' return $? } function activate-venv () { # shellcheck disable=SC1091 source "$SCRIPT_DIRECTORY"/venv/bin/activate || true; detect-venv VENV=$? if [ $VENV = 1 ]; then echo "No venv available." echo "Need a venv and these python dependencies:" echo "" # If you have additional dependencies that are not # in the requirements.txt file, let the user know. sed 's/^/ /' "$SCRIPT_DIRECTORY"/requirements.txt echo "" echo "" read -rp "Do you want to create a python venv and install python dependencies (y/n) " response case ${response:0:1} in y|Y ) echo "Creating venv..." python -m venv "$SCRIPT_DIRECTORY"/venv echo "Activating venv." # shellcheck disable=SC1091 source "$SCRIPT_DIRECTORY"/venv/bin/activate # Sanity check, then Install python dependencies: detect-venv VENV=$? if [ $VENV = 1 ]; then echo "No active venv. Refusing to procede." return 1 fi # You can put install instructions here # for additional dependencies that are not # in the requirements.txt file, like this: # "&& pip install pyLanguagetool \" : \ && echo 'Installing python libraries.' \ && python -m pip install -r "$SCRIPT_DIRECTORY"/requirements.txt \ ; ;; n|N ) echo "Ok, no action taken." ;; * ) echo "Ok, no action taken." ;; esac unset response fi unset VENV } function run-app () { # shellcheck disable=SC2078 if [ "Run your program:" ]; then # Sanity check: detect-venv VENV=$? if [ $VENV = 1 ]; then echo "No active venv. Refusing to procede." return 1 fi # Put your script logic here: # Check for -h or --help in the arguments for arg in "$@"; do if [[ "$arg" == "-h" || "$arg" == "--help" ]]; then echo "$USAGE" exit 0 fi done # Initialize an empty array to hold the filtered arguments filtered_args=() # Loop through each argument passed to the script for arg in "$@"; do # Check if the argument does not start with a dash if [[ "$arg" != -* ]]; then # Add the argument to the filtered_args array filtered_args+=("$arg") fi done # Sets the DOCTYPE variable: determine_file_type "${filtered_args[0]}" # shellcheck disable=SC2181 if [ $? -eq 0 ]; then echo "File type: $DOCTYPE" else echo "The input file either has no file extension" echo "or the file extension is unsupported." echo "Plain text will (possibly erroneously) be assumed." echo "Supported file types are:" echo " txt,html,md,markdown,rst,ipynb,json,xliff" # Or you could refuse to proceed: # echo "Unknown file type: ${filtered_args[0]} " # return 1 fi # start the container if it is not already running: docker start languagetool # Note: the language type is hard-coded here: pylanguagetool -a http://localhost:8010/v2/ -l en-US -t "$DOCTYPE" "$@" fi } # Call the functions: activate-venv run-app "$@"
So, yeah, I did this and it changed my life.