Elvish (Tolkien) translation tools, plus a skill for use in Claude or some lesser LLM.
SaferSkills independently audited elvish-translation-tools (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Every word in an Elvish translation must be traceable to a source: Tolkien's writings, a Neo-Sindarin reconstruction in Eldamo, or a deliberate coinage that you explicitly flag. Hallucinated vocabulary is the failure mode this skill exists to prevent.
Sindarin and Quenya have small attested corpora and large reconstructed vocabularies. Models often "know" plausible-looking words that don't actually exist, or mix Quenya words into Sindarin text. Always check.
sjn) or Quenya (qya).Use when an entry (or a whole file) already exists and you're checking it.
${CLAUDE_SKILL_DIR}/scripts/lookup.py check <word>This reports: where the word is attested, in what language, and any warnings (Quenya-as-Sindarin, deprecated, Gnomish revival, etc.).
elements[] provenance matches what lookup.py check reports.Use when adding new entries (drift from upstream source) or producing a translation from scratch.
${CLAUDE_SKILL_DIR}/scripts/lookup.py search <english-substring>For false-friend awareness across languages, add --any-lang.
northo for "play", reuse it; don't invent a new word). ${CLAUDE_SKILL_DIR}/scripts/lookup.py check <proposed-word>sjn.roman, sjn.literal, and elements[] with attestation and source per element.Path: ${CLAUDE_SKILL_DIR}/scripts/lookup.py. Reads compact TSVs extracted from Paul Strack's Eldamo lexicon. Default scope: Sindarin family (s, n, ns). Add --any-lang to include Quenya / Gnomish.
| Subcommand | Use for |
|---|---|
lookup <lemma> | Exact dictionary lookup. Returns the headword entry. |
search <gloss> | Find Sindarin words whose English gloss contains a substring. |
check <form> | Workhorse. Reports attestation status + warnings for any form. Use before accepting or proposing any word. |
cognates <lemma> | Show related forms across Sindarin / Noldorin / Quenya. False-friend detection. |
forms <pattern> | Search attested inflected / compound surface forms. |
Run ${CLAUDE_SKILL_DIR}/scripts/lookup.py --help for full options.
| Code | Language | Use for |
|---|---|---|
s | Sindarin (post-LotR conception) | Primary target language. |
n | Noldorin (1930s precursor to Sindarin) | Fallback when Sindarin lacks a word; flag explicitly. |
ns | Neo-Sindarin | Modern reconstructions in Eldamo; flag with creator. |
q | Quenya (post-LotR conception) | False-friend check only. Do not mix into Sindarin text. |
nq | Neo-Quenya | Same caution as Quenya. |
g | Gnomish (1910s, ancestor of Sindarin) | Rare revival material; flag explicitly. |
en | Early Noldorin (1920s) | Rare revival material; flag explicitly. |
The most frequent error is reaching for a Quenya word that looks like the right meaning. Some recurring traps:
| Wrong | Correct Sindarin | Note |
|---|---|---|
lambë "language" | lam (no attested plural; Neo-Sindarin reconstruction: laim) | lambë is Quenya. |
la "not" | ú- (prefix, with mutation) or law / baw | la is Quenya. |
panta "open" | pant | panta is Quenya. |
lintië "speed" | lint "swift" (adj.; no direct attested noun) | lintië is Quenya. |
enquet- "repeat" | No attested Sindarin verb; use ad- prefix on a verb of doing/saying | enquet- is Quenya AND deprecated in Eldamo. |
When in doubt, check the candidate.
Sindarin requires consonant mutations at certain word boundaries. Get these wrong and the text looks foreign even when the vocabulary is right.
Triggered by: prepositions ab, adel, am, be, di, na, nu, trî, u-; adjectives after their noun; direct objects of verbs; compound second elements.
| Basic | Mutated | |
|---|---|---|
| b → v | c → g | d → dh |
| g → (silent) | gw → w | h → ch |
| hw → chw | m → v | p → b |
| s → h | t → d | th → th (no change) |
Triggered by: plural article in; preposition / prefix an "for, to"; preposition dan "against".
| Basic | Mutated | |
|---|---|---|
| b → m | d → n | g → ng |
| p → ph | t → th | c → ch |
Triggered by: prepositions ed "out of", ned "in", o / od "from".
| Basic | Mutated | |
|---|---|---|
| p → ph | t → th | c → ch |
| b → p | d → t | g → c |
For the full table including speculative mixed / liquid mutations, see ${CLAUDE_SKILL_DIR}/references/mutations.md.
-o for all persons. dar- "halt" → daro!.-ed or -ad. teitha- "write" → teithad "writing / to write".-en (sg) / -in (pl). teitha- → teithen / teithin.-ta- extended into Neo-Sindarin (nor- "run" → northa- "make run"). Defensible but flag as reconstructed.i (sg, lenites) / in (pl, nasal-mutates).aran "king" + pant "open" → aran bant.mi (no mutation) or lenited vi. Not bir (unattested).For verb paradigms, pluralization rules, pronouns, and prepositions, see ${CLAUDE_SKILL_DIR}/references/grammar.md.
Many translation projects benefit from a structured YAML that records per-word provenance. The recommended v1 schema lives at ${CLAUDE_SKILL_DIR}/references/translation-schema.md. Read it when:
elements[] or similar.Key enums to know:
attested / noldorin / gnomish / neo-sindarin / quenya / coined / deprecated.attested / attested-components / defensible-neo-sindarin / coinage / needs-revision / placeholder.When filling out elements[], the source citation should be whatever lookup.py check reports (e.g. Ety/NOR, PE17/174, LotR/0339).
The sjn.tengwar field uses CSUR (ConScript Unicode Registry) Tengwar at U+E000–U+E07F, the encoding supported by the Tengwar Telcontar font and the Tecendil transliterator. Do not hand-edit these characters; they render as boxes without a CSUR-aware font. See ${CLAUDE_SKILL_DIR}/references/tengwar-csur.md for the codepoint table and ${CLAUDE_SKILL_DIR}/references/translation-schema.md for alternative encodings.
Use the bundled transliteration engine instead of hand-typing Tengwar:
${CLAUDE_SKILL_DIR}/scripts/transliterate.py sjn "<romanized>" ${CLAUDE_SKILL_DIR}/scripts/transliterate.py qya "<romanized>" ${CLAUDE_SKILL_DIR}/scripts/transliterate.py sjn "<romanized>" --show-codepoints
The engine implements Tecendil's Sindarin / Quenya / Beleriand modes (mode files vendored under ${CLAUDE_SKILL_DIR}/data/modes/). Its output is CSUR Tengwar codepoints.
Default output format is `\uXXXX` escape sequences, not literal PUA characters. This is so the result can be reviewed in any editor and pasted directly into a YAML sjn.tengwar double-quoted string or a JSON value. Use --literal only to pipe into a font-aware preview, never to store in source files.
When to use:
sjn.roman and compare. A mismatch flags either an old hand-typed entry to update or a roman/Tengwar disagreement worth investigating.Output matches Tecendil's deployed engine for the 24 regression cases in ${CLAUDE_SKILL_DIR}/data/modes/sindarin-tests.yaml and quenya-tests.yaml. Known divergences: no thin-space (U+2009) word separator (Tecendil emits it for display only).
To render escape-encoded Tengwar as literal glyphs (requires Tengwar Telcontar or another CSUR font installed):
echo '' | ${CLAUDE_SKILL_DIR}/scripts/preview.py - ${CLAUDE_SKILL_DIR}/scripts/preview.py --yaml path/to/sjn-translations.yaml
After revising one or more sjn.roman fields in a translation YAML, sync the corresponding sjn.tengwar fields:
${CLAUDE_SKILL_DIR}/scripts/regen_tengwar.py path/to/sjn-translations.yaml ${CLAUDE_SKILL_DIR}/scripts/regen_tengwar.py path/to/qya-translations.yaml --mode qya ${CLAUDE_SKILL_DIR}/scripts/regen_tengwar.py path/to/sjn-translations.yaml --dry-run
For every entry with a non-empty sjn.roman, runs the romanized form through transliterate.py and replaces the value inside tengwar: "..." with the escape-encoded result. Uses targeted text editing -- comments, indentation, blank lines, and surrounding content are preserved exactly.
Use this any time you change a roman field; never hand-update a tengwar field.
When generating or modifying entries in a translation YAML file (typically sjn-translations.yaml), the agent is responsible for emitting valid YAML. Common ways to break it:
Never embed unescaped double quotes inside a double-quoted YAML string. This is the most common failure. When citing source material, never write:
source: "LotR/0307 (in lammen, "lasto beth lammen")" note: "Originally "hasto" (intended pause)."
The second " closes the string and YAML rejects the rest of the line. Use one of these instead:
source: "LotR/0307 (in lammen, 'lasto beth lammen')"source: "LotR/0307 (in lammen, \"lasto beth lammen\")"source: "LotR/0307 (in the lammen phrase)"The single-quote-inside-double-quote form is usually the most readable and is the preferred style for this project.
Other YAML safety rules when generating entries:
: or any other YAML special characters (#, &, *, !, |, >, ', ", %, @, leading -/?/,).python3 -c "import yaml; yaml.safe_load(open('path.yaml'))" to verify the file parses.For values containing Tengwar codepoints: store as \uXXXX escape sequences (see "Producing Tengwar" above), never as literal PUA characters. This avoids invisible-character bugs and makes the YAML reviewable.
For each entry being reviewed or proposed:
lookup.py check. Status recorded.cognates if in doubt.meiras, enquet-).check will surface this via the surface-form listings.northo for "play").| Excuse | Reality |
|---|---|
| "I know this Sindarin word" | Training data is unreliable for low-resource constructed languages. Always check. |
| "Quenya is close enough" | It isn't. Quenya inside Sindarin reads as broken to anyone who recognizes the difference. |
| "I'll just coin a plausible word" | Coinage is OK, but must be flagged; never present as established. |
| "Mutations are too complex, skip them" | Wrong mutation = visibly broken Sindarin. Apply per the tables. |
| "The user will fix it later" | Make the right call now or defer explicitly. Don't ship guesses. |
| "It's just a UI string, low stakes" | Low stakes means more reason to be honest about confidence, not less. |
| "I'll add a citation with the phrase quoted" | Watch the YAML quoting. Embedded double quotes inside a double-quoted string break the file. Use single quotes for the inner phrase, or paraphrase. See 'Editing translation YAML files safely'. |
These came up reviewing an existing UI translation. Patterns to recognize:
lambë for "language" (Sindarin: lam). panta for "open" (Sindarin: pant). The Quenya forms are more famous from fan material; the Sindarin equivalents are less seen but correct.enqueto from Quenya enquet- + Sindarin imperative -o. The grammar is Sindarinized but the lexeme is Quenya. Always start from a Sindarin headword.unt "nothing" is Gnomish (1910s), not Sindarin. May be a deliberate revival; flag it.meiras for "value" was coined by Paul Strack but flagged deprecated in Eldamo. The tool surfaces this.Translation projects layer their own conventions on this framework. Look for:
applying-schema.md in the project root.glossary.md or similar from prior review work.Don't override project-specific conventions; reuse them. If the project consistently uses fanwos for "picture / screen", new entries that touch that concept should use fanwos too.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.