wiktionary · git:20260911.6c63f05 · 2026-09-11 · sha256 96493fb9d0ce0673
wiktionary git:20260911.6c63f05A
Immutable. This exact content never changes and is served at /api/v1/blob/96493fb9d0ce0673.
---
name: wiktionary
description: "Work with Wiktionary entries across languages - entry anatomy, definitions, translation tables, pronunciations, and Wikidata lexemes."
license: MIT
compatibility: opencode
last_verified: 2026-09-11
depends_on: [wikimedia-api-access, wikimedia-commons, pywikibot]
skill_discovery_hints:
- keywords: ["Wiktionary", "dictionary entry", "definition", "translation table", "etymology"]
- keywords: ["lexeme", "IPA", "audio pronunciation", "part of speech", "language section"]
---
> ⚠️ **Prerequisites:** This skill assumes familiarity with the MediaWiki Action API
> (see **wikimedia-api-access**), Commons file handling (**wikimedia-commons**), and
> basic Pywikibot patterns (**pywikibot**). Wikidata lexemes are covered in
> **wikidata**; language detection and fallback chains are covered in
> **wikimedia-i18n-l10n-for-tools**.
Wiktionary is the free dictionary — one of the largest Wikimedia content
projects, with **over 170 language editions** (en.wiktionary alone is one of the
biggest). It runs on the same MediaWiki platform as Wikipedia but has a
**fundamentally different content structure**: a single page holds entries for
the *same word in many languages*, and each language section contains
part-of-speech sections with numbered definitions, usage examples, translation
tables, and pronunciation data. Wikitext is the primary format — the rendered
HTML flattens the language/part-of-speech hierarchy, so almost all tooling works
against raw wikitext.
**When to use:**
- You need a definition, etymology, or example sentence for a word in a
specific language.
- You need a word's translations into other languages (the `{{trans-top}}`
tables).
- You need IPA or audio pronunciation files for a word.
- You need to match a dictionary entry to a Wikidata **lexeme** (L-entity) for
structured data, or to import lexeme data back into an entry.
## API Basics
- **Base URL:** `{lang}.wiktionary.org/w/api.php` (e.g.
`en.wiktionary.org/w/api.php`, `fr.wiktionary.org/w/api.php`). Every edition
uses the standard MediaWiki Action API.
- **Docs:** the Action API help lives at `www.mediawiki.org/w/api.php`; for
Wiktionary-specific templates the wiki itself is the reference (fetch the raw
wikitext of an entry to see current template usage).
- **Primary format:** `action=parse&prop=wikitext`. Use `prop=text` only when
you actually want rendered HTML.
- **Auth:** read-only public API; no key. A descriptive `User-Agent` is
mandatory — see **wikimedia-api-access** for the format and rate-limit rules.
| Task | Call |
|------|------|
| Entry wikitext (primary) | `action=parse&page=word&prop=wikitext` |
| Wikitext + HTML + categories | `action=parse&page=word&prop=wikitext\|text\|categories` |
| Same word in other editions | `action=query&prop=langlinks&titles=word&lllimit=max` |
| Words in a category | `action=query&list=categorymembers&cmtitle=Category:English_nouns` |
| Prefix lookup | `action=query&list=prefixsearch&pssearch=word&pslimit=10` |
| Full-text search | `action=query&list=search&srsearch=etymology&srwhat=text` |
| Namespaces / languages | `action=query&meta=siteinfo&siprop=namespaces\|languages` |
## Quick Smoke Test
```bash
UA="WiktSkill/1.0 (https://example.org; maintainer@example.org) WiktionarySkill"
# 1) Parsed wikitext for an entry — the primary format
curl -s -G "https://en.wiktionary.org/w/api.php" \
--data-urlencode "action=parse" \
--data-urlencode "page=word" \
--data-urlencode "prop=wikitext" \
--data-urlencode "format=json" \
-H "User-Agent: $UA"
# → {"parse":{"pageid":45,"title":"word","wikitext":{"*":"{{also|Word|worð}}\n==English==..."}}}
# 2) The same word on other editions (interwiki links)
curl -s -G "https://en.wiktionary.org/w/api.php" \
--data-urlencode "action=query" \
--data-urlencode "titles=word" \
--data-urlencode "prop=langlinks" \
--data-urlencode "lllimit=3" \
--data-urlencode "format=json" \
-H "User-Agent: $UA"
# → {"query":{"pages":{"45":{"title":"word","langlinks":[{"lang":"af","*":"word"}, ...]}}}}
# 3) Enumerate words in a namespace-backed category
curl -s -G "https://en.wiktionary.org/w/api.php" \
--data-urlencode "action=query" \
--data-urlencode "list=categorymembers" \
--data-urlencode "cmtitle=Category:English nouns" \
--data-urlencode "cmlimit=3" \
--data-urlencode "format=json" \
-H "User-Agent: $UA"
# → {"query":{"categorymembers":[{"ns":100,"title":"Appendix:English nouns"}, ...]}}
```
## SOP 1: Fetch an Entry's Wikitext
```python
import requests
def fetch_wikitext(word: str, lang: str = "en") -> str | None:
"""Return the raw wikitext of a Wiktionary entry, or None if missing.
The parse module's `wikitext` field is a dict with a single `*` key
(formatversion=1, the default) — not a bare string.
"""
api = f"https://{lang}.wiktionary.org/w/api.php"
resp = requests.get(api, params={
"action": "parse",
"page": word,
"prop": "wikitext",
"format": "json",
}, headers={
"User-Agent": "WiktSkill/1.0 (https://example.org; maintainer@example.org) WiktionarySkill",
}, timeout=30)
resp.raise_for_status()
data = resp.json()
if "parse" not in data:
return None
return data["parse"]["wikitext"]["*"]
```
## SOP 2: Isolate One Language Section
A page can hold dozens of languages. Isolate the target before parsing, or you
will mix French definitions into the English entry. Split on the legacy `----`
divider **and** on level-2 `==Language==` headings (many current entries have no
divider at all):
```python
import re
SPLIT_RE = re.compile(r"(?m)^(?=(?:-{4,}\s*$|==[^=]))")
def split_language_sections(wikitext: str) -> list[str]:
"""Return one chunk per level-2 language heading (divider optional)."""
chunks = SPLIT_RE.split(wikitext)
return [c for c in chunks if re.search(r"^==([^=]+)==", c, re.MULTILINE)]
def extract_language_section(wikitext: str, target_lang: str) -> str | None:
"""Return the `==English==` / `==French==` … section, or None."""
for section in split_language_sections(wikitext):
first = re.search(r"^==([^=]+)==", section, re.MULTILINE)
if first and first.group(1).strip() == target_lang:
return section
return None
```
`extract_language_section` and `count_languages` are provided ready-made in
[`assets/wt_entry_parser.py`](./assets/wt_entry_parser.py).
## SOP 3: Extract Definitions and Examples
```python
import re
def extract_definitions(lang_section: str) -> list[dict]:
"""Extract numbered definitions with their `#:` example sentences."""
definitions = []
current = None
for line in lang_section.split("\n"):
m = re.match(r"^#(?!:)\s*(.*)", line) # "# definition"
if m:
if current is not None:
definitions.append(current)
current = {"definition": m.group(1).strip(), "examples": []}
continue
m = re.match(r"^#:\s*(.*)", line) # "#: example"
if m and current is not None:
current["examples"].append(m.group(1).strip())
if current is not None:
definitions.append(current)
return definitions
```
Definitions are raw wikitext — internal wiki links (`[[` `…` `]]` markup),
`'''bold'''`, and templates such as
`{{l|en|…}}` are still present. Clean them yourself or hand the section to
**wikimedia-wikitext** for a proper `mwparserfromhell` pass.
## SOP 4: Translation Tables
Translations live in `{{trans-top}} … {{trans-bottom}}` blocks, with `{{t}}` /
`{{t+}}` rows. `{{trans-mid}}` separates left-to-right from right-to-left
languages and carries no data of its own — skip it.
```python
import re
def extract_translations(lang_section: str) -> dict[str, list[str]]:
"""Return {'French': ['mot'], 'Spanish': ['palabra'], ...} for a section."""
translations = {}
current_lang = None
for line in lang_section.split("\n"):
top = re.match(r"\{\{trans-top\|(.+?)\}\}", line)
if top:
current_lang = top.group(1).strip()
translations.setdefault(current_lang, [])
continue
if line.startswith("{{trans-bottom"):
current_lang = None
continue
if line.startswith("{{trans-mid"):
continue
if current_lang:
row = re.match(r"\{\{t\+?\|([a-z]{2,3})\|([^}|]+)", line)
if row:
translations[current_lang].append(row.group(2).strip())
return translations
```
⚠️ Append to translation tables; never replace them (see Guardrails).
## SOP 5: Pronunciation — IPA and Audio
IPA is inline (`{{IPA|en|/wɜːd/}}`); audio files live on Commons and are
referenced by name. Current entries pass the qualifier as a named parameter
(`{{audio|en|En-us-word.ogg|a=GA}}`); older ones use a positional label
(`{{audio|en|en-uk-word.ogg|Audio (UK)}}`).
```python
import re
def extract_pronunciation(lang_section: str) -> list[dict]:
"""Extract IPA values and audio file references from a language section."""
found = []
for line in lang_section.split("\n"):
for m in re.finditer(r"\{\{IPA\|([a-z-]+)\|([^}|]+)", line):
found.append({"type": "ipa", "lang": m.group(1),
"value": m.group(2).strip()})
for m in re.finditer(
r"\{\{audio\|([a-z-]+)\|([^}|]+)(?:\|a=([^}|]+))?", line
):
found.append({"type": "audio", "lang": m.group(1),
"file": m.group(2).strip(), "qualifier": m.group(3)})
return found
```
Resolve the `file` field against Commons to build a playable URL — see
**wikimedia-commons** for the file-path and thumbnail patterns.
## SOP 6: Wikidata Lexemes (L-entities)
Every word-sense can have a structured Wikidata **lexeme**: lemma, language,
lexical category (e.g. noun = Q1084), forms (inflections), and senses.
Lexeme IDs look like `L1`, `L12345`.
```python
import requests
def get_lexeme(lexeme_id: str) -> dict:
"""Fetch a Wikidata lexeme (lemma, language, lexical category, forms, senses)."""
url = f"https://www.wikidata.org/wiki/Special:EntityData/{lexeme_id}.json"
resp = requests.get(url, headers={
"User-Agent": "WiktSkill/1.0 (https://example.org; maintainer@example.org) WiktionarySkill",
}, timeout=30)
resp.raise_for_status()
return resp.json()["entities"][lexeme_id]
# Batch work via Pywikibot's data repository:
# import pywikibot
# site = pywikibot.Site("en", "wiktionary")
# repo = site.data_repository()
# lexeme = repo.get_lexeme("L1")
# print(lexeme.lemma)
```
## Guardrails
### ❌ Don't assume English Wiktionary
Wiktionary has **over 170 language editions**, each with its own template
conventions and its own sense of what a "section" contains. Always pass the
language code explicitly; never hard-code `en`.
### ❌ Don't confuse `----` with a section heading
`----` is **four** hyphens, a raw divider between language sections — not a
heading, and **not required**. Verified live on en.wiktionary (2026-09-11):
`word`, `water`, and `cat` contain *no* `----` line at all; sections are
delimited purely by level-2 `==Language==` headings. Split on both, and key on
the headings.
### ❌ Don't overwrite translation tables
Translation tables are maintained by many editors and are easy to corrupt.
**Append** new rows rather than replacing a `{{trans-top}} … {{trans-bottom}}`
block, and preserve the `{{trans-mid}}` separator.
### ❌ Don't trust stale namespace numbers
Older references list `104`=Rhymes, `106`=Thesaurus, `108`=Citations. The live
en.wiktionary layout (verified via `meta=siteinfo&siprop=namespaces`) is
`100`=Appendix, `106`=Rhymes, `108`=Transwiki, `110`=Thesaurus, `114`=Citations,
`116`=Sign gloss, `118`=Reconstruction. Re-query `siteinfo` rather than guessing.
### ❌ Don't mix section-header language names with template language codes
Section headers use the **English name** (`==French==`); template parameters use
the **ISO 639 code** (`{{t|fr|mot}}`). Comparing the two directly never matches.
### ❌ Don't forget the User-Agent
Every request needs a descriptive `User-Agent`; outside Toolforge/WMCS, pace
requests at ≥1 s and handle 429/403. See **wikimedia-api-access**.
## Tooling
### 🔧 Scripts
| Script | Purpose | Usage |
|--------|---------|-------|
| [`scripts/wt-entry-summary.sh`](./scripts/wt-entry-summary.sh) | Fetch an entry and print its language sections, parts of speech, definitions, templates, and interwiki links | `./wt-entry-summary.sh word en` |
### 🐍 Python Assets
| Asset | Purpose | Usage |
|-------|---------|-------|
| [`assets/wt_entry_parser.py`](./assets/wt_entry_parser.py) | Importable parser: language sections, definitions + examples, translation tables, IPA/audio, inflection templates | `from wt_entry_parser import WiktionaryParser` |
### 📚 Reference Docs
| Document | Contents |
|----------|----------|
| [`references/wiktionary-entry-structure.md`](./references/wiktionary-entry-structure.md) | Entry anatomy: heading hierarchy, POS and subsection tables, template families, language codes, API modules, namespaces |
## Cross-References
| Related Skill | Why |
|--------------|-----|
| **[wikimedia-api-access](../wikimedia-api-access/SKILL.md)** | User-Agent format, rate limits, and Action API request patterns for every call above |
| **[wikimedia-commons](../wikimedia-commons/SKILL.md)** | Audio pronunciation files referenced by `{{audio}}` are hosted on Commons |
| **[wikimedia-wikitext](../wikimedia-wikitext/SKILL.md)** | Parsing entry wikitext with `mwparserfromhell` — templates, links, `<ref>` tags |
| **[pywikibot](../pywikibot/SKILL.md)** | Batch entry edits and lexeme operations via the bot framework |
| **[wikidata](../wikidata/SKILL.md)** | Lexemes, senses, and structured data behind each entry |
| **[wikimedia-i18n-l10n-for-tools](../wikimedia-i18n-l10n-for-tools/SKILL.md)** | Language detection and fallback for multilingual entries |