return callback(makeColumn("round-carrier"))(character);
}
};
- } else if (character === "e") { // e
+ } else if (character === "e" || character === "ë") { // e
return function (character) {
if (character === "i") { // ei
return callback(makeColumn("yanta").addAbove("í"));
return callback([previous, makeColumn("short-carrier").addAbove("a")])(character);
}
};
- } else if (character === "e") {
+ } else if (character === "e" || character === "ë") {
var tehta = swapDotSlash("e", options);
return function (character) {
if (character === "e") {
"full-stop": "-",
"exclamation-point": "Á",
"question-mark": "À",
- "open-paren": "Œ",
- "close-paren": "œ",
+ "open-paren": "=", // alt "Œ",
+ "close-paren": "=", // alt "œ",
"flourish-left": "Ğ",
"flourish-right": "ğ",
// numbers
<p><em><strong>Purpose:</strong></em> The transcriber is suitable for
rendering <a href="http://tolkiengateway.net/wiki/Sindarin">Sindarin</a> in
- <a href="http://at.mansbjorkman.net/teng_general.htm">General Use </a>,
- more or the
+ the mode for <a href="http://at.mansbjorkman.net/teng_general.htm">general use</a>,
+ or the
<a href="http://at.mansbjorkman.net/teng_beleriand.htm">Mode of Beleriand</a>,
and <a href="http://tolkiengateway.net/wiki/Quenay">Quenya</a> in the
<a href="http://at.mansbjorkman.net/teng_quenya.htm">Classical</a>
<script src="http://static.getclicky.com/js" type="text/javascript"></script>
<script type="text/javascript">clicky.init(237383);</script>
- <noscript><p><img alt="Clicky" width="1" height="1" src="http://in.getclicky.com/237383ns.gif" /></p></noscript>
+ <noscript><p><img alt="Clicky" width="1" height="1" src="http://in.getclicky.com/237383ns.gif"></p></noscript>
<script type="text/javascript">
var _gaq = _gaq || [];
body {
margin: 0;
padding: 0;
- height: 100%;
- width: 100%;
-
- overflow: auto;
-
}
-body.annatar {
+body.annatar #body-box {
background-repeat: no-repeat;
background: #fefcea; /* Old browsers */
text-shadow: 0px 0px 10px #730 ;
}
-body.parmaite {
+body.parmaite #body-box {
background-image: url(paper.jpg);
background-repeat: no-repeat;
background-size: 100%;
/* layout */
#body-box {
+ position: absolute;
+ bottom: 0;
+ top: 0;
+ left: 0;
+ right: 0;
+ overflow: auto;
display: -webkit-flex;
-webkit-flex-flow: column;
-webkit-align-items: stretch;
- height: 100%;
- width: 100%;
}
#output-box {
+<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
var Bindings = require("frb");
require("frb/dom");
-var Properties = require("frb/properties");
var QS = require("qs");
var modes = require("tengwar/modes");
var fonts = require("tengwar/fonts");
state.font = state.font || "annatar";
state.options = state.options || [];
state.height = state.height || null;
+state.language = state.language || null;
-var bindings = Bindings.create(null, {
+var bindings = Bindings.defineBindings({
+ modes: modes,
+ fonts: fonts,
state: state,
window: window,
bodyElement: document.body,
dividerElement: document.querySelector("#divider"),
inputBoxElement: document.querySelector("#input-box"),
selectModeElement: document.querySelector("#select-mode"),
- wikiTextElement: document.querySelector("#wiki-text")
+ wikiTextElement: document.querySelector("#wiki-text"),
+
+ searchString: function () {
+ var state = this.state;
+
+ // remove things that QS can't seem to handle
+ delete state[""]; // XXX QS bug interprets empty array as & and back to an empty assignment
+ if (state.options.length == 0) {
+ delete state.options;
+ }
+ var string = "?" + QS.stringify(this.state);
+ window.history.replaceState(this.state, "", string);
+
+ state.options = state.options || [];
+ return string;
+ }
+
}, {
"inputElement.value": {"<->": "state.q"},
},
"inputBoxElement.style.height": {
- "<-": "heightPx"
- },
-
- "heightPx": {
- "<-": "state.height",
- convert: function (height) {
- return height + "px";
- },
- revert: function (height) {
- return parseInt(height, 10);
- }
+ "<-": "state.height + 'px'"
},
"mode": {
- args: ["state.mode"],
- compute: function (mode) {
- return modes[mode] || modes['general-use'];
- }
+ "<-": "modes[state.mode] ?? modes['general-use']"
},
"font": {
- args: ["state.font"],
- compute: function (font) {
- return fonts[font] || modes['annatar'];
- }
+ "<-": "fonts[state.font] ?? fonts['annatar']"
},
"outputElement.innerHTML": {
- args: ["mode", "font", "state.q", "state.options"],
- compute: function (mode, font, input, flags) {
+ args: ["mode", "font", "state.language ?? 'unknown'", "state.q", "state.options"],
+ compute: function (mode, font, language, input, flags) {
var options = {
font: font,
+ language: language,
block: true
};
flags.forEach(function (flag) {
},
"selectModeElement.href": {
- "<-": "'modes.html' + searchString"
- },
-
- "searchString": {
- args: ["state.q", "state.mode", "state.font", "state.height", "state.options"],
- compute: function () {
- var state = this.state;
-
- // remove things that QS can't seem to handle
- delete state[""]; // XXX QS bug interprets empty array as & and back to an empty assignment
- if (state.options.length == 0) {
- delete state.options;
- }
- var string = "?" + QS.stringify(this.state);
- window.history.replaceState(this.state, "", string);
-
- state.options = state.options || [];
- return string;
- }
+ "<-": "'modes.html' + searchString(state.q, state.mode, state.font, state.height ?? 0, state.options)"
},
"wikiTextElement.href": {
<h3><em>For a Tengar Transcriber</em></h3>
<ul>
- <li><a href="#general-use">General Use Mode</a></li>
+ <li><a href="#general-use">Mode for general use</a></li>
<ul>
<li><a href="#black-speech">Black Speech</a></li>
<li><a href="#kings-letter-general-use">The Third Version of the King’s Letter</a></li>
+ <li><a href="#english">English</a></li>
</ul>
- <li><a href="#classical">Classical Mode</a></li>
+ <li><a href="#classical">The Classical mode</a></li>
<ul>
<li><a href="#namarie">Namárië</a></li>
</ul>
<a name="kings-letter-general-use"></a>
- <h2><a class="dagger" href="http://at.mansbjorkman.net/teng_quenya.htm">General Use Mode</a></h2>
+ <h2><a class="dagger" href="http://at.mansbjorkman.net/teng_quenya.htm">Mode for general use</a></h2>
<h3>As in the third version of <em><a class="dagger" href="http://tolkiengateway.net/wiki/King's_Letter">The King’s Letter</a></em></h3>
<div>
Westlands, will approach the Bridge of Baranduin on the eighth day of
Spring, or in the Shire-reckoning the second day of April.</blockquote>
- <p>General Use Mode uses diacritics to represent vowels, usually above
- the tengwa the vowel precedes. General Use mode is suitable for
+ <p>The mode for general use employs diacritics to represent vowels,
+ usually above the tengwa the vowel precedes. The mode is suitable for
representing most languages. The King’s Letter is the longest example
of
<a class="dagger" href="http://tolkiengateway.net/wiki/Sindarin">Sindarin</a>
One Ring to bring them all, and in the darkness bind them
</blockquote>
- <p>General Use Mode uses diacritics to represent vowels, usually above
- the tengwa the vowel <em>precedes</em>. The inscription on the One
- Ring is a variant of General Use mode that reverses the direction of
- curls for O and U, and uses extended tengwa for the SH and GH sounds.
- The italic of the
+ <p>The mode for general use employs diacritics to represent vowels,
+ usually above the tengwa the vowel <em>precedes</em>. The inscription
+ on the One Ring is a variant of mode for general use that reverses the
+ direction of curls for O and U, and uses extended tengwa for the SH and
+ GH sounds. The italic of the
<a class="dagger" href="http://home.student.uu.se/jowi4905/fonts/annatar.html">Tengwar Annatar</a>
font by Johan Winge captures the flowing hand presumed of Sauron.</p>
+
+ <hr>
+
+
+ <a name="english"></a>
+ <h2><a class="dagger" href="http://at.mansbjorkman.net/teng_general_english.htm">English</a></h2>
+
+ <div>
+ <span id="english-general-use-tengwar"
+ class="dynamic-tengwar"
+ data-tengwar="The Lord of the Rings"
+ data-mode="general-use"
+ data-font="parmaite"
+ data-lang="english">
+ Rendering… (Requires JavaScript and Web Fonts)
+ </span>
+ <button id="english-general-use-button">Select</button>
+ </div>
+
+ <blockquote>The Lord of the Rings</blockquote>
+
+ <p>This is a slight adaptation of the mode for general use. The mode
+ introduces an underposed dot that stands for a silent, orthographic “e”
+ at the end of a word. Medial “e” can be transformed to a silent in in
+ this mode with a following tick, “<code>'</code>”. Also, the common
+ patterns, “of”, “the”, “of the”, and “and” have single-tengwa
+ shorthands, some with variations that can be introduced with a
+ following tick, or a tick between “<code>of'the</code>” to separate
+ these words.</p>
+
+ <p>The mode is haltingly suitable for both orthographic and phonetic
+ transliteration for the clever writer if they are willing to tease the
+ machine.</p>
+
+
<hr>
function TengwarComponent(element, state) {
// extract options from the mode string
- var flags = element.dataset.mode.split(" ");
+ var flags = (element.dataset.mode || "").split(" ");
flags.shift();
var options = {};
flags.forEach(function (flag) {
options[flag] = true;
});
- Bindings.create(null, {
+ Bindings.defineBindings({
+ fonts: fonts,
+ modes: modes,
input: element.dataset.tengwar,
element: element,
options: options,
state: state
}, {
- "element.classList.*": {
- "<-": "('tengwar', state.font, state.mode, 'rendered')"
+ "element.classList.rangeContent()": {
+ "<-": "['tengwar', state.font, state.mode, 'rendered']"
},
- "mode": {
- "args": ["state.mode"],
- "compute": function (mode) {
- return modes[mode];
- }
- },
+ "mode": {"<-": "modes[state.mode]"},
- "options.font": {
- "args": ["state.font"],
- "compute": function (font) {
- return fonts[font];
- }
- },
+ "options.font": {"<-": "fonts[state.font]"},
+
+ "options.language": {"<-": "state.language"},
"element.innerHTML": {
"args": ["options.font", "mode", "input", "options"],
Array.prototype.forEach.call($$(".dynamic-tengwar"), function (element) {
TengwarComponent(element, {
font: element.dataset.font,
- mode: element.dataset.mode.split(" ")[0]
+ mode: (element.dataset.mode || "").split(" ")[0],
+ language: element.dataset.lang || "unknown"
});
});
event.stopPropagation();
event.preventDefault();
state.options.sort();
- window.location = "index.html?" + QS.stringify(state);
+ window.location = "./?" + QS.stringify(state);
});
}
TengwarComponent(element, state);
});
- Bindings.create(null, {
+ Bindings.defineBindings({
parmaite: document.getElementById(mode + "-font-parmaite"),
annatar: document.getElementById(mode + "-font-annatar"),
state: state
}, {
- "state.font = 'parmaite'": {"<->": "parmaite.checked"},
- "state.font = 'annatar'": {"<->": "annatar.checked"}
+ "state.font == 'parmaite'": {"<->": "parmaite.checked"},
+ "state.font == 'annatar'": {"<->": "annatar.checked"}
});
Button(document.getElementById(mode + "-button"), state);
q: document.getElementById("black-speech-tengwar").dataset.tengwar,
font: "annatar",
mode: "general-use",
- options: ["black-speech"]
+ language: "blackSpeech",
+ options: []
+});
+
+Button(document.getElementById("english-general-use-button"), {
+ q: document.getElementById("english-general-use-tengwar").dataset.tengwar,
+ font: "parmaite",
+ mode: "general-use",
+ language: "english",
+ options: []
});
Button(document.getElementById("namarie-button"), {
}
].forEach(function (flag) {
- Bindings.create(null, {
+ Bindings.defineBindings({
option: flag.option,
off: $("#" + flag.off),
on: $("#" + flag.on),
});
// special multi-way binding for treatment of H
-Bindings.create(null, {
+Bindings.defineBindings({
halla: $("#classical-period-halla"),
aha: $("#classical-period-aha"),
hyarmen: $("#classical-period-hyarmen"),
{
- "name": "tengwar-editor",
- "version": "0.0.0",
- "dependencies": {
- "frb": "0.0.x",
- "mr": "0.0.x",
- "qs": "0.1.x",
- "tengwar": "0.1.x"
+ "name": "tengwar-editor",
+ "version": "0.0.0",
+ "dependencies": {
+ "frb": "~0.2.16",
+ "qs": "0.5.x",
+ "tengwar": "0.1.x",
+ "mr": "~0.14.2"
+ },
+ "bugs": {
+ "mail": "kris@cixar.com",
+ "web": "http://github.com/kriskowal/tengwarjs/issues"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://github.com/kriskowal/tengwarjs/raw/master/LICENSE"
},
- "bugs": {
- "mail": "kris@cixar.com",
- "web": "http://github.com/kriskowal/tengwarjs/issues"
- },
- "licenses": [
- {
- "type": "MIT",
- "url": "http://github.com/kriskowal/tengwarjs/raw/master/LICENSE"
- },
- {
- "url": "http://github.com/kriskowal/tengwarjs/raw/master/tengwar-annatar/tngandoc.pdf"
- }
- ],
- "repository": {
- "type": "git",
- "url": "http://github.com/kriskowal/tengwarjs.git"
+ {
+ "url": "http://github.com/kriskowal/tengwarjs/raw/master/tengwar-annatar/tngandoc.pdf"
}
+ ],
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/kriskowal/tengwarjs.git"
+ }
}
exports.makeOptions = makeOptions;
function makeOptions(options) {
options = options || defaults;
+ // legacy
+ if (options.blackSpeech) {
+ options.language = "blackSpeech";
+ }
return {
font: options.font || TengwarAnnatar,
block: options.block,
// or below.
// false: by default, place a tilde above doubled nasals.
// true: place the tilde below doubled nasals.
- reverseCurls: options.reverseCurls || options.blackSpeech,
+ reverseCurls: options.reverseCurls || options.language === "blackSpeech",
// false: by default, o is forward, u is backward
// true: o is backward, u is forward
swapDotSlash: options.swapDotSlash,
// false: by default, e is a slash, i is a dot
// true: e is a dot, i is a slash
- medialOre: options.medialOre || options.blackSpeech,
+ medialOre: options.medialOre || options.language === "blackSpeech",
// false: by default, ore only appears in final position
// true: ore also appears before consonants, as in the ring inscription
- blackSpeech: options.blackSpeech,
- // false: sh is harma, gh is unque
- // true: sh is calma-extended, gh is ungwe-extended, as in the ring
- // inscription
+ language: options.language,
+ // by default, no change
+ // "english": final e implicitly silent
+ // "black speech": sh is calma-extended, gh is ungwe-extended, as in
+ // the ring inscription
+ // not "blackSpeech": sh is harma, gh is unque
noAchLaut: options.noAchLaut,
// false: "ch" is interpreted as ach-laut, "cc" as "ch" as in "chew"
// true: "ch" is interpreted as "ch" as in chew
var font = options.font;
var makeColumn = font.makeColumn;
return scanWord(function (word) {
- if (book[word]) {
+ if (options.language === "english" && word === "of") {
+ return function (character) {
+ var of = Notation.decodeWord(englishBook[word], makeColumn);
+ if (character === " ") {
+ return scanWord(function (word, rewind) {
+ if (word === "the") {
+ return callback(Notation.decodeWord(englishBook["of the"], makeColumn));
+ } else {
+ return rewind(callback(of)(character));
+ }
+ });
+ } else {
+ return callback(of)(character);
+ }
+ };
+ } else if (options.language === "english" && englishBook[word]) {
+ return callback(Notation.decodeWord(englishBook[word], makeColumn));
+ } else if (book[word]) {
return callback(Notation.decodeWord(book[word], makeColumn));
} else {
- return callback(parseWordPiecewise(word, options));
+ return callback(parseWordPiecewise(word, word.length, options));
}
}, options);
}
"noldor": "nwalme;lambe:o;ando;ore:o"
};
-function scanWord(callback, options, word) {
+var englishBook = {
+ "of": "umbar-extended",
+ "of'": "umbar-extended:u",
+ "of the": "umbar-extended:tilde-below",
+ "of'the": "umbar-extended ando-extended",
+ "the": "ando-extended",
+ "the'": "ando-extended:i-below",
+ "and": "ando:tilde-above",
+ "and'": "ando:tilde-above,i-below",
+ "we": "vala:y"
+};
+
+function scanWord(callback, options, word, rewind) {
word = word || "";
+ rewind = rewind || function (state) {
+ return state;
+ };
return function (character) {
if (Parser.isBreak(character)) {
- return callback(word)(character);
+ return callback(word, rewind)(character);
} else {
- return scanWord(callback, options, word + character);
+ return scanWord(callback, options, word + character, function (state) {
+ return rewind(state)(character);
+ });
}
};
}
-var parseWordPiecewise = Parser.makeParser(function (callback, options) {
- return parseWordTail(callback, options, []);
+var parseWordPiecewise = Parser.makeParser(function (callback, length, options) {
+ return parseWordTail(callback, length, options, []);
});
-function parseWordTail(callback, options, columns, previous) {
+function parseWordTail(callback, length, options, columns, previous) {
return parseColumn(function (moreColumns) {
if (!moreColumns.length) {
return callback(columns);
} else {
return parseWordTail(
callback,
+ length,
options,
columns.concat(moreColumns),
moreColumns[moreColumns.length - 1] // previous
);
}
- }, options, previous);
+ }, length, options, previous);
}
-function parseColumn(callback, options, previous) {
+function parseColumn(callback, length, options, previous) {
var font = options.font;
var makeColumn = font.makeColumn;
return parseTehta(function (tehta) {
- return parseTengwa(function (column) {
+ return parseTengwa(function (column, tehta) {
if (column) {
if (tehta) {
if (options.reverseCurls) {
column.addAbove(tehta);
return parseTengwaAnnotations(function (column) {
return callback([column]);
- }, column);
+ }, column, length, options);
} else {
// some tengwar inherently lack space above them
// and cannot be reversed to make room.
// then follow up with this tengwa.
return parseTengwaAnnotations(function (column) {
return callback([makeCarrier(tehta, options), column]);
- }, column);
+ }, column, length, options);
}
} else {
return parseTengwaAnnotations(function (column) {
return callback([column]);
- }, column);
+ }, column, length, options);
}
} else if (tehta) {
if (options.reverseCurls) {
}
return parseTengwaAnnotations(function (carrier) {
return callback([carrier]);
- }, makeCarrier(tehta, options));
+ }, makeCarrier(tehta, options), length, options);
} else {
return function (character) {
if (Parser.isBreak(character)) {
} else if (punctuation[character]) {
return callback([makeColumn(punctuation[character])]);
} else {
- return callback([makeColumn("ure").addError("Cannot transcribe " + JSON.stringify(character) + " in General Use Mode")]);
+ return callback([
+ makeColumn("ure")
+ .addError(
+ "Cannot transcribe " +
+ JSON.stringify(character) +
+ " in General Use Mode"
+ )
+ ]);
}
};
}
}, options, tehta);
- });
+ }, options);
}
}
}
-function parseTehta(callback) {
+function parseTehta(callback, options) {
return function (character) {
+ var firstCharacter = character;
+ if (character === "ë" && options.language !== "english") {
+ character = "e";
+ }
if (character === "") {
return callback();
} else if (lengthenableVowels.indexOf(character) !== -1) {
var longerVowels = {"a": "á", "e": "é", "i": "í", "o": "ó", "u": "ú"};
var nonLengthenableVowels = "aeióú";
var tehtarThatCanBeAddedAbove = "aeiouóú";
-var vowels = "aeiouáéíóú";
+var vowels = "aeëiouáéíóú";
var shorterVowels = {"á": "a", "é": "e", "í": "i", "ó": "o", "ú": "u"};
var reverseCurls = {"o": "u", "u": "o", "ó": "ú", "ú": "ó"};
var swapDotSlash = {"i": "e", "e": "i"};
return function (character) {
if (character === "n") { // nn
if (options.doubleNasalsWithTildeBelow) {
- return callback(makeColumn("numen").addTildeBelow());
+ return callback(makeColumn("numen").addTildeBelow(), tehta);
} else {
- return callback(makeColumn("numen").addTildeAbove());
+ return callback(makeColumn("numen").addTildeAbove(), tehta);
}
} else if (character === "t") { // nt
return function (character) {
if (character === "h") { // nth
- return callback(makeColumn("thule").addTildeAbove());
+ return callback(makeColumn("thule").addTildeAbove(), tehta);
} else { // nt.
- return callback(makeColumn("tinco").addTildeAbove())(character);
+ return callback(makeColumn("tinco").addTildeAbove(), tehta)(character);
}
};
} else if (character === "d") { // nd
- return callback(makeColumn("ando").addTildeAbove());
+ return callback(makeColumn("ando").addTildeAbove(), tehta);
} else if (character === "c") { // nc -> ñc
- return callback(makeColumn("quesse").addTildeAbove());
+ return callback(makeColumn("quesse").addTildeAbove(), tehta);
} else if (character === "g") { // ng -> ñg
- return callback(makeColumn("ungwe").addTildeAbove());
+ return callback(makeColumn("ungwe").addTildeAbove(), tehta);
} else if (character === "j") { // nj
- return callback(makeColumn("anca").addTildeAbove());
+ return callback(makeColumn("anca").addTildeAbove(), tehta);
} else if (character === "f") { // nf -> nv
- return callback(makeColumn("numen"))("v");
+ return callback(makeColumn("numen"), tehta)("v");
} else if (character === "w") { // nw -> ñw
return function (character) {
if (character === "a") { // nwa
return function (character) { // nwal
if (character === "l") {
- return callback(makeColumn("nwalme").addAbove("w"))("a")(character);
+ return callback(makeColumn("nwalme").addAbove("w"), tehta)("a")(character);
} else { // nwa.
- return callback(makeColumn("numen").addAbove("w"))("a")(character);
+ return callback(makeColumn("numen").addAbove("w"), tehta)("a")(character);
}
};
} else if (character === "nw'") { // nw' prime -> ñw
- return callback(makeColumn("nwalme").addAbove("w"));
+ return callback(makeColumn("nwalme").addAbove("w"), tehta);
} else { // nw.
- return callback(makeColumn("numen").addAbove("w"))(character);
+ return callback(makeColumn("numen").addAbove("w"), tehta)(character);
}
};
} else { // n.
- return callback(makeColumn("numen"))(character);
+ return callback(makeColumn("numen"), tehta)(character);
}
};
} else if (character === "m") { // m
return function (character) {
if (character === "m") { // mm
if (options.doubleNasalsWithTildeBelow) {
- return callback(makeColumn("malta").addTildeBelow());
+ return callback(makeColumn("malta").addTildeBelow(), tehta);
} else {
- return callback(makeColumn("malta").addTildeAbove());
+ return callback(makeColumn("malta").addTildeAbove(), tehta);
}
} else if (character === "p") { // mp
// mph is simplified to mf using the normalizer
- return callback(makeColumn("parma").addTildeAbove());
+ return callback(makeColumn("parma").addTildeAbove(), tehta);
} else if (character === "b") { // mb
// mbh is simplified to mf using the normalizer
- return callback(makeColumn("umbar").addTildeAbove());
+ return callback(makeColumn("umbar").addTildeAbove(), tehta);
} else if (character === "f") { // mf
- return callback(makeColumn("formen").addTildeAbove());
+ return callback(makeColumn("formen").addTildeAbove(), tehta);
} else if (character === "v") { // mv
- return callback(makeColumn("ampa").addTildeAbove());
+ return callback(makeColumn("ampa").addTildeAbove(), tehta);
} else { // m.
- return callback(makeColumn("malta"))(character);
+ return callback(makeColumn("malta"), tehta)(character);
}
};
} else if (character === "ñ") { // ñ
// ññ does not exist to the best of my knowledge
// ñw is handled naturally by following w
if (character === "c") { // ñc
- return callback(makeColumn("quesse").addTildeAbove());
+ return callback(makeColumn("quesse").addTildeAbove(), tehta);
} else if (character === "g") { // ñg
- return callback(makeColumn("ungwe").addTildeAbove());
+ return callback(makeColumn("ungwe").addTildeAbove(), tehta);
} else { // ñ.
- return callback(makeColumn("nwalme"))(character);
+ return callback(makeColumn("nwalme"), tehta)(character);
}
};
} else if (character === "t") { // t
return function (character) {
if (character === "t") { // tt
- return callback(makeColumn("tinco").addTildeBelow());
+ return callback(makeColumn("tinco").addTildeBelow(), tehta);
} else if (character === "h") { // th
- return callback(makeColumn("thule"));
+ return callback(makeColumn("thule"), tehta);
} else if (character === "c") { // tc
return function (character) {
if (character === "h") { // tch -> tinco calma
- return callback(makeColumn("tinco"))("c")("h")("'");
+ return callback(makeColumn("tinco"), tehta)("c")("h")("'");
} else {
- return callback(makeColumn("tinco"))("c")(character);
+ return callback(makeColumn("tinco"), tehta)("c")(character);
}
};
} else if (character === "s" && options.tsdz) { // ts
- return callback(makeColumn("calma"));
+ return callback(makeColumn("calma"), tehta);
} else { // t.
- return callback(makeColumn("tinco"))(character);
+ return callback(makeColumn("tinco"), tehta)(character);
}
};
} else if (character === "p") { // p
return function (character) {
// ph is simplified to f by the normalizer
if (character === "p") { // pp
- return callback(makeColumn("parma").addTildeBelow());
+ return callback(makeColumn("parma").addTildeBelow(), tehta);
} else { // p.
- return callback(makeColumn("parma"))(character);
+ return callback(makeColumn("parma"), tehta)(character);
}
};
} else if (character === "c") { // c
// cw should be handled either by following-w or a subsequent
// vala
if (character === "c") { // ch as in charm
- return callback(makeColumn("calma"));
+ return callback(makeColumn("calma"), tehta);
} else if (character === "h") { // ch, ach-laut, as in bach
return Parser.countPrimes(function (primes) {
if (options.noAchLaut && !primes) {
- return callback(makeColumn("calma")); // ch as in charm
+ return callback(makeColumn("calma"), tehta); // ch as in charm
} else {
- return callback(makeColumn("hwesta")); // ch as in bach
+ return callback(makeColumn("hwesta"), tehta); // ch as in bach
}
});
} else { // c.
- return callback(makeColumn("quesse"))(character);
+ return callback(makeColumn("quesse"), tehta)(character);
}
};
} else if (character === "d") {
return function (character) {
if (character === "d") { // dd
- return callback(makeColumn("ando").addTildeBelow());
+ return callback(makeColumn("ando").addTildeBelow(), tehta);
} else if (character === "j") { // dj
- return callback(makeColumn("anga"));
+ return callback(makeColumn("anga"), tehta);
} else if (character === "z" && options.tsdz) { // dz
- return callback(makeColumn("anga"));
+ return callback(makeColumn("anga"), tehta);
} else if (character === "h") { // dh
- return callback(makeColumn("anto"));
+ return callback(makeColumn("anto"), tehta);
} else { // d.
- return callback(makeColumn("ando"))(character);
+ return callback(makeColumn("ando"), tehta)(character);
}
};
} else if (character === "b") { // b
return function (character) {
// bh is simplified to v by the normalizer
if (character === "b") { // bb
- return callback(makeColumn("umbar").addTildeBelow());
+ return callback(makeColumn("umbar").addTildeBelow(), tehta);
} else { // b.
- return callback(makeColumn("umbar"))(character);
+ return callback(makeColumn("umbar"), tehta)(character);
}
};
} else if (character === "g") { // g
return function (character) {
if (character === "g") { // gg
- return callback(makeColumn("ungwe").addTildeBelow());
+ return callback(makeColumn("ungwe").addTildeBelow(), tehta);
} else if (character === "h") { // gh
- if (options.blackSpeech) {
- return callback(makeColumn("ungwe-extended"));
+ if (options.language === "blackSpeech") {
+ return callback(makeColumn("ungwe-extended"), tehta);
} else {
- return callback(makeColumn("unque"));
+ return callback(makeColumn("unque"), tehta);
}
} else { // g.
- return callback(makeColumn("ungwe"))(character);
+ return callback(makeColumn("ungwe"), tehta)(character);
}
};
} else if (character === "f") { // f
return function (character) {
if (character === "f") { // ff
- return callback(makeColumn("formen").addTildeBelow());
+ return callback(makeColumn("formen").addTildeBelow(), tehta);
} else { // f.
- return callback(makeColumn("formen"))(character);
+ return callback(makeColumn("formen"), tehta)(character);
}
};
} else if (character === "v") { // v
- return callback(makeColumn("ampa"));
+ return callback(makeColumn("ampa"), tehta);
} else if (character === "j") { // j
- return callback(makeColumn("anca"));
+ return callback(makeColumn("anca"), tehta);
} else if (character === "s") { // s
return function (character) {
if (character === "s") { // ss
if (primes > 1) {
column.addError("Silme does not have this many alternate forms.");
}
- return callback(column);
+ return callback(column, tehta);
});
} else if (character === "h") { // sh
- if (options.blackSpeech) {
- return callback(makeColumn("calma-extended"));
+ if (options.language === "blackSpeech") {
+ return callback(makeColumn("calma-extended"), tehta);
} else {
- return callback(makeColumn("harma"));
+ return callback(makeColumn("harma"), tehta);
}
} else { // s.
return Parser.countPrimes(function (primes) {
if (primes > 1) {
column.addError("Silme does not have this many alternate forms.");
}
- return callback(column);
+ return callback(column, tehta);
})(character);
}
};
if (primes > 1) {
column.addError("Esse does not have this many alternate forms.");
}
- return callback(column);
+ return callback(column, tehta);
});
} else { // z.
return Parser.countPrimes(function (primes) {
if (primes > 1) {
column.addError("Silme does not have this many alternate forms.");
}
- return callback(column);
+ return callback(column, tehta);
})(character);
}
};
} else if (character === "h") { // h
return function (character) {
if (character === "w") { // hw
- return callback(makeColumn("hwesta-sindarinwa"));
+ return callback(makeColumn("hwesta-sindarinwa"), tehta);
} else { // h.
- return callback(makeColumn("hyarmen"))(character);
+ return callback(makeColumn("hyarmen"), tehta)(character);
}
};
} else if (character === "r") { // r
return function (character) {
if (character === "r") { // rr
- return callback(makeColumn("romen").addTildeBelow());
+ return callback(makeColumn("romen").addTildeBelow(), tehta);
} else if (character === "h") { // rh
- return callback(makeColumn("arda"));
+ return callback(makeColumn("arda"), tehta);
} else if (
Parser.isFinal(character) || (
options.medialOre &&
vowels.indexOf(character) === -1
)
) { // r final (optionally r before consonant)
- return callback(makeColumn("ore"))(character);
+ return callback(makeColumn("ore"), tehta)(character);
} else { // r.
- return callback(makeColumn("romen"))(character);
+ return callback(makeColumn("romen"), tehta)(character);
}
};
} else if (character === "l") {
return function (character) {
if (character === "l") { // ll
- return callback(makeColumn("lambe").addTildeBelow());
+ return callback(makeColumn("lambe").addTildeBelow(), tehta);
} else if (character === "h") { // lh
- return callback(makeColumn("alda"));
+ return callback(makeColumn("alda"), tehta);
} else { // l.
- return callback(makeColumn("lambe"))(character);
+ return callback(makeColumn("lambe"), tehta)(character);
}
};
} else if (character === "i") { // i
- return callback(makeColumn("anna"));
+ return callback(makeColumn("anna"), tehta);
} else if (character === "u") { // u
- return callback(makeColumn("vala"));
+ return callback(makeColumn("vala"), tehta);
} else if (character === "w") { // w
return function (character) {
if (character === "h") { // wh
- return callback(makeColumn("hwesta-sindarinwa"));
+ return callback(makeColumn("hwesta-sindarinwa"), tehta);
} else { // w.
- return callback(makeColumn("vala"))(character);
+ return callback(makeColumn("vala"), tehta)(character);
}
};
} else if (character === "e" && (!tehta || tehta === "a")) { // ae or e after consonants
- return callback(makeColumn("yanta"));
+ return callback(makeColumn("yanta"), tehta);
+ } else if (character === "ë") { // if "ë" makes it this far, it's a diaresis for english
+ return callback(makeColumn("short-carrier").addAbove("e"));
} else if (character === "y") {
- return callback(makeColumn("wilya").addBelow("y"));
- // TODO consider alt: return callback(makeColumn("long-carrier").addAbove("i"));
+ return Parser.countPrimes(function (primes) {
+ if (primes === 0) {
+ return callback(makeColumn("wilya").addBelow("y"), tehta);
+ } else if (primes === 1) {
+ return callback(makeColumn("long-carrier").addAbove("i"), tehta);
+ } else {
+ return callback(makeColumn("ure").addError("Consonantal Y only has one variation"));
+ }
+ });
} else if (shorterVowels[character]) {
- return callback(makeCarrier(character, options).addAbove(shorterVowels[character]));
+ return callback(makeCarrier(character, options).addAbove(shorterVowels[character]), tehta);
+ } else if (character === "'" && options.language === "english" && tehta === "e") {
+ // final e' in english should be equivalent to diaresis
+ return callback(makeColumn("short-carrier").addAbove("e"));
+ } else if (character === "" && options.language === "english" && tehta === "e") {
+ // tehta deliberately consumed in this one case, not passed forward
+ return callback(makeColumn("short-carrier").addBelow("i-below"))(character);
} else {
- return callback()(character);
+ return callback(null, tehta)(character);
}
};
}
exports.parseTengwaAnnotations = parseTengwaAnnotations;
-function parseTengwaAnnotations(callback, column) {
+function parseTengwaAnnotations(callback, column, length, options) {
return parseFollowingAbove(function (column) {
return parseFollowingBelow(function (column) {
return parseFollowing(callback, column);
- }, column);
+ }, column, length, options);
}, column);
}
}
}
-function parseFollowingBelow(callback, column) {
+function parseFollowingBelow(callback, column, length, options) {
return function (character) {
+ if (character === "ë" && options.language !== "english") {
+ character = "e";
+ }
if (character === "y" && column.canAddBelow("y")) {
return callback(column.addBelow("y"));
} else if (character === "e" && column.canAddBelow("i-below")) {
return Parser.countPrimes(function (primes) {
- if (primes === 0) {
- return callback(column)(character);
- } else {
- if (primes > 1) {
- column.addError("Following E has only one variation.");
+ return function (character) {
+ if (Parser.isFinal(character) && options.language === "english" && length > 2) {
+ if (primes === 0) {
+ return callback(column.addBelow("i-below"))(character);
+ } else {
+ if (primes > 1) {
+ column.addError("Following E has only one variation.");
+ }
+ return callback(column)("e")(character);
+ }
+ } else {
+ if (primes === 0) {
+ return callback(column)("e")(character);
+ } else {
+ if (primes > 1) {
+ column.addError("Following E has only one variation.");
+ }
+ return callback(column.addBelow("i-below"))(character);
+ }
}
- return callback(column.addBelow("i-below"));
- }
+ };
});
} else {
return callback(column)(character);
}
return state;
}
- return callback(column);
+ return callback(column)(character);
} else {
return rewind(callback(column)("s"))(character);
}
// the keys of this table are characters and clusters of characters that must
// be simplified to the corresponding values before pumping them into an
// adapted parser. The adapted parser therefore only needs to handle the
-// normal phoneitc form of the cluster.
+// normal phonetic form of the cluster.
var table = {
"k": "c",
"x": "cs",
"ph": "f",
"b": "b",
"bh": "v",
- "ë": "e",
"â": "á",
"ê": "é",
"î": "í",
if (tehta === "tilde-above") {
result.addTildeAbove();
} else if (tehta === "tilde-below") {
- result.addBarBelow();
+ result.addTildeBelow();
} else if (tehta === "y") {
result.addBelow("y");
} else if (
{
- "name": "tengwar",
- "version": "0.1.2",
- "homepage": "http://3rin.gs/tengwar",
- "author": "Kris Kowal <kris@cixar.com> (http://github.com/kriskowal/)",
- "bugs": {
- "mail": "kris@cixar.com",
- "web": "http://github.com/kriskowal/tengwarjs/issues"
+ "name": "tengwar",
+ "version": "0.1.2",
+ "homepage": "http://3rin.gs/tengwar",
+ "author": "Kris Kowal <kris@cixar.com> (http://github.com/kriskowal/)",
+ "bugs": {
+ "mail": "kris@cixar.com",
+ "url": "http://github.com/kriskowal/tengwarjs/issues"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://github.com/kriskowal/tengwarjs/raw/master/LICENSE"
},
- "licenses": [
- {
- "type": "MIT",
- "url": "http://github.com/kriskowal/tengwarjs/raw/master/LICENSE"
- },
- {
- "url": "http://github.com/kriskowal/tengwarjs/raw/master/tengwar-annatar/tngandoc.pdf"
- }
- ],
- "repository": {
- "type": "git",
- "url": "http://github.com/kriskowal/tengwarjs.git"
- },
- "devDependencies": {
- "jasmine-node": "1.0.x"
- },
- "scripts": {
- "test": "jasmine-node spec"
+ {
+ "url": "http://github.com/kriskowal/tengwarjs/raw/master/tengwar-annatar/tngandoc.pdf"
}
+ ],
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/kriskowal/tengwarjs.git"
+ },
+ "devDependencies": {
+ "jasmine-node": "~1"
+ },
+ "scripts": {
+ "test": "jasmine-node spec"
+ }
}
+++ /dev/null
-
-var GeneralUse = require("../general-use");
-var tests = require("./black-speech");
-
-describe("black speech", function () {
- Object.keys(tests).forEach(function (input) {
- it("should encode " + input, function () {
- expect(GeneralUse.encode(input, {
- blackSpeech: true
- })).toEqual(tests[input]);
- });
- });
-});
-
+++ /dev/null
-module.exports = {
- "ash": "calma-extended:a",
- "nazg": "numen;esse-nuquerna:a;ungwe",
- "durbatulûk": "ando;ore:o;umbar;tinco:a;lambe:o;quesse:ó", // swap o and u, medial ore before consonant
- "gimbatul": "ungwe;umbar:i,tilde-above;tinco:a;lambe:o",
- "thrakatulûk": "thule;romen;quesse:a;tinco:a;lambe:o;quesse:ó",
- "agh": "ungwe-extended:a",
- "burzumishi": "umbar;ore:o;esse;malta:o;calma-extended:i;short-carrier:i",
- "krimpatul": "quesse;romen;parma:i,tilde-above;tinco:a;lambe:o"
-};
var tests = require("./general-use");
describe("general use", function () {
- Object.keys(tests).forEach(function (input) {
- it("should encode " + input, function () {
- expect(GeneralUse.encode(input)).toEqual(tests[input]);
+ Object.keys(tests).forEach(function (language) {
+ var languageTests = tests[language];
+ describe(language, function () {
+ Object.keys(languageTests).forEach(function (input) {
+ it("should encode " + input, function () {
+ var expected = languageTests[input];
+ expect(GeneralUse.encode(input, {
+ language: language
+ })).toEqual(expected);
+ });
+ });
});
});
});
+
module.exports = {
- // sindarin (sorted)
- "ainur": "anna:a;numen;ore:u",
- "aldost": "lambe:a;ando;silme-nuquerna:o;tinco",
- "amon sûl": "malta:a;numen:o silme;lambe:ú",
- "aragorn": "romen:a;ungwe:a;romen:o;numen",
- "Aragorn Arathorn:\nTelcontar, Elessar": "romen:a;ungwe:a;romen:o;numen romen:a;thule:a;romen:o;numen;comma\ntinco;lambe:e;quesse;tinco:o,tilde-above;ore:a;comma lambe:e;silme-nuquerna:e,tilde-below;ore:a",
- "atto": "tinco:a,tilde-below;short-carrier:o",
- "baranduiniant": "umbar;romen:a;ando:a,tilde-above;anna:u;yanta;anto:a,tilde-above",
- "dagor bragolach": "ando;ungwe:a;ore:o umbar;romen;ungwe:a;lambe:o;hwesta:a",
- "galadhrim": "ungwe;lambe:a;anto:a;romen;malta:i",
- "galadriel": "ungwe;lambe:a;ando:a;romen;short-carrier:i;lambe:e",
- "gandalf": "ungwe;ando:a,tilde-above;lambe:a;formen",
- "glorfindel": "ungwe;lambe;romen:o;formen;ando:i,tilde-above;lambe:e",
- "gwaith iaur arnor": "ungwe:w;anna:a;thule yanta;vala:a;ore romen:a;numen;ore:o",
- "gwathló": "ungwe:w;thule:a;lambe;long-carrier:o",
- "hwesta sindarinwa": "hwesta-sindarinwa;silme-nuquerna:e;tinco;short-carrier:a silme;ando:i,tilde-above;romen:a;short-carrier:i;numen:w;short-carrier:a",
- "iant": "yanta;tinco:a,tilde-above",
- "iaur": "yanta;vala:a;ore",
- "isildur": "silme-nuquerna:i;lambe:i;ando;ore:u",
- "lhûn": "alda;numen:ú",
- "lothlórien": "lambe;thule:o;lambe;romen:ó;short-carrier:i;numen:e",
- "mae govannen": "malta;yanta:a ungwe;ampa:o;numen:a,tilde-above;numen:e",
- "mellon": "malta;lambe:e,tilde-below;numen:o",
- "mordor": "malta;romen:o;ando;ore:o",
- "moria": "malta;romen:o;short-carrier:i;short-carrier:a",
- "noldor": "nwalme;lambe:o;ando;ore:o",
- "periannath": "parma;romen:e;short-carrier:i;numen:a,tilde-above;thule:a",
- "rhûn": "arda;numen:ú",
- "tyelpe": "tinco:y;lambe:e;parma;short-carrier:e",
- "varda": "ampa;romen:a;ando;short-carrier:a",
- "á": "wilya:a",
- "ñoldor": "nwalme;lambe:o;ando;ore:o",
-
- // quenya (improper mode) (sorted)
- "ardalambion": "romen:a;ando;lambe:a;umbar:a,tilde-above;short-carrier:i;numen:o",
- "helcaraxë": "hyarmen;lambe:e;quesse;romen:a;quesse:a,s;short-carrier:e",
- "hyarmen": "hyarmen:y;romen:a;malta;numen:e",
- "istari": "silme-nuquerna:i;tinco;romen:a;short-carrier:i",
- "sinome maruvan": "silme;numen:i;malta:o;short-carrier:e malta;romen:a;ampa:u;numen:a",
- "telperion": "tinco;lambe:e;parma;romen:e;short-carrier:i;numen:o",
- // TODO alt? "yuldar": "long-carrier:i;lambe:u;ando;ore:a",
- "yuldar": "wilya:y;lambe:u;ando;ore:a",
-
- // english (appropriate mode) (sorted)
- "hobbits": "hyarmen;umbar:o,tilde-below;tinco:i,s-final",
- "hobbits'": "hyarmen;umbar:o,tilde-below;tinco:i,s-inverse",
- "hobbits''": "hyarmen;umbar:o,tilde-below;tinco:i,s-extended",
- "hobbits'''": "hyarmen;umbar:o,tilde-below;tinco:i,s-flourish",
-
- // old english
- "írensaga": "long-carrier:i;romen;numen:e;silme;ungwe:a;short-carrier:a",
-
- // interesting clusters
- "xx": "quesse:s;quesse:s",
- "tsts": "tinco;silme;tinco:s-final",
- "iqs": "quesse:i;vala;silme",
- "aty": "tinco:a,y",
- "allys": "lambe:a,y,s-final,tilde-below",
- "alyssa": "lambe:a,y;silme:tilde-below;short-carrier:a",
- "ls": "lambe:s-final",
- "ls'": "lambe:s-flourish",
-
- // long vowels
- "á": "wilya:a",
- "aa": "wilya:a",
- "é": "long-carrier:e",
- "ee": "long-carrier:e",
- "í": "long-carrier:i",
- "ii": "long-carrier:i",
- "ó": "long-carrier:o",
- "oo": "long-carrier:o",
- "ú": "long-carrier:u",
- "uu": "long-carrier:u"
+
+ any: {
+
+ // interesting clusters
+ "xx": "quesse:s;quesse:s",
+ "tsts": "tinco;silme;tinco:s-final",
+ "iqs": "quesse:i;vala;silme",
+ "aty": "tinco:a,y",
+ "allys": "lambe:a,y,s-final,tilde-below",
+ "alyssa": "lambe:a,y;silme:tilde-below;short-carrier:a",
+ "ls": "lambe:s-final",
+ "ls'": "lambe:s-flourish",
+
+ // long vowels
+ "á": "wilya:a",
+ "aa": "wilya:a",
+ "é": "long-carrier:e",
+ "ee": "long-carrier:e",
+ "í": "long-carrier:i",
+ "ii": "long-carrier:i",
+ "ó": "long-carrier:o",
+ "oo": "long-carrier:o",
+ "ú": "long-carrier:u",
+ "uu": "long-carrier:u",
+
+ // final-e modification for non-english
+ "cake'": "quesse;quesse:a,i-below",
+
+ },
+
+ english: {
+
+ // english (appropriate mode) (sorted)
+ "cake": "quesse;quesse:a,i-below",
+ "cakes": "quesse;quesse:a;silme-nuquerna:e",
+ "cats.": "quesse;tinco:a,s-final;full-stop", // regression
+ "hobbits": "hyarmen;umbar:o,tilde-below;tinco:i,s-final",
+ "hobbits'": "hyarmen;umbar:o,tilde-below;tinco:i,s-inverse",
+ "hobbits''": "hyarmen;umbar:o,tilde-below;tinco:i,s-extended",
+ "hobbits'''": "hyarmen;umbar:o,tilde-below;tinco:i,s-flourish",
+ "there": "thule;romen:e,i-below",
+ "these": "thule;silme-nuquerna:e;short-carrier:i-below",
+ "these'": "thule;silme-nuquerna:e;short-carrier:e",
+ "finwë": "formen;short-carrier:i;numen:w;short-carrier:e",
+ "finwe": "formen;short-carrier:i;numen:w,i-below", // invalid input
+ "helcaraxë": "hyarmen;lambe:e;quesse;romen:a;quesse:a,s;short-carrier:e",
+ "helcaraxe": "hyarmen;lambe:e;quesse;romen:a;quesse:a,s;short-carrier:i-below", // invalid input
+
+ // abbreviated words
+ "of": "umbar-extended",
+ "the": "ando-extended",
+ "of the": "umbar-extended:tilde-below",
+ "of'the": "umbar-extended ando-extended",
+ "and": "ando:tilde-above",
+ "and'": "ando:i-below,tilde-above"
+
+ },
+
+ oldEnglish: {
+ "írensaga": "long-carrier:i;romen;numen:e;silme;ungwe:a;short-carrier:a"
+ },
+
+ sindarin: {
+ // (sorted)
+ "ainur": "anna:a;numen;ore:u",
+ "aldost": "lambe:a;ando;silme-nuquerna:o;tinco",
+ "amon sûl": "malta:a;numen:o silme;lambe:ú",
+ "aragorn": "romen:a;ungwe:a;romen:o;numen",
+ "Aragorn Arathorn:\nTelcontar, Elessar": "romen:a;ungwe:a;romen:o;numen romen:a;thule:a;romen:o;numen;comma\ntinco;lambe:e;quesse;tinco:o,tilde-above;ore:a;comma lambe:e;silme-nuquerna:e,tilde-below;ore:a",
+ "atto": "tinco:a,tilde-below;short-carrier:o",
+ "baranduiniant": "umbar;romen:a;ando:a,tilde-above;anna:u;yanta;anto:a,tilde-above",
+ "dagor bragolach": "ando;ungwe:a;ore:o umbar;romen;ungwe:a;lambe:o;hwesta:a",
+ "galadhrim": "ungwe;lambe:a;anto:a;romen;malta:i",
+ "galadriel": "ungwe;lambe:a;ando:a;romen;short-carrier:i;lambe:e",
+ "gandalf": "ungwe;ando:a,tilde-above;lambe:a;formen",
+ "glorfindel": "ungwe;lambe;romen:o;formen;ando:i,tilde-above;lambe:e",
+ "gwaith iaur arnor": "ungwe:w;anna:a;thule yanta;vala:a;ore romen:a;numen;ore:o",
+ "gwathló": "ungwe:w;thule:a;lambe;long-carrier:o",
+ "hwesta sindarinwa": "hwesta-sindarinwa;silme-nuquerna:e;tinco;short-carrier:a silme;ando:i,tilde-above;romen:a;short-carrier:i;numen:w;short-carrier:a",
+ "iant": "yanta;tinco:a,tilde-above",
+ "iaur": "yanta;vala:a;ore",
+ "isildur": "silme-nuquerna:i;lambe:i;ando;ore:u",
+ "lhûn": "alda;numen:ú",
+ "lothlórien": "lambe;thule:o;lambe;romen:ó;short-carrier:i;numen:e",
+ "mae govannen": "malta;yanta:a ungwe;ampa:o;numen:a,tilde-above;numen:e",
+ "mellon": "malta;lambe:e,tilde-below;numen:o",
+ "mordor": "malta;romen:o;ando;ore:o",
+ "moria": "malta;romen:o;short-carrier:i;short-carrier:a",
+ "noldor": "nwalme;lambe:o;ando;ore:o",
+ "periannath": "parma;romen:e;short-carrier:i;numen:a,tilde-above;thule:a",
+ "rhûn": "arda;numen:ú",
+ "tyelpe": "tinco:y;lambe:e;parma;short-carrier:e",
+ "varda": "ampa;romen:a;ando;short-carrier:a",
+ "á": "wilya:a",
+ "ñoldor": "nwalme;lambe:o;ando;ore:o",
+ },
+
+ blackSpeech: {
+ // in order of appearance in the ring poem
+ "ash": "calma-extended:a",
+ "nazg": "numen;esse-nuquerna:a;ungwe",
+ "durbatulûk": "ando;ore:o;umbar;tinco:a;lambe:o;quesse:ó", // swap o and u, medial ore before consonant
+ "gimbatul": "ungwe;umbar:i,tilde-above;tinco:a;lambe:o",
+ "thrakatulûk": "thule;romen;quesse:a;tinco:a;lambe:o;quesse:ó",
+ "agh": "ungwe-extended:a",
+ "burzumishi": "umbar;ore:o;esse;malta:o;calma-extended:i;short-carrier:i",
+ "krimpatul": "quesse;romen;parma:i,tilde-above;tinco:a;lambe:o"
+ },
+
+ quenya: {
+ // (improper mode) (sorted)
+ "ardalambion": "romen:a;ando;lambe:a;umbar:a,tilde-above;short-carrier:i;numen:o",
+ "ëa": "short-carrier:e;short-carrier:a",
+ "helcaraxë": "hyarmen;lambe:e;quesse;romen:a;quesse:a,s;short-carrier:e",
+ "hyarmen": "hyarmen:y;romen:a;malta;numen:e",
+ "istari": "silme-nuquerna:i;tinco;romen:a;short-carrier:i",
+ "sinome maruvan": "silme;numen:i;malta:o;short-carrier:e malta;romen:a;ampa:u;numen:a",
+ "telperion": "tinco;lambe:e;parma;romen:e;short-carrier:i;numen:o",
+ "yuldar": "wilya:y;lambe:u;ando;ore:a",
+ "y'uldar": "long-carrier:i;lambe:u;ando;ore:a"
+ }
};
+