Build an Urdu Root Dictionary
Model Urdu vocabulary as a graph with ArangoDB and AQL traversals.
Introduction
30 Second Summary
Every Urdu word carries a hidden connection to other words through a shared three-letter root. If you know the root, you can unlock an entire family of related vocabulary.
In this project, you will build a browser-based Urdu dictionary powered by a graph database. You will model words, roots, and synonyms as a connected graph, then search and explore those relationships through an interactive web interface.
What You'll Build
A local Urdu root dictionary application that uses ArangoDB's native graph engine to reveal how words connect through shared trilateral roots, served through a Flask backend with proper Nastaliq script rendering.
By the end of this project, you'll have:
- A graph database with vertex collections for words and roots, plus edge collections for derivation and synonym relationships.
- A bulk-imported dataset of 200 trilateral roots and 14,400+ conjugation entries from a real Arabic-Urdu research dataset.
- A searchable web interface that renders Urdu in Nastaliq script and lets you navigate between connected words using AQL graph traversal queries.
- Secret Mission: Add antonym edges and a "Word of the Day" feature that picks a random word and displays its full graph neighborhood.
Are there any prerequisites?
This project assumes you have Docker installed and working, Python 3.14 available, and Git installed. You should also be comfortable with basic Python scripting and have a general understanding of HTML.
Before We Start
Before We Start...
Set Up Your Environment
Before you can build the dictionary, you need a running graph database to store the data and a Python environment to talk to it.
In this step, you will start an ArangoDB instance inside a Docker container, create a Python project with the right packages installed, and confirm everything connects.
In this step, get ready to:
- Start an ArangoDB container and verify the web UI is accessible.
- Create a project directory with a Python virtual environment and install dependencies.
- Write and run a connection test that creates your dictionary database.
Start the ArangoDB container
ArangoDB is the graph database that will store your Urdu words, roots, and the connections between them. You will run it inside a Docker container so there is nothing to install on your machine directly.
- Start the ArangoDB container by running this command in your terminal:
docker run -d --name urdu-dict -e ARANGO_ROOT_PASSWORD=rootpass -p 8529:8529 arangodb/enterprise:3.12.6
What does this command do?
- -d runs the container in the background so your terminal stays free.
- --name urdu-dict gives the container a memorable name you can use to stop or restart it later.
- -e ARANGO_ROOT_PASSWORD=rootpass sets the root password for the database.
- -p 8529:8529 maps the container's default ArangoDB port to the same port on your machine.
- Open your browser and navigate to http://localhost:8529
- You should see the ArangoDB web interface login page prompting for credentials.
Don't see the login page?
- Run docker ps to check the container is running. If it is not listed, run docker logs urdu-dict to see what went wrong.
- If you see an error about port 8529 already being in use, another process is occupying that port. Stop it or choose a different host port (e.g. -p 8530:8529).
- If you see an error that the container name is already in use, remove the old one first with docker rm -f urdu-dict and rerun the command.
Help me troubleshoot my ArangoDB Docker container
Create the project directory and install dependencies
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir urdu-root-dictionary
cd urdu-root-dictionary
- Confirm the folder exists by running:
ls
You should see an empty directory (no output from ls). That is expected since you just created it.
- Create a Python virtual environment by running the command for your operating system below, then activate it:
macOS
python3 -m venv venv
source venv/bin/activate
Your terminal prompt should now start with (venv) indicating the virtual environment is active.
Windows
python -m venv venv
venv\Scripts\activate
Your terminal prompt should now start with (venv) indicating the virtual environment is active.
What is a virtual environment?
A virtual environment is an isolated Python workspace. Packages you install inside it stay separate from your system Python, so different projects never conflict with each other.
- Install the two packages your project needs by running:
pip install python-arango==8.3.3 flask==3.1.3
What are these packages?
- python-arango is the official Python driver for ArangoDB. It lets your scripts create databases, collections, and run queries.
- Flask is a lightweight web framework. You will use it later to serve the dictionary interface in your browser.
- Verify the packages installed correctly by running:
pip show python-arango flask
You should see version 8.3.3 for python-arango and 3.1.3 for Flask in the output.
Seeing an install error?
- Make sure your virtual environment is active. You should see (venv) at the start of your terminal prompt.
- If you see a network timeout, check your internet connection and try running the install command again.
Help me fix my pip install error
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Select the urdu-root-dictionary folder on your Desktop, then click Open.
Create and run the connection test
You now have a running database and a Python environment that can talk to it. Time to prove the two can connect.
- In VS Code, create a new file called test_connection.py by pressing Cmd+N (macOS) or Ctrl+N (Windows), then save it with Cmd+S (macOS) or Ctrl+S (Windows) and name it test_connection.py.
- Copy the following code into test_connection.py:
from arango import ArangoClient
# Connect to ArangoDB running in the Docker container
client = ArangoClient(hosts="http://localhost:8529")
# Authenticate as root against the system database
sys_db = client.db("_system", username="root", password="rootpass")
# Create the urdu_dict database if it doesn't exist yet
if not sys_db.has_database("urdu_dict"):
sys_db.create_database("urdu_dict")
print("Created database: urdu_dict")
else:
print("Database urdu_dict already exists")
# Connect to the new database and confirm
db = client.db("urdu_dict", username="root", password="rootpass")
print(f"Connected to: {db.name}")
print(f"ArangoDB version: {sys_db.version()}")
What does this code do?
- It connects to ArangoDB at localhost:8529 using the root password you set in the Docker command.
- It checks whether a database called urdu_dict already exists. If not, it creates one.
- It prints the database name and ArangoDB version to confirm everything is working.
- Save test_connection.py.
- Run the script in your terminal (make sure your venv is still active) by running:
python test_connection.py
You should see output confirming the database was created and displaying the ArangoDB version number.
- Open your browser and go back to http://localhost:8529.
- Log in with username root and password rootpass.
- In the top-left dropdown, you should see urdu_dict listed as a database.
Script failing to connect?
- Make sure your Docker container is still running. Run docker ps and look for urdu-dict in the output.
- Confirm your venv is active (you should see (venv) in your prompt). If not, reactivate it.
- If you see ModuleNotFoundError: No module named 'arango', you are running the script outside the venv. Activate it and try again.
Help me debug my test_connection.py script
✔️ Awesome, I've got everything!
Great. Double check you have saved test_connection.py and that your terminal showed the database creation message.
ⓧ I'd like to double check the full code
Here is the complete test_connection.py for comparison:
from arango import ArangoClient
client = ArangoClient(hosts="http://localhost:8529")
sys_db = client.db("_system", username="root", password="rootpass")
if not sys_db.has_database("urdu_dict"):
sys_db.create_database("urdu_dict")
print("Created database: urdu_dict")
else:
print("Database urdu_dict already exists")
db = client.db("urdu_dict", username="root", password="rootpass")
print(f"Connected to: {db.name}")
print(f"ArangoDB version: {sys_db.version()}")
Your environment is ready. ArangoDB is running, Python can connect to it, and your urdu_dict database is waiting for data. Next up, you will design the graph schema that models how words relate to roots and to each other.
Design and Create the Graph Schema
In the previous step, you got ArangoDB running in Docker and confirmed your Python script can connect to it. Now it is time to design the data model.
Urdu vocabulary has a natural graph structure. Words derive from trilateral roots, and words can be synonyms of each other. In this step, you will create the collections and graph that represent these relationships inside ArangoDB.
In this step, get ready to:
- Create vertex collections for words and roots.
- Create edge collections for derivation and synonym relationships.
- Bind everything into a named graph with edge definitions.
Create vertex and edge collections
A graph database stores data in two types of collections. Vertex collections hold the entities (your words and roots). Edge collections hold the relationships between entities (which word derives from which root, and which words are synonyms).
- Create a new file called setup_graph.py in your project directory.
- Add the connection setup and vertex collection creation by pasting this code:
from arango import ArangoClient
client = ArangoClient(hosts="http://localhost:8529")
db = client.db("urdu_dict", username="root", password="rootpass")
# Create vertex collections
if not db.has_collection("words"):
db.create_collection("words")
print("Created collection: words")
if not db.has_collection("roots"):
db.create_collection("roots")
print("Created collection: roots")
What does this code do?
The script connects directly to the urdu_dict database (not _system like before). This is where all your dictionary data will live.
The has_collection checks make the script safe to re-run. If the collections already exist, it skips creation instead of crashing.
- Below the vertex collections, add the edge collection creation code:
# Create edge collections
if not db.has_collection("derived_from"):
db.create_collection("derived_from", edge=True)
print("Created edge collection: derived_from")
if not db.has_collection("synonym_of"):
db.create_collection("synonym_of", edge=True)
print("Created edge collection: synonym_of")
What makes edge collections different?
The edge=True parameter tells ArangoDB this collection stores relationships, not standalone documents. Every document in an edge collection must have _from and _to fields pointing to vertices.
The derived_from collection will link words to their trilateral roots. The synonym_of collection will link words to other words that share similar meanings.
Create the named graph and verify the schema
Collections on their own are just containers. A named graph binds them together with edge definitions that tell ArangoDB which vertex collections each edge collection connects.
- Add the named graph creation at the bottom of setup_graph.py:
# Create the named graph
if not db.has_graph("urdu_language"):
graph = db.create_graph("urdu_language")
graph.create_edge_definition(
edge_collection="derived_from",
from_vertex_collections=["words"],
to_vertex_collections=["roots"],
)
graph.create_edge_definition(
edge_collection="synonym_of",
from_vertex_collections=["words"],
to_vertex_collections=["words"],
)
print("Created graph: urdu_language")
else:
print("Graph urdu_language already exists")
print("Schema setup complete!")
What do edge definitions do?
Each create_edge_definition call declares a rule: derived_from edges go FROM words TO roots. This means "a word derives from a root."
The synonym_of edges go FROM words TO words. This means "a word is a synonym of another word." Later, ArangoDB will use these definitions to power graph traversal queries.
- Save setup_graph.py.
- Run the script by executing this command in your terminal:
python setup_graph.py
- You should see output confirming each collection and the graph were created:
What should I see?
Created collection: words
Created collection: roots
Created edge collection: derived_from
Created edge collection: synonym_of
Created graph: urdu_language
Schema setup complete!
- Open the ArangoDB web UI at http://localhost:8529 in your browser.
- Select the urdu_dict database from the database dropdown at the top.
- Click Collections in the left sidebar. You should see all four collections listed: words, roots, derived_from, and synonym_of.
- Click Graphs in the left sidebar. You should see the urdu_language graph listed.
Don't see the collections or graph?
- Make sure you selected the urdu_dict database in the dropdown, not _system. Collections are database-specific.
- Check your terminal for error messages. If you see a connection error, confirm the Docker container is still running with docker ps.
- If you see a permissions error, double-check that your password in the script matches rootpass.
Ask for help: My setup_graph.py script ran but I don't see collections in the ArangoDB web UI. How do I fix this?
✔️ Awesome, I've got everything!
Great. Double check you have saved setup_graph.py and that all four collections plus the graph appear in the web UI.
ⓧ I'd like to double check the full code
Here is the complete setup_graph.py file:
from arango import ArangoClient
client = ArangoClient(hosts="http://localhost:8529")
db = client.db("urdu_dict", username="root", password="rootpass")
# Create vertex collections
if not db.has_collection("words"):
db.create_collection("words")
print("Created collection: words")
if not db.has_collection("roots"):
db.create_collection("roots")
print("Created collection: roots")
# Create edge collections
if not db.has_collection("derived_from"):
db.create_collection("derived_from", edge=True)
print("Created edge collection: derived_from")
if not db.has_collection("synonym_of"):
db.create_collection("synonym_of", edge=True)
print("Created edge collection: synonym_of")
# Create the named graph
if not db.has_graph("urdu_language"):
graph = db.create_graph("urdu_language")
graph.create_edge_definition(
edge_collection="derived_from",
from_vertex_collections=["words"],
to_vertex_collections=["roots"],
)
graph.create_edge_definition(
edge_collection="synonym_of",
from_vertex_collections=["words"],
to_vertex_collections=["words"],
)
print("Created graph: urdu_language")
else:
print("Graph urdu_language already exists")
print("Schema setup complete!")
Why a graph database instead of a relational one?
In a relational database, finding all words that share a root requires multiple JOIN operations across tables. As the dataset grows, these queries get expensive.
In ArangoDB, the same question is a single graph traversal: start at a root vertex, follow the derived_from edges inward, and you immediately have every word derived from that root. The relationships are stored directly, not computed at query time.
Your graph schema is ready. Next up, you will populate it with thousands of real Urdu words from the Arabic-Urdu Conjugation Dataset.
Seed the Dictionary with Data
Your graph schema is ready with empty collections waiting for data. Now you need actual Urdu vocabulary to fill them.
In this step, you will clone a real linguistic dataset containing over 14,400 Arabic verb conjugations with Urdu translations, import it into your graph, and supplement it with hand-crafted vocabulary that includes synonym relationships. By the end, your database will have hundreds of roots connected to thousands of words through edges.
In this step, get ready to:
- Clone the Arabic-Urdu Conjugation Dataset and set up the import script.
- Parse 14,400+ CSV rows into root and word documents with derivation edges.
- Add hand-crafted Urdu nouns with synonym connections and verify the full import.
Clone the dataset and set up the import script
The Arabic-Urdu Conjugation Dataset is an open-source linguistic resource containing 200 trilateral Arabic roots and their conjugations with Urdu translations. Each row in its CSV file includes the root, the conjugated Arabic verb, the Urdu translation, and grammatical annotations (gender, number, tense, person).
- Clone the dataset repository into your project directory by running this command:
git clone https://github.com/haqnawaz99/Arabic-Urdu-Conjugation-Dataset.git
- Confirm the repository was cloned by running:
ls Arabic-Urdu-Conjugation-Dataset
You should see a CSV file and a README.md listed in the output.
Don't see the folder?
Make sure git is installed and your terminal is in your project directory (the same folder where setup_graph.py lives).
help me troubleshoot why git clone failed for the Arabic-Urdu Conjugation Dataset
Now create the import script that will read this CSV and populate your graph.
- Create a new file called import_dataset.py and add the following imports and database setup:
import csv
import os
import re
from arango import ArangoClient
client = ArangoClient(hosts="http://localhost:8529")
db = client.db("urdu_dict", username="root", password="rootpass")
roots_col = db.collection("roots")
words_col = db.collection("words")
derived_col = db.collection("derived_from")
synonym_col = db.collection("synonym_of")
# Clear existing data for re-runs
derived_col.truncate()
synonym_col.truncate()
words_col.truncate()
roots_col.truncate()
# --- Find the CSV file in the cloned dataset ---
dataset_dir = "Arabic-Urdu-Conjugation-Dataset"
csv_file = None
for fname in os.listdir(dataset_dir):
if fname.endswith(".csv"):
csv_file = os.path.join(dataset_dir, fname)
break
if csv_file is None:
print("ERROR: No CSV file found in Arabic-Urdu-Conjugation-Dataset/")
print("Make sure you cloned the repo: git clone https://github.com/haqnawaz99/Arabic-Urdu-Conjugation-Dataset.git")
exit(1)
print(f"Reading: {csv_file}")
What does this code do?
- The script connects to your urdu_dict database and grabs references to all four collections you created earlier.
- It truncates (empties) all collections so you can safely re-run the script without duplicating data.
- It searches the cloned dataset directory for a .csv file automatically, so you do not need to hard-code the filename.
Parse the CSV and insert roots and words
Each row in the CSV contains a trilateral root, an Arabic conjugation, and its Urdu translation along with grammatical metadata. The script needs to extract unique roots, create word documents for each conjugation, and build edges connecting words to their root.
- Add the CSV parsing loop below your existing code in import_dataset.py:
# --- Parse CSV and extract unique roots ---
roots_seen = set()
word_docs = []
edge_docs = []
def make_root_key(root_str):
"""Convert Arabic root to a safe document key."""
return re.sub(r'[^a-zA-Z0-9_-]', '', root_str.replace(' ', '-')) or root_str.encode('utf-8').hex()
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for i, row in enumerate(reader):
root_raw = row.get("Root", "").strip()
conjugation = row.get("Conjugation", "").strip()
translation = row.get("Translation", "").strip()
gender = row.get("Gender", "").strip()
number = row.get("Number", "").strip()
tense = row.get("Tense", "").strip()
person = row.get("Person", "").strip()
if not root_raw or not translation:
continue
# Create root if not seen
root_key = make_root_key(root_raw)
if root_key not in roots_seen:
roots_seen.add(root_key)
roots_col.insert({
"_key": root_key,
"letters": root_raw,
"meaning": f"root: {root_raw}",
})
# Create word document
word_key = f"conj_{i}"
word_docs.append({
"_key": word_key,
"urdu": translation,
"transliteration": "",
"meaning": f"{conjugation} ({tense}, {person}, {gender}, {number})",
"pos": "verb",
"origin": "Arabic",
"arabic": conjugation,
"gender": gender,
"number": number,
"tense": tense,
"person": person,
})
# Create derivation edge
edge_docs.append({
"_from": f"words/{word_key}",
"_to": f"roots/{root_key}",
})
# --- Batch insert words and edges ---
print(f"Inserting {len(word_docs)} words...")
words_col.import_bulk(word_docs)
print(f"Inserting {len(edge_docs)} derivation edges...")
derived_col.import_bulk(edge_docs)
print(f"\nInserted {len(roots_seen)} unique roots")
What does this code do?
- The make_root_key function converts Arabic root strings into safe document keys that ArangoDB can use (only alphanumeric characters, hyphens, and underscores).
- The loop reads each CSV row, inserts unique roots one at a time (so duplicates are skipped), and accumulates word and edge documents in lists.
- After the loop, import_bulk inserts all word documents and all derivation edges in efficient batch operations instead of one-by-one network calls.
- Each derived_from edge uses _from (the word) and _to (the root) to create a directional link in the graph.
Add hand-crafted vocabulary and verify the import
The bulk dataset covers verb conjugations, but a real dictionary also needs common nouns and synonym relationships. Adding a small set of hand-crafted words demonstrates the full power of the graph model.
- Add the supplemental words, synonym edges, and verification code below your existing code in import_dataset.py:
# --- Add hand-crafted supplement with synonyms ---
supplement_words = [
{"_key": "kitab", "urdu": "کتاب", "transliteration": "kitab", "meaning": "book", "pos": "noun", "origin": "Arabic"},
{"_key": "ilm", "urdu": "علم", "transliteration": "ilm", "meaning": "knowledge, science", "pos": "noun", "origin": "Arabic"},
{"_key": "madrasa", "urdu": "مدرسہ", "transliteration": "madrasa", "meaning": "school", "pos": "noun", "origin": "Arabic"},
{"_key": "muallim", "urdu": "معلم", "transliteration": "muallim", "meaning": "teacher", "pos": "noun", "origin": "Arabic"},
{"_key": "mudarris", "urdu": "مدرس", "transliteration": "mudarris", "meaning": "teacher, instructor", "pos": "noun", "origin": "Arabic"},
{"_key": "dars", "urdu": "درس", "transliteration": "dars", "meaning": "lesson", "pos": "noun", "origin": "Arabic"},
{"_key": "taleem", "urdu": "تعلیم", "transliteration": "taleem", "meaning": "education, instruction", "pos": "noun", "origin": "Arabic"},
]
for word in supplement_words:
words_col.insert(word)
print(f"Added {len(supplement_words)} supplemental words")
# Add synonym edges
synonyms = [
("words/muallim", "words/mudarris"),
("words/taleem", "words/dars"),
]
for from_id, to_id in synonyms:
synonym_col.insert({"_from": from_id, "_to": to_id})
synonym_col.insert({"_from": to_id, "_to": from_id})
print(f"Added {len(synonyms) * 2} synonym edges")
# --- Verification ---
print("\n--- Statistics ---")
print(f" Roots: {roots_col.count()}")
print(f" Words: {words_col.count()}")
print(f" Derivation edges: {derived_col.count()}")
print(f" Synonym edges: {synonym_col.count()}")
print("\n--- Sample traversal: first 5 words from a random root ---")
cursor = db.aql.execute("""
LET root = FIRST(FOR r IN roots LIMIT 1 RETURN r)
FOR word IN 1..1 INBOUND root GRAPH 'urdu_language'
OPTIONS {edgeCollections: ['derived_from']}
LIMIT 5
RETURN {urdu: word.urdu, meaning: word.meaning}
""")
for doc in cursor:
print(f" {doc['urdu']} - {doc['meaning']}")
print("\nImport complete!")
What does this code do?
- Seven common Urdu nouns are inserted individually with transliterations and meanings.
- Bidirectional synonym edges connect muallim (teacher) to mudarris (instructor), and taleem (education) to dars (lesson). Each synonym pair gets two edges so traversal works in both directions.
- The verification section prints collection counts and runs an AQL graph traversal that starts at a root and walks INBOUND through derived_from edges to find words. This proves the graph connections are working.
- Save import_dataset.py.
- Run the import script by executing:
python import_dataset.py
You should see output showing the number of words inserted (14,400+), the number of roots (~200), the supplemental words added, synonym edges created, and a sample traversal printing 5 Urdu words with their meanings.
Seeing a FileNotFoundError?
Make sure the Arabic-Urdu-Conjugation-Dataset folder is in the same directory as import_dataset.py. Run ls to confirm both are side by side.
If you see a connection error, make sure your ArangoDB container is still running with docker ps.
help me debug why my import_dataset.py script cannot find the CSV or connect to ArangoDB
- Open the ArangoDB web UI at http://localhost:8529 and sign in with username root and password rootpass.
- Select the urdu_dict database from the dropdown.
- Click Collections in the left sidebar. You should see document counts next to each collection name confirming the data was imported.
✔️ Awesome, I've got everything!
Great. Double check you have saved import_dataset.py and that the script ran without errors.
ⓧ I'd like to double check the full code
Here is the complete import_dataset.py for comparison:
import csv
import os
import re
from arango import ArangoClient
client = ArangoClient(hosts="http://localhost:8529")
db = client.db("urdu_dict", username="root", password="rootpass")
roots_col = db.collection("roots")
words_col = db.collection("words")
derived_col = db.collection("derived_from")
synonym_col = db.collection("synonym_of")
# Clear existing data for re-runs
derived_col.truncate()
synonym_col.truncate()
words_col.truncate()
roots_col.truncate()
# --- Find the CSV file in the cloned dataset ---
dataset_dir = "Arabic-Urdu-Conjugation-Dataset"
csv_file = None
for fname in os.listdir(dataset_dir):
if fname.endswith(".csv"):
csv_file = os.path.join(dataset_dir, fname)
break
if csv_file is None:
print("ERROR: No CSV file found in Arabic-Urdu-Conjugation-Dataset/")
print("Make sure you cloned the repo: git clone https://github.com/haqnawaz99/Arabic-Urdu-Conjugation-Dataset.git")
exit(1)
print(f"Reading: {csv_file}")
# --- Parse CSV and extract unique roots ---
roots_seen = set()
word_docs = []
edge_docs = []
def make_root_key(root_str):
"""Convert Arabic root to a safe document key."""
return re.sub(r'[^a-zA-Z0-9_-]', '', root_str.replace(' ', '-')) or root_str.encode('utf-8').hex()
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for i, row in enumerate(reader):
root_raw = row.get("Root", "").strip()
conjugation = row.get("Conjugation", "").strip()
translation = row.get("Translation", "").strip()
gender = row.get("Gender", "").strip()
number = row.get("Number", "").strip()
tense = row.get("Tense", "").strip()
person = row.get("Person", "").strip()
if not root_raw or not translation:
continue
# Create root if not seen
root_key = make_root_key(root_raw)
if root_key not in roots_seen:
roots_seen.add(root_key)
roots_col.insert({
"_key": root_key,
"letters": root_raw,
"meaning": f"root: {root_raw}",
})
# Create word document
word_key = f"conj_{i}"
word_docs.append({
"_key": word_key,
"urdu": translation,
"transliteration": "",
"meaning": f"{conjugation} ({tense}, {person}, {gender}, {number})",
"pos": "verb",
"origin": "Arabic",
"arabic": conjugation,
"gender": gender,
"number": number,
"tense": tense,
"person": person,
})
# Create derivation edge
edge_docs.append({
"_from": f"words/{word_key}",
"_to": f"roots/{root_key}",
})
# --- Batch insert words and edges ---
print(f"Inserting {len(word_docs)} words...")
words_col.import_bulk(word_docs)
print(f"Inserting {len(edge_docs)} derivation edges...")
derived_col.import_bulk(edge_docs)
print(f"\nInserted {len(roots_seen)} unique roots")
# --- Add hand-crafted supplement with synonyms ---
supplement_words = [
{"_key": "kitab", "urdu": "کتاب", "transliteration": "kitab", "meaning": "book", "pos": "noun", "origin": "Arabic"},
{"_key": "ilm", "urdu": "علم", "transliteration": "ilm", "meaning": "knowledge, science", "pos": "noun", "origin": "Arabic"},
{"_key": "madrasa", "urdu": "مدرسہ", "transliteration": "madrasa", "meaning": "school", "pos": "noun", "origin": "Arabic"},
{"_key": "muallim", "urdu": "معلم", "transliteration": "muallim", "meaning": "teacher", "pos": "noun", "origin": "Arabic"},
{"_key": "mudarris", "urdu": "مدرس", "transliteration": "mudarris", "meaning": "teacher, instructor", "pos": "noun", "origin": "Arabic"},
{"_key": "dars", "urdu": "درس", "transliteration": "dars", "meaning": "lesson", "pos": "noun", "origin": "Arabic"},
{"_key": "taleem", "urdu": "تعلیم", "transliteration": "taleem", "meaning": "education, instruction", "pos": "noun", "origin": "Arabic"},
]
for word in supplement_words:
words_col.insert(word)
print(f"Added {len(supplement_words)} supplemental words")
# Add synonym edges
synonyms = [
("words/muallim", "words/mudarris"),
("words/taleem", "words/dars"),
]
for from_id, to_id in synonyms:
synonym_col.insert({"_from": from_id, "_to": to_id})
synonym_col.insert({"_from": to_id, "_to": from_id})
print(f"Added {len(synonyms) * 2} synonym edges")
# --- Verification ---
print("\n--- Statistics ---")
print(f" Roots: {roots_col.count()}")
print(f" Words: {words_col.count()}")
print(f" Derivation edges: {derived_col.count()}")
print(f" Synonym edges: {synonym_col.count()}")
print("\n--- Sample traversal: first 5 words from a random root ---")
cursor = db.aql.execute("""
LET root = FIRST(FOR r IN roots LIMIT 1 RETURN r)
FOR word IN 1..1 INBOUND root GRAPH 'urdu_language'
OPTIONS {edgeCollections: ['derived_from']}
LIMIT 5
RETURN {urdu: word.urdu, meaning: word.meaning}
""")
for doc in cursor:
print(f" {doc['urdu']} - {doc['meaning']}")
print("\nImport complete!")
Your graph now holds a rich linguistic dataset with hundreds of roots, thousands of words, and meaningful connections between them. Next up, you will build a Flask web app that lets you search and explore these relationships through a browser interface.
Build the Flask Backend and Browser Frontend
Your graph database is fully populated with 200 trilateral roots and thousands of derived words. Now you need a way to query that data and display it in a browser.
In this step, you will build a Flask web server that exposes API endpoints for searching words and traversing graph connections. You will also create an HTML frontend that renders Urdu text in proper Nastaliq script using Noto Nastaliq Urdu from Google Fonts.
In this step, get ready to:
- Create a Flask app with search and graph traversal API endpoints.
- Build an HTML interface with RTL Nastaliq rendering for Urdu text.
- Test the full dictionary flow in your browser.
Create the Flask app with API routes
The Flask app connects to ArangoDB and exposes three routes: a home page, a search endpoint, and a word detail endpoint that performs graph traversals.
- Create a new file called app.py in your project directory.
- Add the imports, app initialization, database connection, and home route by pasting this code:
from flask import Flask, render_template, jsonify, request
from arango import ArangoClient
app = Flask(__name__)
client = ArangoClient(hosts="http://localhost:8529")
db = client.db("urdu_dict", username="root", password="rootpass")
@app.route("/")
def home():
return render_template("index.html")
What does this code do?
The first two imports bring in Flask's web framework and the ArangoDB client you installed earlier.
The app = Flask(__name__) line creates your web application instance. The database connection reuses the same credentials from your earlier scripts. The home route serves an HTML template that you will create in the next substep.
- Below the home route, add the search endpoint that queries the words collection:
@app.route("/search")
def search():
query = request.args.get("q", "").strip()
if not query:
return jsonify([])
cursor = db.aql.execute(
"""
FOR word IN words
FILTER CONTAINS(word.urdu, @query)
OR CONTAINS(LOWER(word.transliteration), LOWER(@query))
OR CONTAINS(LOWER(word.meaning), LOWER(@query))
LIMIT 20
RETURN {
key: word._key,
urdu: word.urdu,
transliteration: word.transliteration,
meaning: word.meaning,
pos: word.pos
}
""",
bind_vars={"query": query},
)
return jsonify(list(cursor))
What does the search route do?
This endpoint reads the q query parameter from the URL (e.g. /search?q=kitab). It runs an AQL query that checks three fields: the Urdu text, the transliteration, and the English meaning.
The CONTAINS function performs substring matching, so partial searches work. Results are capped at 20 to keep the response fast.
- Below the search route, add the word detail endpoint that performs graph traversals to find roots, siblings, and synonyms:
@app.route("/word/<key>")
def word_detail(key):
cursor = db.aql.execute(
"""
LET word = DOCUMENT(CONCAT('words/', @key))
LET root = FIRST(
FOR r IN 1..1 OUTBOUND word GRAPH 'urdu_language'
OPTIONS {edgeCollections: ['derived_from']}
RETURN r
)
LET siblings = (
FOR sibling IN 1..1 INBOUND root GRAPH 'urdu_language'
OPTIONS {edgeCollections: ['derived_from']}
FILTER sibling._key != @key
LIMIT 20
RETURN {
key: sibling._key,
urdu: sibling.urdu,
transliteration: sibling.transliteration,
meaning: sibling.meaning,
pos: sibling.pos
}
)
LET synonyms = (
FOR syn IN 1..1 OUTBOUND word GRAPH 'urdu_language'
OPTIONS {edgeCollections: ['synonym_of']}
RETURN {
key: syn._key,
urdu: syn.urdu,
transliteration: syn.transliteration,
meaning: syn.meaning
}
)
RETURN {
word: {
key: word._key,
urdu: word.urdu,
transliteration: word.transliteration,
meaning: word.meaning,
pos: word.pos,
origin: word.origin
},
root: root != null ? {key: root._key, letters: root.letters, meaning: root.meaning} : null,
siblings: siblings,
synonyms: synonyms
}
""",
bind_vars={"key": key},
)
result = list(cursor)
if result:
return jsonify(result[0])
return jsonify({"error": "Word not found"}), 404
if __name__ == "__main__":
app.run(debug=True, port=5000)
How does the graph traversal work?
This is where ArangoDB's graph engine shines. The AQL query performs three separate traversals in a single request:
- OUTBOUND through derived_from edges finds the word's trilateral root.
- INBOUND through derived_from from that root finds all sibling words sharing the same root.
- OUTBOUND through synonym_of edges finds the word's synonyms.
The OPTIONS {edgeCollections: ['derived_from']} clause restricts each traversal to only follow one edge type. Without this, the traversal would follow all edge types in the graph.
- Save app.py.
Seeing a syntax error when you save?
Make sure the triple-quoted AQL strings use three double-quotes (""") and not single-quotes. Also check that the bind_vars line sits outside the triple-quoted string, not inside it.
help me fix a syntax error in my Flask app.py file
✔️ Awesome, I've got everything!
Great. Make sure you have saved app.py before moving on.
ⓧ I'd like to double check the full code
from flask import Flask, render_template, jsonify, request
from arango import ArangoClient
app = Flask(__name__)
client = ArangoClient(hosts="http://localhost:8529")
db = client.db("urdu_dict", username="root", password="rootpass")
@app.route("/")
def home():
return render_template("index.html")
@app.route("/search")
def search():
query = request.args.get("q", "").strip()
if not query:
return jsonify([])
cursor = db.aql.execute(
"""
FOR word IN words
FILTER CONTAINS(word.urdu, @query)
OR CONTAINS(LOWER(word.transliteration), LOWER(@query))
OR CONTAINS(LOWER(word.meaning), LOWER(@query))
LIMIT 20
RETURN {
key: word._key,
urdu: word.urdu,
transliteration: word.transliteration,
meaning: word.meaning,
pos: word.pos
}
""",
bind_vars={"query": query},
)
return jsonify(list(cursor))
@app.route("/word/<key>")
def word_detail(key):
cursor = db.aql.execute(
"""
LET word = DOCUMENT(CONCAT('words/', @key))
LET root = FIRST(
FOR r IN 1..1 OUTBOUND word GRAPH 'urdu_language'
OPTIONS {edgeCollections: ['derived_from']}
RETURN r
)
LET siblings = (
FOR sibling IN 1..1 INBOUND root GRAPH 'urdu_language'
OPTIONS {edgeCollections: ['derived_from']}
FILTER sibling._key != @key
LIMIT 20
RETURN {
key: sibling._key,
urdu: sibling.urdu,
transliteration: sibling.transliteration,
meaning: sibling.meaning,
pos: sibling.pos
}
)
LET synonyms = (
FOR syn IN 1..1 OUTBOUND word GRAPH 'urdu_language'
OPTIONS {edgeCollections: ['synonym_of']}
RETURN {
key: syn._key,
urdu: syn.urdu,
transliteration: syn.transliteration,
meaning: syn.meaning
}
)
RETURN {
word: {
key: word._key,
urdu: word.urdu,
transliteration: word.transliteration,
meaning: word.meaning,
pos: word.pos,
origin: word.origin
},
root: root != null ? {key: root._key, letters: root.letters, meaning: root.meaning} : null,
siblings: siblings,
synonyms: synonyms
}
""",
bind_vars={"key": key},
)
result = list(cursor)
if result:
return jsonify(result[0])
return jsonify({"error": "Word not found"}), 404
if __name__ == "__main__":
app.run(debug=True, port=5000)
Build the HTML frontend with Nastaliq rendering
Flask looks for HTML templates in a templates/ directory by default. You need to create that directory and add the dictionary interface inside it.
- Create the templates directory by running this command:
mkdir templates
- Confirm the directory exists by running:
ls
You should see templates listed alongside your other project files.
- Create a new file called index.html inside the templates/ folder.
- Add the document structure, font loading, and styles by pasting this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Urdu Root Dictionary</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Nastaliq+Urdu:wght@400;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #f5f5f5; padding: 2rem; max-width: 900px; margin: 0 auto; }
h1 { text-align: center; margin-bottom: 1.5rem; color: #2c3e50; }
.search-box { display: flex; gap: 0.5rem; margin-bottom: 2rem; }
.search-box input { flex: 1; padding: 0.75rem 1rem; font-size: 1.1rem; border: 2px solid #ddd; border-radius: 8px; }
.search-box input:focus { outline: none; border-color: #3498db; }
.search-box button { padding: 0.75rem 1.5rem; background: #3498db; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 1rem; }
.search-box button:hover { background: #2980b9; }
.urdu { font-family: 'Noto Nastaliq Urdu', serif; direction: rtl; text-align: right; }
.urdu-large { font-size: 2.5rem; line-height: 2; }
.urdu-medium { font-size: 1.5rem; line-height: 1.8; }
.results { list-style: none; }
.results li { background: white; padding: 1rem; margin-bottom: 0.5rem; border-radius: 8px; cursor: pointer; border: 1px solid #eee; }
.results li:hover { border-color: #3498db; background: #f8fbff; }
.detail-panel { background: white; border-radius: 12px; padding: 2rem; border: 1px solid #eee; display: none; }
.detail-panel.active { display: block; }
.detail-header { text-align: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 2px solid #ecf0f1; }
.detail-meta { color: #7f8c8d; margin-top: 0.5rem; }
.section { margin-top: 1.5rem; }
.section h3 { color: #2c3e50; margin-bottom: 0.75rem; font-size: 1rem; text-transform: uppercase; letter-spacing: 0.05em; }
.root-badge { display: inline-block; background: #e8f4f8; padding: 0.5rem 1rem; border-radius: 20px; margin-bottom: 0.5rem; }
.word-chip { display: inline-block; background: #f0f0f0; padding: 0.4rem 0.8rem; border-radius: 6px; margin: 0.25rem; cursor: pointer; border: 1px solid #ddd; }
.word-chip:hover { background: #e0e0e0; border-color: #3498db; }
.word-chip .chip-urdu { font-family: 'Noto Nastaliq Urdu', serif; direction: rtl; }
#message { text-align: center; color: #7f8c8d; padding: 2rem; }
</style>
</head>
<body>
<h1>Urdu Root Dictionary</h1>
<div class="search-box">
<input type="text" id="searchInput" placeholder="Search Urdu word, transliteration, or meaning...">
<button onclick="doSearch()">Search</button>
</div>
<div id="message">Type a word to explore its roots and connections.</div>
<ul class="results" id="results"></ul>
<div class="detail-panel" id="detail"></div>
How does the RTL rendering work?
The .urdu CSS class sets direction: rtl and uses font-family: 'Noto Nastaliq Urdu', serif. This ensures Urdu text flows right-to-left in the calligraphic Nastaliq style that Urdu readers expect.
The HTML body also uses lang="ur" and dir="rtl" attributes on Urdu content elements for proper browser rendering and accessibility.
- Below the HTML structure you just added, paste the JavaScript that powers search and word navigation:
<script>
const searchInput = document.getElementById('searchInput');
const resultsEl = document.getElementById('results');
const detailEl = document.getElementById('detail');
const messageEl = document.getElementById('message');
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') doSearch();
});
async function doSearch() {
const query = searchInput.value.trim();
if (!query) return;
messageEl.style.display = 'none';
detailEl.classList.remove('active');
const res = await fetch('/search?q=' + encodeURIComponent(query));
const data = await res.json();
if (data.length === 0) {
resultsEl.innerHTML = '<li>No results found.</li>';
return;
}
resultsEl.innerHTML = data.map(w =>
`<li onclick="showWord('${w.key}')">
<span class="urdu urdu-medium" lang="ur" dir="rtl">${w.urdu}</span>
<span style="color:#7f8c8d; margin-left:1rem;">${w.transliteration || ''} - ${w.meaning} (${w.pos})</span>
</li>`
).join('');
}
async function showWord(key) {
const res = await fetch('/word/' + key);
const data = await res.json();
if (data.error) return;
resultsEl.innerHTML = '';
let html = `
<div class="detail-header">
<div class="urdu urdu-large" lang="ur" dir="rtl">${data.word.urdu}</div>
<div class="detail-meta">${data.word.transliteration || ''} - ${data.word.meaning} (${data.word.pos}, ${data.word.origin})</div>
</div>`;
if (data.root) {
html += `
<div class="section">
<h3>Trilateral Root</h3>
<div class="root-badge">
<span class="urdu urdu-medium" lang="ur" dir="rtl">${data.root.letters}</span>
<span style="margin-left:0.5rem; color:#555;">${data.root.meaning}</span>
</div>
</div>`;
}
if (data.siblings && data.siblings.length > 0) {
html += `<div class="section"><h3>Words from the same root</h3><div>`;
data.siblings.forEach(s => {
html += `<span class="word-chip" onclick="showWord('${s.key}')">
<span class="chip-urdu" lang="ur" dir="rtl">${s.urdu}</span> ${s.transliteration || ''} - ${s.meaning}
</span>`;
});
html += `</div></div>`;
}
if (data.synonyms && data.synonyms.length > 0) {
html += `<div class="section"><h3>Synonyms</h3><div>`;
data.synonyms.forEach(s => {
html += `<span class="word-chip" onclick="showWord('${s.key}')">
<span class="chip-urdu" lang="ur" dir="rtl">${s.urdu}</span> ${s.transliteration || ''} - ${s.meaning}
</span>`;
});
html += `</div></div>`;
}
detailEl.innerHTML = html;
detailEl.classList.add('active');
}
</script>
</body>
</html>
What does the JavaScript do?
The doSearch() function sends the search term to your /search endpoint and renders each result as a clickable list item. The showWord() function calls the /word/<key> endpoint and displays the full word detail panel with root, siblings, and synonyms.
Clicking any word chip (a sibling or synonym) calls showWord() again with that word's key, letting you navigate the graph by clicking through related words.
- Save templates/index.html.
File not found when Flask tries to load the template?
Make sure the file is at templates/index.html (not template/index.html without the 's'). Flask requires the folder to be named exactly templates.
help me fix a template not found error in my Flask app
✔️ Awesome, I've got everything!
Great. Make sure you have saved templates/index.html before moving on.
ⓧ I'd like to double check the full code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Urdu Root Dictionary</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Nastaliq+Urdu:wght@400;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #f5f5f5; padding: 2rem; max-width: 900px; margin: 0 auto; }
h1 { text-align: center; margin-bottom: 1.5rem; color: #2c3e50; }
.search-box { display: flex; gap: 0.5rem; margin-bottom: 2rem; }
.search-box input { flex: 1; padding: 0.75rem 1rem; font-size: 1.1rem; border: 2px solid #ddd; border-radius: 8px; }
.search-box input:focus { outline: none; border-color: #3498db; }
.search-box button { padding: 0.75rem 1.5rem; background: #3498db; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 1rem; }
.search-box button:hover { background: #2980b9; }
.urdu { font-family: 'Noto Nastaliq Urdu', serif; direction: rtl; text-align: right; }
.urdu-large { font-size: 2.5rem; line-height: 2; }
.urdu-medium { font-size: 1.5rem; line-height: 1.8; }
.results { list-style: none; }
.results li { background: white; padding: 1rem; margin-bottom: 0.5rem; border-radius: 8px; cursor: pointer; border: 1px solid #eee; }
.results li:hover { border-color: #3498db; background: #f8fbff; }
.detail-panel { background: white; border-radius: 12px; padding: 2rem; border: 1px solid #eee; display: none; }
.detail-panel.active { display: block; }
.detail-header { text-align: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 2px solid #ecf0f1; }
.detail-meta { color: #7f8c8d; margin-top: 0.5rem; }
.section { margin-top: 1.5rem; }
.section h3 { color: #2c3e50; margin-bottom: 0.75rem; font-size: 1rem; text-transform: uppercase; letter-spacing: 0.05em; }
.root-badge { display: inline-block; background: #e8f4f8; padding: 0.5rem 1rem; border-radius: 20px; margin-bottom: 0.5rem; }
.word-chip { display: inline-block; background: #f0f0f0; padding: 0.4rem 0.8rem; border-radius: 6px; margin: 0.25rem; cursor: pointer; border: 1px solid #ddd; }
.word-chip:hover { background: #e0e0e0; border-color: #3498db; }
.word-chip .chip-urdu { font-family: 'Noto Nastaliq Urdu', serif; direction: rtl; }
#message { text-align: center; color: #7f8c8d; padding: 2rem; }
</style>
</head>
<body>
<h1>Urdu Root Dictionary</h1>
<div class="search-box">
<input type="text" id="searchInput" placeholder="Search Urdu word, transliteration, or meaning...">
<button onclick="doSearch()">Search</button>
</div>
<div id="message">Type a word to explore its roots and connections.</div>
<ul class="results" id="results"></ul>
<div class="detail-panel" id="detail"></div>
<script>
const searchInput = document.getElementById('searchInput');
const resultsEl = document.getElementById('results');
const detailEl = document.getElementById('detail');
const messageEl = document.getElementById('message');
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') doSearch();
});
async function doSearch() {
const query = searchInput.value.trim();
if (!query) return;
messageEl.style.display = 'none';
detailEl.classList.remove('active');
const res = await fetch('/search?q=' + encodeURIComponent(query));
const data = await res.json();
if (data.length === 0) {
resultsEl.innerHTML = '<li>No results found.</li>';
return;
}
resultsEl.innerHTML = data.map(w =>
`<li onclick="showWord('${w.key}')">
<span class="urdu urdu-medium" lang="ur" dir="rtl">${w.urdu}</span>
<span style="color:#7f8c8d; margin-left:1rem;">${w.transliteration || ''} - ${w.meaning} (${w.pos})</span>
</li>`
).join('');
}
async function showWord(key) {
const res = await fetch('/word/' + key);
const data = await res.json();
if (data.error) return;
resultsEl.innerHTML = '';
let html = `
<div class="detail-header">
<div class="urdu urdu-large" lang="ur" dir="rtl">${data.word.urdu}</div>
<div class="detail-meta">${data.word.transliteration || ''} - ${data.word.meaning} (${data.word.pos}, ${data.word.origin})</div>
</div>`;
if (data.root) {
html += `
<div class="section">
<h3>Trilateral Root</h3>
<div class="root-badge">
<span class="urdu urdu-medium" lang="ur" dir="rtl">${data.root.letters}</span>
<span style="margin-left:0.5rem; color:#555;">${data.root.meaning}</span>
</div>
</div>`;
}
if (data.siblings && data.siblings.length > 0) {
html += `<div class="section"><h3>Words from the same root</h3><div>`;
data.siblings.forEach(s => {
html += `<span class="word-chip" onclick="showWord('${s.key}')">
<span class="chip-urdu" lang="ur" dir="rtl">${s.urdu}</span> ${s.transliteration || ''} - ${s.meaning}
</span>`;
});
html += `</div></div>`;
}
if (data.synonyms && data.synonyms.length > 0) {
html += `<div class="section"><h3>Synonyms</h3><div>`;
data.synonyms.forEach(s => {
html += `<span class="word-chip" onclick="showWord('${s.key}')">
<span class="chip-urdu" lang="ur" dir="rtl">${s.urdu}</span> ${s.transliteration || ''} - ${s.meaning}
</span>`;
});
html += `</div></div>`;
}
detailEl.innerHTML = html;
detailEl.classList.add('active');
}
</script>
</body>
</html>
Run and test the dictionary
Both files are ready. Time to start the Flask server and see your dictionary in action.
- Start the Flask development server by running:
python app.py
You should see output indicating the server is running on http://127.0.0.1:5000 with debug mode active.
- Open your browser and go to http://localhost:5000.
- Type kitab into the search bar and press **Enter** or click **Search**.
- Click on a result to see its word detail panel with the trilateral root and related words.
- Click a word chip (a sibling or synonym) to navigate to that word's detail view.
What should I see?
Searching for kitab returns the supplemental word you added earlier. Clicking it shows its meaning ("book") in the detail panel. Try searching teacher to find muallim and mudarris. Clicking either one reveals they are synonyms of each other.
Urdu text appears in the flowing Nastaliq script, reading right-to-left. If you see blocky Naskh-style Arabic letters instead, the Google Fonts link may not have loaded. Refresh the page.
Page not loading or showing an error?
- Make sure the ArangoDB container is still running. Check with docker ps and look for urdu-dict.
- If you see a connection error in the terminal, verify port 8529 is not blocked and the container is running.
- If the browser shows a blank page, open your browser's developer console (F12) and check for JavaScript errors.
help me debug my Flask Urdu dictionary app that is not loading
Secret mission
Add Antonyms and Word of the Day
Your dictionary shows how words relate through shared roots and synonyms. But what about opposites? And what if you could discover a random word each day and see its full graph neighborhood? In this mission, you'll add antonym edges and a randomized Word of the Day feature to your Urdu dictionary.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally using Docker, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- A Docker container named urdu-dict running ArangoDB Enterprise 3.12.6 on port 8529 (uses approximately 100-200MB memory).
- A project directory containing Python scripts, templates, and the cloned Arabic-Urdu-Conjugation-Dataset.
- A Python virtual environment with python-arango and flask installed.
Keep everything running
No action needed. Choose this if you're still actively building or want to use the dictionary as a personal vocabulary tool.
- The Docker container costs nothing to keep running locally. Your data persists inside the container volume.
- You can access your dictionary anytime by running the Flask app and opening http://localhost:5000 in your browser.
- The ArangoDB web UI remains available at http://localhost:8529 for running AQL queries directly.
Pause - I'll come back to this later
Shut down the running processes to free up memory. All your data and files stay intact.
- Stop the Flask app by pressing Ctrl+C in the terminal where it is running.
- Stop the ArangoDB container by running:
- All data persists inside the stopped container. Restart later with:
docker stop urdu-dict
When you're ready to come back, restart the container with:
docker start urdu-dict
Delete - I don't want to use this again
Remove all project resources and start fresh. All dictionary data will be lost.
- Stop the Flask app by pressing Ctrl+C in the terminal where it is running.
- Remove the ArangoDB Docker container by running:
docker rm -f urdu-dict
- Deactivate your Python virtual environment by running:
deactivate
- Delete the project directory. On macOS/Linux:
cd ~/Desktop
rm -rf urdu-root-dictionary
On Windows:
cd ~\Desktop
rmdir /s /q urdu-root-dictionary
Nice Work!
Nice Work!
You just built a fully functional Urdu root dictionary powered by ArangoDB's graph engine. From designing a graph schema to rendering Nastaliq script in the browser, you proved that linguistic data belongs in a graph database.
You learned how to:
- Design a graph data model with vertex collections (words, roots) and edge collections (derived_from, synonym_of) that naturally represents linguistic relationships as vertices and edges.
- Import 200 trilateral roots and 14,400+ word entries from the Arabic-Urdu Conjugation Dataset using a bulk CSV import script with batch inserts.
- Write AQL graph traversal queries that discover word families through shared trilateral roots, and serve them through a Flask API with a browser interface rendering Urdu in proper RTL/Nastaliq script.
- Secret Mission: Added antonym relationships and a randomized "Word of the Day" feature using AQL random selection.
Ready to quiz yourself?