Dev notes: how Moradok Lanna is built
Moradok Lanna (มรดกล้านนา) is a non-profit website for the Lanna language and culture, the heritage of northern Thailand. Its heart is a Lanna to Thai dictionary of about 17,000 entries, compiled and maintained by its founder. Around that dictionary the site has grown a set of tools: a word of the day, quizzes and games, a Lanna calendar, a typing tutor, a browser-based Lanna text editor, shared writing boards, a Markdown blog, and an AI teacher called "พ่อครู" (Pho Khru, "the master teacher") who answers only from the dictionary.
These notes are the developer's write-up: the stack, the shape of the system, the principles behind it, three case studies, and a changelog of every release. They are written for other developers, and for anyone curious about building software for a small script that most tools ignore.
The stack in one page
- PHP 8.2, no framework. Small single-purpose pages and a shared "portal" library for layout, navigation, accounts and the AI plumbing. A framework would have been more weight than this site needs, and the host is shared hosting where every extra layer costs speed.
- MariaDB for the dictionary, accounts, boards and logs. The dictionary table is treated as read-only by every feature that is not the dictionary editor.
- A flat-file Markdown blog. Articles are plain Markdown files with front matter, rendered by a tiny renderer. There is no admin login to attack, nothing to patch weekly, and every article is a file that can be versioned and backed up.
- Vanilla JavaScript. No build step and no front-end framework. Each interactive tool is one or two plain script files, tested with a headless browser.
- Self-hosted fonts, including LN TILOK, the Lanna (Tai Tham) font created at the Center for the Promotion of Arts and Culture, Chiang Mai University (CMU). It is credited on the About page and in the footer of every page. Thai UI text uses Noto Sans Thai, and body text in the editors uses Sarabun.
- Shared sign-in. Accounts use a single sign-in service shared with another of the developer's sites; the server verifies the sign-in itself and keeps its own session. Signing in is always optional.
- A hosted LLM behind an Ollama-compatible API powers "ถามพ่อครู AI" (Ask Pho Khru AI). All model calls happen on the server; the browser never talks to the model provider.
- Images live in object storage behind a CDN; everything else runs on one shared-hosting account.
Architecture
+------------------------------------------------------+
| Browser: vanilla JS, self-hosted fonts |
+--------------------------+---------------------------+
|
+--------------------------v---------------------------+
| Pages (server-rendered PHP) |
| Portal tools | Dictionary | Calendar | Jaan + boards|
+----------+--------------+--------------+-------------+
| | |
+----------v---+ +-------v------+ +----v-------------+
| Shared | | AI layer | | Accounts |
| portal | | retrieval, | | shared sign-in, |
| library | | cache, limits| | credit tiers |
+----------+---+ +-------+------+ +----+-------------+
| | |
+----------v--------------v--------------v-------------+
| Database: dictionary (read-only for features), |
| accounts, boards + versions, logs |
+------------------------------------------------------+
Side by side: Markdown blog (flat files)
Object storage + CDN (images)
Hosted LLM, called from the server only
Everything is server-rendered first. JavaScript enhances a page that already works, so search engines, screen readers and slow phones all get real content.
Principles
- Dictionary first. The dictionary is the authority for every Lanna spelling, reading and meaning. Features look words up; they do not guess. When the dictionary and a model disagree, the dictionary wins. If a word is missing, the interface says so instead of inventing it.
- Privacy. Visitors are not tracked by name. Network addresses are never stored raw. Anonymous features use a random visitor id, and accounts have a "delete my data" page. Analytics is on, and the privacy policy says exactly what is collected.
- Everything works without signing in. An account adds saved conversations, saved words and more AI credit. It never gates the dictionary, the tools or the boards.
- Accessibility. Real headings and landmarks, a skip link, visible focus, text that scales, and dark mode that follows the device. The tools are keyboard friendly.
- Performance on shared hosting. Cache what can be cached (sitemaps, AI answers, daily content), build daily content ahead of time, keep pages small, and avoid anything that needs a long-running process.
- SEO-safe changes. Redesigns keep URLs, titles, canonicals and the text of indexed pages unchanged, and are diffed before and after. A navigation change that alters an indexed dictionary page is a bug.
- Boring where it counts. Plain files, plain SQL and plain HTTP. Clever is reserved for the hard parts: the editor and live collaboration.
Case studies
- Jaan Editor (จ๋าร): a Lanna text editor in the browser, and the keystroke bugs that taught the most.
- Live editing by paragraph: collaborative editing on shared hosting, without websockets.
- A dictionary-grounded AI teacher: an assistant that cites the dictionary and never writes a Lanna spelling on its own.
- Changelog: every release, newest first.
About the project
Moradok Lanna is free to use. Optional member packages are planned only to help cover AI running costs. The Lanna script is the Tai Tham script as used in northern Thailand (called "Tilok" here after the font), and many of the engineering problems in these notes come from handling a script that sits outside most software's assumptions: combining marks, stacked consonants and no standard keyboard.
Credits
Designed and built by Jarus Soontornsing (Toni Maxx) (จรุส สุนทรสิงห์) · บริษัท ลอจิกเบเกอร์ จำกัด (LogicBaker Company Limited)
Lanna knowledge and dictionary: อ.จรีย์ สุนทรสิงห์, founder of มรดกล้านนา
LN TILOK font: Center for the Promotion of Arts and Culture, Chiang Mai University.