KBBI Chrome Extension
KBBI Chrome Extension
Chrome extension (Manifest V3) to look up Indonesian word definitions from the official KBBI VI Daring — Kementerian Pendidikan Dasar dan Menengah.
Features
- Popup search — click the toolbar icon, type or paste a word, press Enter
- Context menu — select any word on a page → right-click → “Cari ‘[word]’ di KBBI” → opens a full results tab
- Displays numbered definitions, grammar labels (v, n, ki, pb, Ark, Tas, …), and usage examples
- “Lihat di KBBI VI Daring” source link on every result
- Color scheme matches the official KBBI VI site (navy #292261 → red gradient, gold K)
Install (development)
chrome://extensions→ enable Developer mode- Load unpacked → select this folder
No build step. No dependencies.
File structure
kbbi-chrome-extension/
│
├── manifest.json # MV3 manifest — permissions, icons, entry points
├── background.js # Service worker — registers & handles the context menu
│
├── kbbi.js # Fetch + parse logic (shared by popup and results page)
├── render.js # DOM rendering logic (shared by popup and results page)
├── shared.css # All styles (shared by popup and results page)
│
├── popup.html # Toolbar popup — 380px wide search UI
├── popup.js # Popup controller
│
├── results.html # Full-page results tab (opened from context menu)
├── results.js # Results page controller
│
├── icons/
│ ├── icon16.png # Extracted from kbbi.kemendikdasmen.go.id favicon
│ ├── icon48.png # (upscaled from 16px)
│ └── icon128.png # (upscaled from 48px)
│
└── generate-icons.js # Node script to regenerate icons (fallback, run once)
Architecture
Data flow — popup
popup.html
└── popup.js → kbbi.js (searchKBBI) → fetch https://kbbi.kemendikdasmen.go.id/entri/{word}
└── parseKBBI()
├── detect "Entri tidak ditemukan" → found: false
└── parse h2 + ol/ul → entries[]
↓
render.js (renderResults / renderLoading / renderError)
↓
popup.html #results
Data flow — context menu
User selects text → right-click → "Cari … di KBBI"
└── background.js (contextMenus.onClicked)
└── chrome.tabs.create({ url: results.html?word=… })
└── results.js → kbbi.js → render.js (same pipeline as popup)
Parser (kbbi.js)
The site renders definitions as plain server-side HTML. There is no JSON data container (unlike the old kbbi.web.id).
URL: GET https://kbbi.kemendikdasmen.go.id/entri/{word}
Page structure:
<h2 style="margin-bottom:3px">ma.kan<sup>1</sup></h2>
<ol>
<li>
<font color="red"><i>
<span title="Verba: kata kerja">v</span>
<span title="kiasan"><font color="green">ki</font></span>
</i></font>
definition text:
<font color="grey"><i>usage example</i></font>
<font color="brown"><i>(clarification gloss)</i></font>
</li>
</ol>
<ul class="adjusted-par"> <!-- derived/secondary entries -->
<li>...</li>
</ul>
Not found: <h4 style="color:red">Entri tidak ditemukan.</h4>
Parsing strategy:
- Find all
<h2>elements with a<sup>child ormargin-bottomstyle → one entry per h2 - For each h2, walk forward siblings collecting
<ol>and<ul>elements until the next entry h2 - For each
<li>:- Grammar labels:
<span>insidefont[color="red"] i - Usage-type labels (ki, pb, etc.):
font[color="green"] - Examples:
font[color="grey"] i - Brown gloss:
font[color="brown"] i— appended to examples in parentheses - Definition text: remaining text after removing all
<font>elements
- Grammar labels:
Rendering (render.js)
renderResults(data, container, word) is the single entry point. data shape:
{
found: boolean,
entries: [
{
word: string, // e.g. "ma.kan¹"
definitions: [
{
type: 'definition' | 'compound',
text: string,
labels: string[], // e.g. ["v", "ki"]
examples: string[] // usage examples
}
]
}
],
suggestions: string[] // always [] for this source (no suggestions provided)
}
onSuggestionClick(word) must be defined in the page’s own JS (popup.js / results.js).
Permissions
| Permission | Why |
|---|---|
contextMenus |
Register right-click “Cari di KBBI” item |
tabs |
Open results.html in a new tab |
host_permissions: https://kbbi.kemendikdasmen.go.id/* |
Bypass CORS to fetch definitions |
Icons
Icons were extracted from the official KBBI VI Daring favicon (/kbbi-daring-3.ico) using Pillow:
python3 -c "
from PIL import Image
ico = Image.open('kbbi-daring-3.ico')
ico.size = (16,16); ico.convert('RGBA').save('icons/icon16.png')
ico.size = (48,48); ico.convert('RGBA').save('icons/icon48.png')
img128 = ico.convert('RGBA').resize((128,128), Image.LANCZOS)
img128.save('icons/icon128.png')
"
The icon is a gold “K” on dark navy (#292261) background — matching the KBBI VI Daring brand.
Known limitations & future work
- Multi-word phrases —
/entri/{word}only handles single words. The site has a/Cari/Hasil?frasa={query}endpoint for phrases; could be added as a fallback. - Login-only content — etymology and some extended info only appear for registered users; the extension shows the publicly available subset.
- No suggestions — when a word is not found, kbbi.kemendikdasmen.go.id does not return similar-word suggestions (unlike the old kbbi.web.id).
- Offline/cache — no caching; every lookup is a live fetch.
Potential improvements
- Phrase search via
/Cari/Hasil?frasa=when direct/entri/returns not found - Search history (
chrome.storage.local) - Keyboard shortcut (
commandsAPI) to open popup with selected text pre-filled - Side panel mode (Chrome 114+
sidePanelAPI) instead of new tab for context menu - Dark mode (
prefers-color-scheme)