@heroiclands/package-build 23.12.5 → 23.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/assets/styles/content-base.css +25 -0
- package/bin/content-commands.mjs +264 -3
- package/bin/package-build.mjs +25 -0
- package/content-config.mjs +98 -0
- package/docs/api.md +18 -14
- package/docs/authoring/dates-and-calendars.md +29 -1
- package/docs/authoring/links-and-markup.md +442 -50
- package/docs/commands.md +127 -0
- package/docs/configuration.md +90 -3
- package/docs/content-format.md +3 -1
- package/docs/guides/book.md +11 -0
- package/docs/guides/module-project.md +17 -1
- package/docs/guides/site.md +19 -0
- package/docs/reference/format-details.md +434 -23
- package/docs/reference/note-types.md +45 -21
- package/engine/base-compiler.mjs +2 -0
- package/engine/bundles.mjs +7 -2
- package/engine/compile-corpus.mjs +3 -1
- package/engine/content-charset.mjs +21 -2
- package/engine/content-format.yaml +1 -2
- package/engine/content-index.mjs +33 -2
- package/engine/content-links.mjs +3 -0
- package/engine/content-poetry.mjs +22 -4
- package/engine/content-scripts.mjs +1219 -0
- package/engine/content-session.mjs +2 -0
- package/engine/data-keys.mjs +5 -1
- package/engine/dictionary-layout.mjs +18 -12
- package/engine/dictionary-rows.mjs +11 -2
- package/engine/event-rows.mjs +63 -18
- package/engine/event-views.mjs +28 -15
- package/engine/helpers.mjs +8 -2
- package/engine/index-records.mjs +2 -0
- package/engine/infobox.mjs +4 -8
- package/engine/journals.mjs +14 -3
- package/engine/language-data.mjs +137 -1
- package/engine/language-tag.mjs +103 -0
- package/engine/language-terms.mjs +16 -4
- package/engine/map-lexicon.mjs +762 -0
- package/engine/markdown-expressions.mjs +72 -17
- package/engine/markup-validation.mjs +17 -3
- package/engine/names.mjs +1 -1
- package/engine/note-calendars.mjs +314 -0
- package/engine/note-vocabulary.mjs +124 -16
- package/engine/pdf-build.mjs +28 -10
- package/engine/pdf-render.mjs +116 -11
- package/engine/reckoning-markers.mjs +63 -3
- package/engine/run-rows.mjs +119 -0
- package/engine/run-words.mjs +266 -0
- package/engine/scenes.mjs +1 -0
- package/engine/script-font-delivery.mjs +321 -0
- package/engine/script-fonts.mjs +172 -0
- package/engine/site-build.mjs +10 -3
- package/engine/site-config.mjs +5 -0
- package/engine/site-root.mjs +19 -5
- package/engine/sql-tables.mjs +120 -4
- package/engine/text-maps.mjs +607 -0
- package/engine/verb-pattern.mjs +99 -0
- package/engine/word-pattern.mjs +27 -0
- package/engine/word-rows.mjs +8 -2
- package/engine/word-terms.mjs +44 -0
- package/hugo-theme/layouts/_default/baseof.html +3 -0
- package/hugo-theme/static/css/style.css +10 -0
- package/manifest.mjs +9 -1
- package/package.json +1 -1
- package/types/content-config.d.mts +38 -0
- package/types/engine/compile-corpus.d.mts +2 -1
- package/types/engine/content-index.d.mts +17 -3
- package/types/engine/content-poetry.d.mts +7 -0
- package/types/engine/content-scripts.d.mts +226 -0
- package/types/engine/data-keys.d.mts +2 -2
- package/types/engine/dictionary-rows.d.mts +15 -0
- package/types/engine/event-rows.d.mts +16 -1
- package/types/engine/event-views.d.mts +2 -1
- package/types/engine/helpers.d.mts +5 -1
- package/types/engine/journals.d.mts +7 -2
- package/types/engine/language-data.d.mts +2 -0
- package/types/engine/language-tag.d.mts +8 -0
- package/types/engine/language-terms.d.mts +2 -1
- package/types/engine/map-lexicon.d.mts +274 -0
- package/types/engine/markup-validation.d.mts +7 -2
- package/types/engine/note-calendars.d.mts +60 -0
- package/types/engine/pdf-build.d.mts +26 -3
- package/types/engine/pdf-render.d.mts +17 -2
- package/types/engine/reckoning-markers.d.mts +32 -1
- package/types/engine/run-rows.d.mts +89 -0
- package/types/engine/run-words.d.mts +86 -0
- package/types/engine/script-font-delivery.d.mts +134 -0
- package/types/engine/script-fonts.d.mts +168 -0
- package/types/engine/site-build.d.mts +2 -1
- package/types/engine/site-root.d.mts +11 -3
- package/types/engine/text-maps.d.mts +175 -0
- package/types/engine/verb-pattern.d.mts +65 -0
- package/types/engine/word-pattern.d.mts +13 -0
- package/types/engine/word-terms.d.mts +6 -0
package/engine/language-data.mjs
CHANGED
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
* type;
|
|
35
35
|
* - every language a cognate's `forms` names is a language note;
|
|
36
36
|
* - a register row states a `tongue` or a `kind`;
|
|
37
|
+
* - a register row tagged `other:<tongue>` or `exonym:<tongue>` names a name
|
|
38
|
+
* the tongue's own language note registers under that tongue, case folded
|
|
39
|
+
* and NFC (see {@link foreignRowFindings});
|
|
37
40
|
* - a sound-change set's name stands once in the note, a step's label once
|
|
38
41
|
* in its set, and its `from` and `to` name language notes;
|
|
39
42
|
* - a frame's classes are bases of the master word classes;
|
|
@@ -52,7 +55,7 @@
|
|
|
52
55
|
|
|
53
56
|
import { pathLabel as label } from "./data-keys.mjs";
|
|
54
57
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
55
|
-
import { addressKeyOf } from "./culture-reach.mjs";
|
|
58
|
+
import { addressKeyOf, isLanguageNote } from "./culture-reach.mjs";
|
|
56
59
|
import { AddressEntries } from "./address-values.mjs";
|
|
57
60
|
import { isAddressTuple, renderAddress } from "./address.mjs";
|
|
58
61
|
import {
|
|
@@ -111,6 +114,119 @@ export function tongueDeclared(tag, declared) {
|
|
|
111
114
|
: declared.has(`${match[1]}:${TONGUE_PLACEHOLDER}`);
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
/** The tag families whose rows name a name of another tongue's register. */
|
|
118
|
+
const FOREIGN_FAMILIES = new Set(["other", "exonym"]);
|
|
119
|
+
|
|
120
|
+
/** How a foreign row that its tongue's register does not bear out is reported. */
|
|
121
|
+
export const FOREIGN_ROW_SEVERITY = "warning";
|
|
122
|
+
|
|
123
|
+
/** A name as a foreign row is compared: NFC and lowercase. */
|
|
124
|
+
const foldName = (name) => name.normalize("NFC").toLowerCase();
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Each tag a language note of the index declares with a `language`, to the
|
|
128
|
+
* canonical Addresses of the language notes it names, keyed once per index.
|
|
129
|
+
*
|
|
130
|
+
* @type {WeakMap<object, Map<string, Set<string>>>}
|
|
131
|
+
*/
|
|
132
|
+
const tongueLanguagesByIndex = new WeakMap();
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The language notes each tag names: every `data.tongues` entry of the index's
|
|
136
|
+
* own language notes whose `tag` is a plain tag and that states a `language`.
|
|
137
|
+
*
|
|
138
|
+
* @param {object} index - The link index.
|
|
139
|
+
* @returns {Map<string, Set<string>>} Canonical Addresses by tag.
|
|
140
|
+
*/
|
|
141
|
+
function tongueLanguages(index) {
|
|
142
|
+
const known = tongueLanguagesByIndex.get(index);
|
|
143
|
+
if (known) return known;
|
|
144
|
+
const out = new Map();
|
|
145
|
+
for (const note of index.notes ?? []) {
|
|
146
|
+
if (!isLanguageNote(note.fm)) continue;
|
|
147
|
+
for (const tongue of listOf(note.fm?.data?.tongues)) {
|
|
148
|
+
if (!isMap(tongue) || typeof tongue.tag !== "string" || tongue.tag.includes(":"))
|
|
149
|
+
continue;
|
|
150
|
+
const key = addressKeyOf(tongue.language, index, "skill");
|
|
151
|
+
if (!key) continue;
|
|
152
|
+
if (!out.has(tongue.tag)) out.set(tongue.tag, new Set());
|
|
153
|
+
out.get(tongue.tag).add(key);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
tongueLanguagesByIndex.set(index, out);
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The findings on a register's rows tagged `other:<tongue>` or
|
|
162
|
+
* `exonym:<tongue>`, each checked against the register of the tongue's own
|
|
163
|
+
* language note.
|
|
164
|
+
*
|
|
165
|
+
* A tongue names the language note a `data.tongues` entry of one of the
|
|
166
|
+
* index's language notes tags it with, through its `language`: `other:helonic`
|
|
167
|
+
* reads the note that `{tag: helonic, language: skill-helonclng}` names. The
|
|
168
|
+
* row passes when that note's `data.register` holds a row of the same name
|
|
169
|
+
* whose `tongue` is `helonic`. A tongue no entry links to a language note, or
|
|
170
|
+
* one linked to two, is a finding at the row's `tongue`; a language note of
|
|
171
|
+
* another package is not read, since its register does not reach this index.
|
|
172
|
+
* Bare `other`, and every other tag, are not checked.
|
|
173
|
+
*
|
|
174
|
+
* @param {Array<{row: object, i: number}>} rows - The register rows whose tag
|
|
175
|
+
* `data.tongues` declares.
|
|
176
|
+
* @param {object|undefined} index - The link index.
|
|
177
|
+
* @param {(path: Array<string|number>, severity: string, message: string) => object} finding -
|
|
178
|
+
* Builds a finding at a frontmatter path.
|
|
179
|
+
* @returns {object[]} The findings.
|
|
180
|
+
*/
|
|
181
|
+
function foreignRowFindings(rows, index, finding) {
|
|
182
|
+
if (!index?.addressHit) return [];
|
|
183
|
+
const findings = [];
|
|
184
|
+
for (const { row, i } of rows) {
|
|
185
|
+
const match = USED_TAG_PATTERN.exec(row.tongue);
|
|
186
|
+
if (!match || match[2] === undefined || !FOREIGN_FAMILIES.has(match[1])) continue;
|
|
187
|
+
const tongue = match[2];
|
|
188
|
+
const languages = [...(tongueLanguages(index).get(tongue) ?? [])];
|
|
189
|
+
if (languages.length !== 1) {
|
|
190
|
+
findings.push(
|
|
191
|
+
finding(
|
|
192
|
+
["data", "register", i, "tongue"],
|
|
193
|
+
FOREIGN_ROW_SEVERITY,
|
|
194
|
+
languages.length ?
|
|
195
|
+
`the tongue \`${tongue}\` in \`${row.tongue}\` names ${languages.length} ` +
|
|
196
|
+
`language notes, ${languages.join(" and ")}; the \`data.tongues\` entries ` +
|
|
197
|
+
`tagged \`${tongue}\` name one`
|
|
198
|
+
: `the tongue \`${tongue}\` in \`${row.tongue}\` names no language note: no ` +
|
|
199
|
+
`language note's \`data.tongues\` tags \`${tongue}\` with a \`language\``,
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const target = index.addressHit(languages[0]);
|
|
205
|
+
const fm = target?.fm;
|
|
206
|
+
if (!fm || !isLanguageNote(fm)) continue;
|
|
207
|
+
const name = foldName(row.name);
|
|
208
|
+
const listed = listOf(fm.data?.register).some(
|
|
209
|
+
(other) =>
|
|
210
|
+
isMap(other) &&
|
|
211
|
+
other.tongue === tongue &&
|
|
212
|
+
typeof other.name === "string" &&
|
|
213
|
+
foldName(other.name) === name,
|
|
214
|
+
);
|
|
215
|
+
if (listed) continue;
|
|
216
|
+
const title = typeof fm.name?.full === "string" ? fm.name.full : fm.name;
|
|
217
|
+
findings.push(
|
|
218
|
+
finding(
|
|
219
|
+
["data", "register", i, "name"],
|
|
220
|
+
FOREIGN_ROW_SEVERITY,
|
|
221
|
+
`\`${row.name}\` is registered as \`${row.tongue}\`, but the register of ` +
|
|
222
|
+
`${title} (${fm.type}-${fm.shortcode}) lists no \`${tongue}\` name ` +
|
|
223
|
+
`\`${row.name}\`; correct the spelling, or register the name there first`,
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return findings;
|
|
228
|
+
}
|
|
229
|
+
|
|
114
230
|
/**
|
|
115
231
|
* The entries of a cognate's `forms`, whatever form it reached the check in:
|
|
116
232
|
* `[written key, value, Address]`.
|
|
@@ -192,6 +308,22 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
192
308
|
"`data.grammar.verbs` must state exactly one of `invariant: true` or `persons`",
|
|
193
309
|
),
|
|
194
310
|
);
|
|
311
|
+
else if (invariant && Object.hasOwn(verbs, "replaces"))
|
|
312
|
+
findings.push(
|
|
313
|
+
finding(
|
|
314
|
+
["data", "grammar", "verbs", "replaces"],
|
|
315
|
+
"error",
|
|
316
|
+
"`data.grammar.verbs.replaces` states what a `persons` ending replaces, and an invariant pattern has none",
|
|
317
|
+
),
|
|
318
|
+
);
|
|
319
|
+
else if (typeof verbs.replaces === "string" && !verbs.replaces.trim())
|
|
320
|
+
findings.push(
|
|
321
|
+
finding(
|
|
322
|
+
["data", "grammar", "verbs", "replaces"],
|
|
323
|
+
"error",
|
|
324
|
+
"`data.grammar.verbs.replaces` must name the letters an ending replaces; leave it out where endings are added",
|
|
325
|
+
),
|
|
326
|
+
);
|
|
195
327
|
else if (invariant && (verbs.invariant == null || verbs.invariant === false))
|
|
196
328
|
findings.push(
|
|
197
329
|
finding(
|
|
@@ -319,6 +451,7 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
319
451
|
);
|
|
320
452
|
});
|
|
321
453
|
};
|
|
454
|
+
const foreignRows = [];
|
|
322
455
|
listOf(data.register).forEach((row, i) => {
|
|
323
456
|
if (!isMap(row)) return;
|
|
324
457
|
if (row.tongue == null && row.kind == null)
|
|
@@ -331,7 +464,10 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
331
464
|
);
|
|
332
465
|
if (typeof row.tongue !== "string") return;
|
|
333
466
|
undeclared([row.tongue], () => ["data", "register", i, "tongue"]);
|
|
467
|
+
if (typeof row.name === "string" && tongueDeclared(row.tongue, declared))
|
|
468
|
+
foreignRows.push({ row, i });
|
|
334
469
|
});
|
|
470
|
+
findings.push(...foreignRowFindings(foreignRows, index, finding));
|
|
335
471
|
|
|
336
472
|
// ── Sounds ───────────────────────────────────────────────────────────────
|
|
337
473
|
const sounds = isMap(data.sounds) ? data.sounds : {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **BCP 47 language tags** (RFC 5646), checked without the IANA registry.
|
|
16
|
+
*
|
|
17
|
+
* A tag is accepted when it is well-formed by the RFC's grammar and meets the
|
|
18
|
+
* two validity rules that need no registry: no variant subtag appears twice,
|
|
19
|
+
* and no extension singleton appears twice. Matching is case-insensitive, as
|
|
20
|
+
* the RFC requires. Whether each subtag is registered is not checked.
|
|
21
|
+
*
|
|
22
|
+
* A constructed language takes `art-x-<name>`: `art` is the registered
|
|
23
|
+
* collective code for artificial languages, and the private-use part names
|
|
24
|
+
* which one.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const ALPHANUM = "[a-z0-9]";
|
|
30
|
+
|
|
31
|
+
/** What separates a tag's subtags: RFC 5646's own separator, not an Address's. */
|
|
32
|
+
const SUBTAG_SEPARATOR = "-";
|
|
33
|
+
|
|
34
|
+
/** `langtag`, with each part captured so the validity rules can read them. */
|
|
35
|
+
const LANGTAG = new RegExp(
|
|
36
|
+
"^" +
|
|
37
|
+
// language: 2–3 letters with up to three extlangs, 4 letters, or 5–8 letters
|
|
38
|
+
"(?:[a-z]{2,3}(?:-[a-z]{3}){0,3}|[a-z]{4}|[a-z]{5,8})" +
|
|
39
|
+
// script
|
|
40
|
+
"(?:-[a-z]{4})?" +
|
|
41
|
+
// region
|
|
42
|
+
"(?:-(?:[a-z]{2}|[0-9]{3}))?" +
|
|
43
|
+
// variants
|
|
44
|
+
`((?:-(?:${ALPHANUM}{5,8}|[0-9]${ALPHANUM}{3}))*)` +
|
|
45
|
+
// extensions: a singleton other than x, then subtags of 2–8
|
|
46
|
+
`((?:-[0-9a-wy-z](?:-${ALPHANUM}{2,8})+)*)` +
|
|
47
|
+
// private use
|
|
48
|
+
`(?:-x(?:-${ALPHANUM}{1,8})+)?` +
|
|
49
|
+
"$",
|
|
50
|
+
"i",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
/** A tag that is private use alone. */
|
|
54
|
+
const PRIVATE_USE = new RegExp(`^x(?:-${ALPHANUM}{1,8})+$`, "i");
|
|
55
|
+
|
|
56
|
+
/** RFC 5646's grandfathered tags, which the grammar does not otherwise admit. */
|
|
57
|
+
const GRANDFATHERED = new Set([
|
|
58
|
+
"en-gb-oed",
|
|
59
|
+
"i-ami",
|
|
60
|
+
"i-bnn",
|
|
61
|
+
"i-default",
|
|
62
|
+
"i-enochian",
|
|
63
|
+
"i-hak",
|
|
64
|
+
"i-klingon",
|
|
65
|
+
"i-lux",
|
|
66
|
+
"i-mingo",
|
|
67
|
+
"i-navajo",
|
|
68
|
+
"i-pwn",
|
|
69
|
+
"i-tao",
|
|
70
|
+
"i-tay",
|
|
71
|
+
"i-tsu",
|
|
72
|
+
"sgn-be-fr",
|
|
73
|
+
"sgn-be-nl",
|
|
74
|
+
"sgn-ch-de",
|
|
75
|
+
"art-lojban",
|
|
76
|
+
"cel-gaulish",
|
|
77
|
+
"no-bok",
|
|
78
|
+
"no-nyn",
|
|
79
|
+
"zh-guoyu",
|
|
80
|
+
"zh-hakka",
|
|
81
|
+
"zh-min",
|
|
82
|
+
"zh-min-nan",
|
|
83
|
+
"zh-xiang",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether a value is a BCP 47 language tag.
|
|
88
|
+
*
|
|
89
|
+
* @param {unknown} value - The value to test.
|
|
90
|
+
* @returns {boolean} Whether it is a well-formed tag with no repeated variant
|
|
91
|
+
* or extension singleton.
|
|
92
|
+
*/
|
|
93
|
+
export function isLanguageTag(value) {
|
|
94
|
+
if (typeof value !== "string" || value === "") return false;
|
|
95
|
+
const tag = value.toLowerCase();
|
|
96
|
+
if (GRANDFATHERED.has(tag) || PRIVATE_USE.test(tag)) return true;
|
|
97
|
+
const match = LANGTAG.exec(tag);
|
|
98
|
+
if (!match) return false;
|
|
99
|
+
const variants = match[1].split(SUBTAG_SEPARATOR).filter(Boolean);
|
|
100
|
+
if (new Set(variants).size !== variants.length) return false;
|
|
101
|
+
const singletons = match[2].split(SUBTAG_SEPARATOR).filter((part) => part.length === 1);
|
|
102
|
+
return new Set(singletons).size === singletons.length;
|
|
103
|
+
}
|
|
@@ -1597,7 +1597,8 @@ function listOfMaps(name, fields, describe) {
|
|
|
1597
1597
|
|
|
1598
1598
|
/**
|
|
1599
1599
|
* The six present persons of the one optional regular verb pattern, in
|
|
1600
|
-
* presentation order.
|
|
1600
|
+
* presentation order. Each ending is added to the citation form, or takes the
|
|
1601
|
+
* place of its closing `replaces`; an empty ending adds nothing.
|
|
1601
1602
|
*
|
|
1602
1603
|
* @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
|
|
1603
1604
|
*/
|
|
@@ -1617,7 +1618,7 @@ export const VERB_PERSON_FIELDS = Object.freeze(
|
|
|
1617
1618
|
required: true,
|
|
1618
1619
|
allowEmpty: true,
|
|
1619
1620
|
shape: "an ending string, empty for unchanged",
|
|
1620
|
-
describe: `The ending added to the citation form
|
|
1621
|
+
describe: `The ending for ${label}, added to the citation form or in place of its closing \`replaces\`.`,
|
|
1621
1622
|
}),
|
|
1622
1623
|
),
|
|
1623
1624
|
);
|
|
@@ -1635,7 +1636,18 @@ const VERB_PATTERN_FIELDS = Object.freeze([
|
|
|
1635
1636
|
kind: "map",
|
|
1636
1637
|
shape: "a map — `{ I, you, heShe, we, youAll, they }` of ending strings",
|
|
1637
1638
|
fields: VERB_PERSON_FIELDS,
|
|
1638
|
-
describe:
|
|
1639
|
+
describe:
|
|
1640
|
+
"All six present endings, each added to the citation form or in place of its " +
|
|
1641
|
+
"closing `replaces`; excludes `invariant`.",
|
|
1642
|
+
}),
|
|
1643
|
+
Object.freeze({
|
|
1644
|
+
name: "replaces",
|
|
1645
|
+
kind: "string",
|
|
1646
|
+
shape: "the closing letters every ending takes the place of",
|
|
1647
|
+
describe:
|
|
1648
|
+
"Letters a citation form closes on, swapped for each `persons` ending: with `then`, " +
|
|
1649
|
+
"`lokathen` and `zor` give `lokazor`. A citation form closing otherwise takes the " +
|
|
1650
|
+
"ending whole. With `persons` only.",
|
|
1639
1651
|
}),
|
|
1640
1652
|
]);
|
|
1641
1653
|
|
|
@@ -1664,7 +1676,7 @@ const GRAMMAR_FIELDS = Object.freeze([
|
|
|
1664
1676
|
Object.freeze({
|
|
1665
1677
|
name: "verbs",
|
|
1666
1678
|
kind: "map",
|
|
1667
|
-
shape: "a map — `{ invariant: true }` or `{ persons: { I, you, heShe, we, youAll, they } }`",
|
|
1679
|
+
shape: "a map — `{ invariant: true }` or `{ persons: { I, you, heShe, we, youAll, they }, replaces? }`",
|
|
1668
1680
|
fields: VERB_PATTERN_FIELDS,
|
|
1669
1681
|
describe: "One optional regular present pattern, printed once at the dictionary head.",
|
|
1670
1682
|
}),
|