#!/usr/bin/env bash
# sdict — terminal dictionary lookup
# Inspired by BreadOnPenguins/scripts/define
# Backends: Oxford Dictionaries API (set OD_APP_ID + OD_APP_KEY)
# sdcv with Oxford StarDict dict (offline, set SDICT_USE_SDCV=1)
# dictionaryapi.dev (free fallback, no config needed)
set -euo pipefail
BOLD='\033[1m'
DIM='\033[2m'
ITALIC='\033[3m'
BLUE='\033[34m'
CYAN='\033[36m'
GREEN='\033[32m'
YELLOW='\033[33m'
RESET='\033[0m'
usage() {
echo -e "${BOLD}sdict${RESET} — look up a word in the dictionary"
echo ""
echo -e " ${BOLD}Usage:${RESET} sdict <word>"
echo ""
echo -e " ${BOLD}Backends (checked in order):${RESET}"
echo " 1. Oxford Dictionaries API — set OD_APP_ID and OD_APP_KEY"
echo " 2. sdcv (offline Oxford) — set SDICT_USE_SDCV=1"
echo " 3. dictionaryapi.dev — free, no config needed"
exit 0
}
die() { echo -e "\033[31merror:\033[0m $1" >&2; exit 1; }
[[ $# -lt 1 || "$1" == "-h" || "$1" == "--help" ]] && usage
word="$1"
[[ "$word" =~ [^a-zA-Z-] ]] && die "invalid word: $word"
# --- Backend 1: Oxford Dictionaries API ---
oxford_lookup() {
local resp
resp=$(curl -sf --connect-timeout 5 --max-time 10 \
-H "app_id: $OD_APP_ID" \
-H "app_key: $OD_APP_KEY" \
"https://od-api-v2.oxforddictionaries.com/api/v2/entries/en-us/${word,,}") || return 1
echo -e "${BOLD}${BLUE}${word}${RESET}"
echo ""
# phonetic
local phonetic
phonetic=$(echo "$resp" | jq -r '
[.results[].lexicalEntries[].pronunciations[]? | select(.phoneticSpelling) | .phoneticSpelling] | first // empty
' 2>/dev/null)
[[ -n "$phonetic" ]] && echo -e " ${DIM}/$phonetic/${RESET}" && echo ""
# etymology
local etym
etym=$(echo "$resp" | jq -r '
[.results[].lexicalEntries[].entries[].etymologies[]?] | first // empty
' 2>/dev/null)
[[ -n "$etym" ]] && echo -e " ${DIM}Origin: $etym${RESET}" && echo ""
# definitions by part of speech
echo "$resp" | jq -r '
.results[].lexicalEntries[] |
.lexicalCategory.id as $pos |
.entries[].senses[:3][] |
"\($pos)|\(.definitions[0] // empty)|\(.examples[0].text? // empty)"
' 2>/dev/null | while IFS='|' read -r pos def example; do
[[ -z "$def" ]] && continue
echo -e " ${GREEN}${pos}${RESET}"
echo -e " ${def}"
[[ -n "$example" ]] && echo -e " ${DIM}\"${example}\"${RESET}"
echo ""
done
}
# --- Backend 2: sdcv (offline) ---
sdcv_lookup() {
command -v sdcv >/dev/null 2>&1 || return 1
local result
result=$(sdcv -n "$word" 2>/dev/null) || return 1
[[ "$result" == *"Nothing similar"* ]] && return 1
echo -e "${BOLD}${BLUE}${word}${RESET}"
echo ""
echo "$result" | sed 's/^/ /'
echo ""
}
# --- Backend 3: Free Dictionary API (fallback) ---
free_lookup() {
local resp
resp=$(curl -sf --connect-timeout 5 --max-time 10 \
"https://api.dictionaryapi.dev/api/v2/entries/en_US/${word}") || return 1
[[ "$resp" == *"No Definitions Found"* ]] && return 1
echo -e "${BOLD}${BLUE}${word}${RESET}"
echo ""
# phonetic
local phonetic
phonetic=$(echo "$resp" | jq -r '[.[].phonetic // empty] | first // empty' 2>/dev/null)
[[ -n "$phonetic" ]] && echo -e " ${DIM}${phonetic}${RESET}" && echo ""
# definitions
echo "$resp" | jq -r '
.[].meanings[] |
.partOfSpeech as $pos |
.definitions[:3][] |
"\($pos)|\(.definition)|\(.example // empty)"
' 2>/dev/null | while IFS='|' read -r pos def example; do
[[ -z "$def" ]] && continue
echo -e " ${GREEN}${pos}${RESET}"
echo -e " ${def}"
[[ -n "$example" ]] && echo -e " ${DIM}\"${example}\"${RESET}"
echo ""
done
echo -e " ${DIM}source: dictionaryapi.dev${RESET}"
}
# --- Dispatch ---
if [[ -n "${OD_APP_ID:-}" && -n "${OD_APP_KEY:-}" ]]; then
oxford_lookup && exit 0
echo -e "${DIM}Oxford API failed, trying fallback...${RESET}" >&2
fi
if [[ "${SDICT_USE_SDCV:-0}" == "1" ]]; then
sdcv_lookup && exit 0
echo -e "${DIM}sdcv failed, trying fallback...${RESET}" >&2
fi
free_lookup || die "no definition found for '${word}'"