Imported from christiansteinert/tibetan-dictionary (
AGENTS.md). Install upstream withnpx skills add christiansteinert/tibetan-dictionary. Copyright stays with the author.
Tibetan-English-Sanskrit Dictionary - AI Agent Instructions
Project Overview
This is a hybrid Tibetan-English-Sanskrit dictionary application with two deployment modes:
- Web app: Vite/React/TypeScript frontend + PHP backend (
api.php) for SQLite queries, served by nginx - Android app: Cordova-packaged version of the same frontend, with a custom Java SQLite plugin
Both share the same frontend codebase (webapp/src/) and use an SQLite database generated from CSV source files.
Architecture & Data Flow
Key directories
backend/— PHP backend for the web application (api.php,snippet.php), generated SQLite DBs (TibetanDictionary.db,TibetanDictionary_compressed.db, plus private variants), static assets (audio/,data/), OpenAPI spec (openapi.yaml), and tests (tests/backend-test.py)webapp/— Vite + React + TypeScript frontend (uses Redux for state). Code is used for both web-based deployment and Cordova Android app. Build output goes towebapp/dist/_input/dictionaries/— CSV source files (public/,public_en/,public_skt/, and optionallyprivate/,private_en/,private_skt/) used to generate the databasebuildscripts/— Shell scripts, Python database builder, and Docker configurations (docker/build-db/,docker/build-android/,docker/backend-dev/,docker/deploy/)_build/mobile/— Apache Cordova project for the Android build_assets/— App icons, splash screens, and Android resource directories (res.normal/,res.full/)
Build pipeline (Docker Compose)
All build and runtime services are defined in docker-compose.yml:
| Service | Image | What it does | Output |
|---|---|---|---|
build-db |
python:3.13-slim |
Runs buildscripts/buildDictionaries.sh which calls _buildDict.py |
backend/TibetanDictionary.db (uncompressed, with FTS5) + TibetanDictionary_compressed.db (zlib-compressed blobs, no FTS5), plus private variants |
build-webapp |
node:22-alpine |
npm ci && npm run build && npm run test in webapp/ |
webapp/dist/ |
build-android |
custom (see buildscripts/docker/build-android/Dockerfile) |
Cordova build inside container (uses compressed DB) | TibetanDictionary-PUBLIC.apk (and FULL variant if private dicts exist) |
backend-dev |
php:8.3-fpm-alpine |
nginx + PHP-FPM serving backend/ for development |
runtime on port 8080 |
backend-test |
python:3.12-slim |
Runs backend/tests/backend-test.py against backend-dev |
test results |
frontend-test |
node:22-alpine |
Runs Vitest test suite in webapp/ |
test results |
frontend-dev |
node:22-alpine |
Vite dev server with hot reload, proxying API/audio/data to backend-dev |
runtime on port 5173 |
Common workflows
# Full build + run:
docker compose run --rm build-db
docker compose up -d backend-dev frontend-dev # http://localhost:5173 (frontend), http://localhost:8080 (backend)
# Rebuild DB only (after CSV changes):
docker compose run --rm build-db && docker compose restart backend-dev
# Rebuild frontend only (only needed if hot reload fails):
docker compose restart frontend-dev
# Build a react frontend:
docker compose run --rm build-webapp
# Run backend tests (requires backend-dev running):
docker compose up backend-test
# Run frontend tests:
docker compose up frontend-test
# Android APK (requires keystore at _build/my-release-key.keystore):
docker compose run --rm build-android
Database variants
Two database formats are produced by the build:
- Uncompressed (
TibetanDictionary.db, ~283 MB):definitionstored as TEXT, includes FTS5 virtual table (DICT_FTS) with Porter stemming. Used by the PHP web backend. - Compressed (
TibetanDictionary_compressed.db, ~89 MB):definitionstored as BLOB (zlib-compressed with custom dictionary),WITHOUT ROWID, no FTS5. Used by the Android app to keep APK size small. - Private variants (
TibetanDictionary_private.db,TibetanDictionary_private_compressed.db) exist when_input/dictionaries/private/is present.
Dual data access strategy
The frontend uses a polymorphic API interface defined in webapp/src/services/DictionaryApi.ts:
PhpDictionaryApi: AJAX calls toapi.php(web deployment, uses uncompressed DB with FTS5)CordovaDictionaryApi: Direct SQLite viawindow.sqlitePlugin(Android, uses compressed DB without FTS5)
The correct implementation is selected at runtime based on whether window.cordova is present.
Frontend structure (webapp/src/)
Standard Vite + React + TypeScript app with Redux state management. Key folders:
components/— UI componentsservices/— Data access layer (DictionaryApi.ts,PhpDictionaryApi.ts,CordovaDictionaryApi.ts)config/— Hand-maintained dictionary metadata:dictlist.ts(known dictionaries),abbreviations.ts(abbreviation expansion rules),globalSettings.ts(auto-generated by build —publicOnlyflag fromVITE_PUBLIC_ONLYenv var)routes/— App routing (AppRoutes.tsx), shared text handling (handleSharedText.ts), legacy redirect (legacyRedirect.ts)store/— Redux state management (store.ts,searchSlice.ts,settingsSlice.ts)hooks/— React hooks for dictionary lookup, fulltext search, etc.utils/— Shared logic:ewts-js/(Wylie transliteration library),fts/(FTS5 query builder),wylieConverter.ts,tokenizer.ts(Tibetan text tokenizer),harvardKyotoConverter.ts,definitionFormatter.ts,escape.ts,tooltip.tsstyles/— Shared CSS modules
Dictionary data format
CSV files in _input/dictionaries/:
- Format:
WylieTerm|DefinitionText(pipe-separated) - Multiple entries per term are allowed
- Input subdirectories:
public/(Tibetan),public_en/(English),public_skt/(Sanskrit), and optionallyprivate/,private_en/,private_skt/ - The Wylie-to-Unicode conversion is handled client-side by the
ewts-js/JavaScript library inwebapp/src/utils/ewts-js/(originally based on Perl Lingua::BO::Wylie)
Compression
Dictionary definitions are compressed with zlib using a custom 32KB deflate dictionary:
- Source:
buildscripts/deflate_dict.txt(read by_buildDict.pyduring DB build) - Android decompression:
DictDecompress.javain the custom Cordova plugin contains the matchingZLIB_DICTconstant - Generation:
buildscripts/generate_deflate_dict.pycan regenerate the JavaZLIB_DICTfromdeflate_dict.txt - CRITICAL: The deflate dictionary must stay in sync between
buildscripts/deflate_dict.txt(Python builder) andDictDecompress.java(Android plugin). Mismatched dictionaries produce garbage output.
Public vs private versions
Some dictionaries are proprietary and not publicly distributed. The build detects whether _input/dictionaries/private/ exists and builds accordingly:
webapp/src/config/globalSettings.tsis auto-generated with apublicOnlyflag (fromVITE_PUBLIC_ONLYenv var)- Android uses different app IDs:
de.christian_steinert.tibetandict(public) vsde.christian_steinert.tibetandict.full(private) - The PHP backend auto-detects: if
TibetanDictionary_private.dbexists, it uses that instead ofTibetanDictionary.db
When modifying
- Add a dictionary: Place CSV in
_input/dictionaries/public/(orpublic_en/,public_skt/), add entry towebapp/src/config/dictlist.ts, rebuild withbuild-db - Change UI: Edit files under
webapp/src/ - Fix data access: Update both
PhpDictionaryApi.tsandCordovaDictionaryApi.ts - Change build logic: Edit
buildscripts/_buildDict.pyor the shell scripts, then re-runbuild-db - Update deflate dictionary: Modify
buildscripts/deflate_dict.txt, then regenerate Java constant viabuildscripts/generate_deflate_dict.py, then rebuild DB and Android app - nginx / PHP config:
buildscripts/docker/deploy/nginx.conf
Testing
- Frontend tests: Vitest suite in
webapp/. Run vianpm run testinwebapp/, ordocker compose up frontend-test. - Backend tests: Python script at
backend/tests/backend-test.py. Run viadocker compose up backend-test(requiresbackend-devrunning).
Common pitfalls
webapp/dist/may be owned by root after a Docker build — runsudo chown -R $USER webapp/dist/if localnpm run buildfails with permission errors- The deflate dictionary (
buildscripts/deflate_dict.txt) must stay in sync withDictDecompress.java(ZLIB_DICTconstant) in the custom Cordova SQLite plugin. Mismatched dictionaries produce decompression errors. - The Cordova SQLite plugin is custom (
_build/mobile/plugins-custom/cordova-sqlite-storage-custom/) — it extracts the bundled DB from the APK on first access and is not the standard community plugin. The basecordova-sqlite-storageplugin does NOT handle BLOB columns correctly. - The Android app uses the compressed database (BLOB definitions, no FTS5), while the web backend uses the uncompressed database (TEXT definitions, with FTS5). FTS5 fulltext search is NOT available on Android.
- Build scripts assume Linux/Unix — not Windows-compatible. Use WSL or Docker for Windows if developing on Android.
- The
frontend-devVite dev server usesBACKEND_URL(set in docker-compose.yml) to proxy API, audio, and data requests tobackend-dev. - The Android build merges the base
cordova-sqlite-storageplugin with custom code at build time via_build/buildAndroid.sh— do not modify the base plugin directly.