#!/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 " 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}'"