morphex 0.0.0-stage → 1.0.0
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/BENCHMARKS.md +95 -0
- package/LICENSE +25 -0
- package/NOTICE +23 -0
- package/README.md +197 -2
- package/dist/full.cjs +1798 -0
- package/dist/full.d.cts +12 -0
- package/dist/full.d.ts +12 -0
- package/dist/full.js +1780 -0
- package/dist/index.cjs +1784 -0
- package/dist/index.d.cts +257 -0
- package/dist/index.d.ts +257 -0
- package/dist/index.js +1767 -0
- package/dist/rules.cjs +1784 -0
- package/dist/rules.js +1767 -0
- package/package.json +101 -4
package/BENCHMARKS.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Benchmarks and bundle sizes
|
|
2
|
+
|
|
3
|
+
These are **Node.js/V8 micro-benchmarks** for the TypeScript port. They are measured with a
|
|
4
|
+
different runtime and harness from the Java JMH results on the main site and are not comparable
|
|
5
|
+
with them. Nothing here is a cross-language speed claim.
|
|
6
|
+
|
|
7
|
+
## Method
|
|
8
|
+
|
|
9
|
+
- Harness: `morphex-ts/scripts/bench.ts` (`npm run bench`), no external benchmark dependency.
|
|
10
|
+
- Machine used for the recorded run: 12th Gen Intel Core i5-12500H, Windows 11 (10.0.26300).
|
|
11
|
+
- Runtime: Node.js 24.20.0 (the package targets Node 18+).
|
|
12
|
+
- Each workload warms up for one full pass, then takes seven timed samples and reports the
|
|
13
|
+
median. The first timed sample is discarded while V8 finishes optimizing.
|
|
14
|
+
- Repeated workloads run 1,000,000 calls. Distinct-word workloads run ten passes over 4,000
|
|
15
|
+
unique lemmas (40,000 calls per timed sample) taken from the committed P1 fixture.
|
|
16
|
+
- Five runs were recorded on the same machine to show the spread; ranges below are the
|
|
17
|
+
minimum and maximum medians, not a confidence interval.
|
|
18
|
+
- Provider construction was measured separately with a tsx harness on the same machine: one
|
|
19
|
+
warm-up, then twelve samples per run, with the median of each of two runs reported.
|
|
20
|
+
|
|
21
|
+
Reproduce with:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd morphex-ts
|
|
25
|
+
npm install
|
|
26
|
+
npm run build:data # writes the generated lexicons and dist/sizes.json
|
|
27
|
+
npm run bench # writes bench-results.json and prints the table
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Latency (ns/op, lower is better)
|
|
31
|
+
|
|
32
|
+
| Workload | Delta engine | Full engine | Rules-only |
|
|
33
|
+
| --- | ---: | ---: | ---: |
|
|
34
|
+
| Repeated lexical word (`child`) | 111–119 | 269–280 | 130–139 |
|
|
35
|
+
| Repeated rule word (`cat`) | 112–118 | 174–188 | 104–116 |
|
|
36
|
+
| Repeated compound (`mother-in-law`) | 258–289 | 434–457 | 358–378 |
|
|
37
|
+
| Distinct fixture lemmas (4,000 words) | 354–432 | 1,006–1,057 | 327–355 |
|
|
38
|
+
| `analyze()` repeated (`die`) | 258–278 | 421–441 | 221–228 |
|
|
39
|
+
|
|
40
|
+
Reading the table:
|
|
41
|
+
|
|
42
|
+
- The **delta** engine answers `pluralize()` from the shipped artifact. At load time it parses
|
|
43
|
+
the four artifact sections and inserts them directly into an open-addressed hash index over
|
|
44
|
+
the 124,883 keys, so a rule-only word such as `cat` costs one hash and a short probe before
|
|
45
|
+
the rules run. The original four-section implementation measured 673–678 ns; a merged-sort
|
|
46
|
+
binary search measured 286–293 ns; the hash index measures 112–118 ns. That is about 5.7–6.0×
|
|
47
|
+
faster than the original and 2.4–2.6× faster than the merged binary search, in the same
|
|
48
|
+
harness.
|
|
49
|
+
- The **full** engine is opt-in (`morphex/full`) and adds alternatives, ambiguity and
|
|
50
|
+
countability. It performs one binary search over its own 147,338-key array, so it is now slower
|
|
51
|
+
than the delta engine on these paths (182–189 ns for `cat`), but it carries the metadata the
|
|
52
|
+
diagnostics need.
|
|
53
|
+
- The **rules-only** engine (`morphex/rules`, or `withoutBundledLexicon()`) uses no data files and
|
|
54
|
+
loses irregular forms by design.
|
|
55
|
+
|
|
56
|
+
The hash index costs 1 MiB of slot storage (262,144 × 4 bytes) and is built once, when the
|
|
57
|
+
provider is created (lazily, on first use). Creating the delta provider now measures 50.3–50.8
|
|
58
|
+
ms median over two runs; parsing the artifact accounts for 32.0–33.1 ms and building the index
|
|
59
|
+
roughly 17–19 ms. Before these changes, construction measured 77–81 ms and parsing 58–65 ms.
|
|
60
|
+
The reduction comes from inserting each section directly into the table instead of merging the
|
|
61
|
+
four lists first, dropping the cross-section duplicate `Set` and the intermediate body copy,
|
|
62
|
+
and sharing one frozen empty alternatives array. The index is still the smaller half of
|
|
63
|
+
construction; nothing is computed at module import.
|
|
64
|
+
- Distinct-word timings include cache misses over a 4,000-word working set and are naturally
|
|
65
|
+
slower than repeated lookups; the spread between runs is largest there.
|
|
66
|
+
|
|
67
|
+
## Artifact and bundle sizes
|
|
68
|
+
|
|
69
|
+
Measured by `scripts/build-lexicon.ts` and `scripts/build-site.ts` and written to
|
|
70
|
+
`dist/sizes.json` / `dist/site/sizes.json`.
|
|
71
|
+
|
|
72
|
+
| Artifact | Raw | Gzip |
|
|
73
|
+
| --- | ---: | ---: |
|
|
74
|
+
| Source lexicon TSV (shared with Java, not shipped) | 7,553,688 B | — |
|
|
75
|
+
| Delta artifact for `pluralize()` (124,883 entries) | 1,962,900 B | 562,893 B |
|
|
76
|
+
| Full artifact for `analyze()` (147,338 entries) | 3,707,556 B | 785,131 B |
|
|
77
|
+
| Membership sidecar (340,726 hashes, base64) | 1,817,208 B | 1,333,152 B |
|
|
78
|
+
| Browser demo bundle (full lexicon, minified ESM) | 3,735,241 B | 794,191 B |
|
|
79
|
+
|
|
80
|
+
The delta artifact is 26.0% of the source TSV's size and 84.8% of the full artifact's entries:
|
|
81
|
+
85% of the lexicon is already reproduced by the three productive rules plus the identity lists,
|
|
82
|
+
so only the differing entries and the mass/plural-only/invariant key lists are stored.
|
|
83
|
+
|
|
84
|
+
The published `dist/lexicon-delta.txt` is the same 1,962,900-byte artifact the site links beside
|
|
85
|
+
the demo; browsers download it gzipped at 562,893 B.
|
|
86
|
+
|
|
87
|
+
## Notes
|
|
88
|
+
|
|
89
|
+
- The browser demo loads the **full lexicon without the membership sidecar**: 794,191 B gzip
|
|
90
|
+
including the engine. Membership is only needed for unknown-uppercase initialism inference, so
|
|
91
|
+
the demo behaves like a rules-only build for that one policy and says so on the page.
|
|
92
|
+
- The `morphex/rules` entry point replaces the generated delta module at build time; it contains
|
|
93
|
+
zero lexicon data and therefore no CC BY-SA 3.0 material.
|
|
94
|
+
- Generated data and bundles live under `morphex-ts/dist/` and are gitignored; only the sizes in
|
|
95
|
+
this file and on the site are committed.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Justin Baker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
The lexical data bundled with this package is not covered by this MIT licence.
|
|
24
|
+
It is licensed under CC BY-SA 3.0; see NOTICE in this directory and the
|
|
25
|
+
repository's data/LICENSE-DATA.md.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
morphex-ts — TypeScript port of morphex
|
|
2
|
+
======================================
|
|
3
|
+
|
|
4
|
+
Code: MIT (see LICENSE in this directory).
|
|
5
|
+
Lexical data: CC BY-SA 3.0.
|
|
6
|
+
|
|
7
|
+
The generated lexicon artifacts (dist/lexicon-delta.txt, dist/lexicon-full.txt, the membership
|
|
8
|
+
table, and the browser demo bundle that embeds the full lexicon) are derived from the same
|
|
9
|
+
permitted corpus as the Java library's resources:
|
|
10
|
+
|
|
11
|
+
inflector-core/src/main/resources/io/github/slayyyyer/morphex/inflector/lexicon-v2.tsv
|
|
12
|
+
inflector-core/src/main/resources/io/github/slayyyyer/morphex/inflector/lexicon-membership-v2.bin
|
|
13
|
+
|
|
14
|
+
That data is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported licence
|
|
15
|
+
(CC BY-SA 3.0): https://creativecommons.org/licenses/by-sa/3.0/
|
|
16
|
+
|
|
17
|
+
Attribution: Wiktionary contributors, via Kaikki.org / Wiktextract.
|
|
18
|
+
|
|
19
|
+
The authoritative attribution and data-licence statements for the whole repository are the root
|
|
20
|
+
NOTICE and data/LICENSE-DATA.md; this file is a pointer, not a replacement. If you redistribute a
|
|
21
|
+
build of this package, keep this NOTICE, the root attribution, and the CC BY-SA 3.0 terms with it.
|
|
22
|
+
|
|
23
|
+
The `morphex/rules` entry point contains no lexicon data and therefore no CC BY-SA 3.0 material.
|
package/README.md
CHANGED
|
@@ -1,3 +1,198 @@
|
|
|
1
|
-
#
|
|
1
|
+
# morphex (TypeScript)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Deterministic English noun pluralization for TypeScript and JavaScript. This is a native port of
|
|
4
|
+
the Java [morphex](../README.md) library: same precedence pipeline, same lexicon, same compound
|
|
5
|
+
table. No runtime dependencies, no network calls, no model downloads.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { EnglishInflector } from 'morphex';
|
|
9
|
+
|
|
10
|
+
const inflector = EnglishInflector.standard();
|
|
11
|
+
|
|
12
|
+
inflector.pluralize('child'); // 'children'
|
|
13
|
+
inflector.pluralize('CHILD'); // 'CHILDREN'
|
|
14
|
+
inflector.pluralize('UFO'); // 'UFOs'
|
|
15
|
+
inflector.pluralize('mother-in-law'); // 'mothers-in-law'
|
|
16
|
+
inflector.pluralize('cat', 1); // 'cat' (count === 1 keeps the source)
|
|
17
|
+
inflector.pluralize('cat', 3); // 'cats'
|
|
18
|
+
|
|
19
|
+
const result = inflector.analyze('die');
|
|
20
|
+
result.plural; // 'dice'
|
|
21
|
+
result.alternatives; // ['dies']
|
|
22
|
+
result.ambiguous; // true
|
|
23
|
+
result.stage; // 'LEXICON'
|
|
24
|
+
result.source; // 'lex:die'
|
|
25
|
+
result.countability; // 'COUNTABLE'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Node 18+ and browsers. Zero runtime dependencies. Immutable instances: configuration is fixed at
|
|
29
|
+
build time, so an instance can be shared freely.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install morphex
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To build from a checkout instead: `cd morphex-ts && npm install && npm run build`.
|
|
38
|
+
|
|
39
|
+
Entry points:
|
|
40
|
+
|
|
41
|
+
| Import | Lexicon | Use |
|
|
42
|
+
| --------------- | ---------------------------- | ----------------------------------------------------------------------------- |
|
|
43
|
+
| `morphex` | 124,883-entry delta artifact | `pluralize()`; `analyze()` reports rule/compound stages without full metadata |
|
|
44
|
+
| `morphex/full` | 147,338-entry full artifact | Java-identical `analyze()`: alternatives, ambiguity, countability |
|
|
45
|
+
| `morphex/rules` | none | rules-only build with no data files and no CC BY-SA material |
|
|
46
|
+
|
|
47
|
+
The full artifact is opt-in so the common case does not pay for diagnostics it never reads:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { EnglishInflector, createBundledFullLexicon } from 'morphex/full';
|
|
51
|
+
|
|
52
|
+
const diagnostics = EnglishInflector.builder().withLexicon(createBundledFullLexicon()).build();
|
|
53
|
+
|
|
54
|
+
diagnostics.analyze('die').alternatives; // ['dies']
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### `pluralize(word, count?)`
|
|
60
|
+
|
|
61
|
+
`pluralize(word, 1)` returns the input unchanged, including its original casing. Any other count
|
|
62
|
+
pluralizes. Both are pure functions of the configured instance.
|
|
63
|
+
|
|
64
|
+
### `analyze(word): Inflection`
|
|
65
|
+
|
|
66
|
+
Every field is always present; nothing is optional.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
interface Inflection {
|
|
70
|
+
readonly input: string;
|
|
71
|
+
readonly plural: string;
|
|
72
|
+
readonly alternatives: readonly string[];
|
|
73
|
+
readonly ambiguous: boolean;
|
|
74
|
+
readonly stage: 'OVERRIDE' | 'LEXICON' | 'INFERRED' | 'COMPOUND' | 'RULE' | 'FALLBACK';
|
|
75
|
+
readonly source: string; // stable id, e.g. 'lex:child', 'rule:default-s'
|
|
76
|
+
readonly countability: 'COUNTABLE' | 'MASS' | 'MIXED' | 'PLURAL_ONLY' | 'UNKNOWN';
|
|
77
|
+
readonly invariant: boolean;
|
|
78
|
+
readonly note: string;
|
|
79
|
+
readonly diagnostics: boolean; // false when the provider cannot supply full metadata
|
|
80
|
+
isAccepted(form: string): boolean;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`stage` follows the Java `DecisionSource`; `source` is the Java `Inflection.id()`.
|
|
85
|
+
|
|
86
|
+
### Configuration
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const custom = EnglishInflector.builder()
|
|
90
|
+
.withOverride('goose', 'geese') // exact, case-sensitive whole-input match
|
|
91
|
+
.withOverride('API', 'APIs')
|
|
92
|
+
.withInitialism('ABC') // suffix policy for a declared acronym
|
|
93
|
+
.withMode('CLASSICAL') // prefer attested classical forms
|
|
94
|
+
.withCasePolicy('LOWERCASE') // render the whole result in one casing
|
|
95
|
+
.withoutBundledLexicon() // rules-only: no data files at all
|
|
96
|
+
.build();
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Overrides win over everything else and are returned literally. The builder is fluent and typed;
|
|
100
|
+
`build()` returns an `EnglishInflector`. Instances are immutable.
|
|
101
|
+
|
|
102
|
+
### Custom lexicons
|
|
103
|
+
|
|
104
|
+
Every data source implements the same provider interface, so bundled, opt-in, rules-only and
|
|
105
|
+
caller-supplied data are interchangeable:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { EnglishInflector, createLexicon } from 'morphex';
|
|
109
|
+
|
|
110
|
+
const provider = createLexicon([
|
|
111
|
+
{ lemma: 'cactus', plural: 'cactuses', classical: 'cacti', alternatives: ['cacti'] },
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
const inflector = EnglishInflector.builder().withLexicon(provider).build();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`createFullLexicon`, `createDeltaLexicon` and `RULES_ONLY_LEXICON` are exported for the same
|
|
118
|
+
purpose. A malformed artifact, duplicate key, unsorted file or unknown flag throws at construction
|
|
119
|
+
time; the loaders never guess.
|
|
120
|
+
|
|
121
|
+
## Precedence
|
|
122
|
+
|
|
123
|
+
First match wins, exactly as in the Java library:
|
|
124
|
+
|
|
125
|
+
1. **Caller override** — exact, case-sensitive whole-input match.
|
|
126
|
+
2. **Exact lexicon entry** — case-insensitive lookup, with case-sensitive entries where needed.
|
|
127
|
+
3. **Declared initialism** — configured acronym suffix policy.
|
|
128
|
+
4. **Supported compound** — bounded table: exact entries, free tails and curated postposed heads.
|
|
129
|
+
Unmatched input is left alone, not guessed.
|
|
130
|
+
5. **Three productive rules** — `+es` after sibilants, consonant + `y` → `ies`, default `+s`.
|
|
131
|
+
6. **Documented fallback** — unsupported shapes are returned unchanged and reported by `analyze`.
|
|
132
|
+
|
|
133
|
+
Lookup normalisation is separate from output case restoration, so `CHILD → CHILDREN` and
|
|
134
|
+
`UFO → UFOs` are both correct.
|
|
135
|
+
|
|
136
|
+
## Rules-only vs bundled lexicon
|
|
137
|
+
|
|
138
|
+
| Build | Data shipped | `pluralize('child')` | `analyze()` metadata |
|
|
139
|
+
| --------------- | -------------------------------: | --------------------- | ----------------------------------------------- |
|
|
140
|
+
| `morphex/rules` | 0 B | `childs` (rules only) | rules/compound stages, `diagnostics: false` |
|
|
141
|
+
| `morphex` | 1,962,900 B raw / 562,893 B gzip | `children` | rule/compound stages; partial lexical metadata |
|
|
142
|
+
| `morphex/full` | 3,707,556 B raw / 785,131 B gzip | `children` | complete: alternatives, ambiguity, countability |
|
|
143
|
+
|
|
144
|
+
A rules-only build is a plain `EnglishInflector.builder().withoutBundledLexicon().build()` and
|
|
145
|
+
needs no data files. The delta engine parses its four artifact sections and builds a 1 MiB
|
|
146
|
+
open-addressed hash index at construction (roughly 50 ms end to end on the measured machine,
|
|
147
|
+
with 17–19 ms of that building the index), so lookups do not scan the sorted keys; nothing is
|
|
148
|
+
built at module import. The full artifact also has a
|
|
149
|
+
1,817,208 B (1,333,152 B gzip) membership sidecar used only for uppercase-initialism inference.
|
|
150
|
+
|
|
151
|
+
## Building data
|
|
152
|
+
|
|
153
|
+
The lexicons are generated from the repository's authoritative artifact, not copied:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm run build:data
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Reads `inflector-core/src/main/resources/io/github/slayyyyer/morphex/inflector/lexicon-v2.tsv`
|
|
160
|
+
and `lexicon-membership-v2.bin`, and writes gitignored modules under `src/generated/` plus
|
|
161
|
+
`dist/lexicon-delta.txt`, `dist/lexicon-full.txt` and `dist/sizes.json`.
|
|
162
|
+
|
|
163
|
+
The delta artifact stores only entries whose answer differs from the productive rules, plus
|
|
164
|
+
sorted key lists for mass, plural-only and invariant nouns. Hyphenated and spaced lemmas are kept
|
|
165
|
+
even when the rules would reproduce them, because the rules are unreachable behind the compound
|
|
166
|
+
policy.
|
|
167
|
+
|
|
168
|
+
## Conformance
|
|
169
|
+
|
|
170
|
+
Both ports are tested against fixtures exported from the Java engine:
|
|
171
|
+
|
|
172
|
+
- `test-fixtures/p1.json` — all 50,978 frozen P1 cases, tuple-typed in
|
|
173
|
+
`test-fixtures/schema.ts`.
|
|
174
|
+
- `test-fixtures/upstream-probe.json` — the `docs/upstream-probe.md` table.
|
|
175
|
+
- `site/catalog.json` — the verified catalog used by the Java site demo.
|
|
176
|
+
|
|
177
|
+
Vitest runs every case through both the delta and full engines and compares the plural, accepted
|
|
178
|
+
forms, primary flag, countability, invariant flag, stage and source id. CI regenerates the
|
|
179
|
+
fixtures with Java and fails on any diff, so the ports cannot drift silently.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm test # typecheck-friendly Vitest suite
|
|
183
|
+
npm run typecheck # strict tsc, no emit
|
|
184
|
+
npm run lint # typescript-eslint + Prettier
|
|
185
|
+
npm run build # ESM + CJS + .d.ts/.d.cts via tsup
|
|
186
|
+
npm run check:package # publint + arethetypeswrong
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Benchmarks
|
|
190
|
+
|
|
191
|
+
See [BENCHMARKS.md](BENCHMARKS.md) for measured Node timings and bundle sizes. The numbers are
|
|
192
|
+
Node/V8 micro-benchmarks and are deliberately separate from the Java JMH results.
|
|
193
|
+
|
|
194
|
+
## Licence
|
|
195
|
+
|
|
196
|
+
Code: MIT (see `LICENSE`). Bundled lexicon data: CC BY-SA 3.0, Wiktionary contributors via
|
|
197
|
+
Kaikki.org / Wiktextract — see `NOTICE` and the repository's root `NOTICE` and
|
|
198
|
+
`data/LICENSE-DATA.md`. The `morphex/rules` entry point contains no lexicon data.
|