which-dialect 0.5.4 → 0.5.6
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/README.md +5 -3
- package/dist/check.js +5 -1
- package/dist/index.js +13 -6
- package/dist/translate.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ Every language's data defines its words in English, so English is the bridge bet
|
|
|
88
88
|
|
|
89
89
|
**Hand-picked words** come before all of these for the meanings that have them (see below).
|
|
90
90
|
|
|
91
|
-
Meanings are ordered by `meaning` (if given), then by being tagged for `fromRegion`, then the dictionary's own order, which lists main meanings first. That order is a guess when a word has several parts of speech: English *just* is listed as an adjective ("fair") before the adverb. Pass `pos` or `meaning` when you know which one you want.
|
|
91
|
+
Meanings are ordered by `meaning` (if given), then by being tagged for `fromRegion`, then the dictionary's own order, which lists main meanings first. **Specialist and sexual meanings come last:** the English data labels later meanings `technical` (physics, chemistry, geometry… from Wiktionary's topics) or `sexual` (by definition, since Wiktionary seldom labels them), so "excited" lists *enthusiastic* and "to stir the emotions" before "in a state of higher energy", "having an erection" and "sexually aroused", for every target language. A word's first meaning is never labelled, so *gold* stays the metal and *circle* the shape (`senseLabels` in `languages/en.ts`). That order is a guess when a word has several parts of speech: English *just* is listed as an adjective ("fair") before the adverb. Pass `pos` or `meaning` when you know which one you want.
|
|
92
92
|
|
|
93
93
|
### Hand-picked words
|
|
94
94
|
|
|
@@ -210,7 +210,7 @@ Each sentence has `original`, `parts`, `corrected`, `changes` (`{ from, to, why,
|
|
|
210
210
|
|
|
211
211
|
One language's dictionary. `baseUrl` defaults to `https://cdn.jsdelivr.net/npm/which-dialect-<lang>@0.1/data`.
|
|
212
212
|
|
|
213
|
-
- `dictionary.lookup(word)`: all entries for a word, with every sense's definitions, regions, labels, synonyms and variant links (`altOf`).
|
|
213
|
+
- `dictionary.lookup(word)`: all entries for a word, with every sense's definitions, regions, labels, synonyms and variant links (`altOf`). The word as written, else lowercase, else capitalized, so names written in lowercase are found (*nhật* → *Nhật*, Japan; *nhật bản* → *Nhật Bản*).
|
|
214
214
|
- `dictionary.searchEnglish(term, { region?, pos?, exclude?, limit?, allSenses? })`: words for an English term in this language, one per word, best first (no meaning handling: use the translator for that). `allSenses: true` returns every matching sense of those words instead.
|
|
215
215
|
- `dictionary.frames()`: the language's sentence frames and frame words, unfilled (`{ frames, words }`; empty without frames).
|
|
216
216
|
- `dictionary.segment(text)`: the text split into the language's words, longest dictionary match first (up to the checker's `maxWordUnits`, 3 for Vietnamese): `Hôm nay tôi ăn cơm.` → *Hôm nay* · *tôi* · *ăn cơm*, each `{ text, start, end, entries }` (offsets into the NFC text; `entries` empty for words the dictionary doesn't have). Use it to treat a word of several syllables as one, e.g. to show its meaning.
|
|
@@ -301,6 +301,8 @@ npm run evaluate # translation accuracy on known-correct cas
|
|
|
301
301
|
npm run build:picks -- vi # rebuild only a language's hand-picked words (after editing `picks` in languages/vi.ts)
|
|
302
302
|
npm run picks-sheet -- vi # review sheet (.cache/picks-vi.csv) of the top English words' meanings; -- --words file.txt, -- --top 500
|
|
303
303
|
npm run picks-sheet -- vi --apply sheet.csv # config rows for the rows whose your_pick is filled in
|
|
304
|
+
npm run picks-sheet -- vi --top 300 --drafts reviews/vi-top300.json --html page.html # the same rows as a review page
|
|
305
|
+
npm run picks-sheet -- vi --apply-decisions <dir> --drafts reviews/vi-top300.json # config rows from the page's decisions
|
|
304
306
|
npm run typecheck
|
|
305
307
|
npm run build # compile the API to packages/core/dist
|
|
306
308
|
```
|
|
@@ -315,7 +317,7 @@ Generated data isn't committed (it would bloat git history); it's built before p
|
|
|
315
317
|
4. `npm run build:data -- <code>`, check the results, add a few real-data tests and evaluation cases, and publish.
|
|
316
318
|
5. Optional: [checker](#checking-text) settings (`checker` in `languages/<code>.ts`): `units: 'syllables'` for languages written in syllables (also builds the syllable list for accent suggestions), `maxWordUnits`, the reliable `pronouns` per pronoun-table column, `ambiguousPronouns`, and `politeEndings`, `similarLetters` (groups of letters often confused, for typo suggestions). Without them, the language is read as space-separated words and gets the region check.
|
|
317
319
|
6. Optional: [sentence frames](#sentence-frames): `frames` (how the language says each frame in `languages/frames.ts`) and `frameWords` (the words that change by region, each from a definition or an override with a note). Frames reach other languages through the shared catalog; a new frame goes in the catalog once.
|
|
318
|
-
7. Optional, later: [hand-picked words](#hand-picked-words). A speaker runs `npm run picks-sheet -- <code>`, fills in `your_pick` only where the first choice is wrong, and pastes the `--apply` output into `picks`.
|
|
320
|
+
7. Optional, later: [hand-picked words](#hand-picked-words). A speaker runs `npm run picks-sheet -- <code>`, fills in `your_pick` only where the first choice is wrong, and pastes the `--apply` output into `picks`. Or as a **review page**: draft suggestions for the meanings whose first word is wrong in `reviews/<code>-top300.json` (picks, confidence, why, an example sentence), build the page with `--html`, publish it as a claude.ai Artifact (it saves each decision in its database), and once a speaker has gone through it, export the decisions and turn them into config rows with `--apply-decisions`. In Claude Code, the project skill `/word-review <language> <N>` does all of this (and `/word-review <language> <N> apply` afterwards).
|
|
319
321
|
|
|
320
322
|
Languages in non-Latin scripts (Chinese, Arabic, Russian, …) will need a script-aware version of `shardKey` first: today files are split by the first two Latin letters.
|
|
321
323
|
|
package/dist/check.js
CHANGED
|
@@ -172,8 +172,12 @@ async function spelling(ctx) {
|
|
|
172
172
|
max: COMPOUND_MAX_COMBINATIONS,
|
|
173
173
|
}).filter((c) => c !== written.join(' '));
|
|
174
174
|
const found = (await Promise.all(combos.map(async (c) => ({ word: c, entries: await ctx.dict.lookup(c) }))))
|
|
175
|
+
// Names (found through a capitalized headword) only for a misspelled run: "nhat ban" → "Nhật Bản",
|
|
176
|
+
// but "hoa lan" (orchid, every syllable a word) isn't "Hòa Lan" (the Netherlands).
|
|
177
|
+
.map((f) => ({ ...f, entries: doubtful ? f.entries : f.entries.filter((e) => e.word === e.word.toLowerCase()) }))
|
|
175
178
|
.filter((f) => f.entries.length)
|
|
176
|
-
|
|
179
|
+
// Spelled as the headword: names keep their capitals ("nhat ban" → "Nhật Bản").
|
|
180
|
+
.map((f) => ({ word: f.entries[0].word.toLowerCase() === f.word ? f.entries[0].word : f.word, frequency: Math.max(-1, ...f.entries.map((e) => e.frequency ?? -1)) }))
|
|
177
181
|
.sort((a, b) => b.frequency - a.frequency)
|
|
178
182
|
.slice(0, 3);
|
|
179
183
|
if (!found.length)
|
package/dist/index.js
CHANGED
|
@@ -72,7 +72,10 @@ export function createDictionary(options) {
|
|
|
72
72
|
async function lookup(word) {
|
|
73
73
|
const w = word.trim();
|
|
74
74
|
const [m, data] = await Promise.all([meta(), shard('words', w)]);
|
|
75
|
-
|
|
75
|
+
// The word as written, else lowercase ("Mẹ" → "mẹ"), else capitalized for names written in lowercase
|
|
76
|
+
// ("nhật" → "Nhật", Japan; "nhật bản" → "Nhật Bản"), which spellchecker suggestions and learners do.
|
|
77
|
+
const titled = w.toLowerCase().replace(/(^|[\s-])(\p{Ll})/gu, (_, sep, c) => sep + c.toUpperCase());
|
|
78
|
+
const stored = data?.[w] ?? data?.[w.toLowerCase()] ?? data?.[titled] ?? [];
|
|
76
79
|
return stored.map((e) => ({ ...e, senses: e.senses.map((s) => fromStored(s, m.regions)) }));
|
|
77
80
|
}
|
|
78
81
|
const checker = async () => {
|
|
@@ -107,11 +110,15 @@ export function createDictionary(options) {
|
|
|
107
110
|
// Each form as written in lowercase and capitalized, for names ("nhat" → "Nhật", Japan; "nhat ban" →
|
|
108
111
|
// "Nhật Bản").
|
|
109
112
|
const forms = [...new Set(combos.flatMap((c) => [c, c.replace(/(^|\s)(\S)/gu, (_, sep, ch) => sep + ch.toUpperCase())]))];
|
|
110
|
-
//
|
|
111
|
-
// name (a place, a people), so given names don't
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// Each form counts only as its own headword (lookup also finds other cases: "nhật" finds "Nhật"), and a
|
|
114
|
+
// capitalized one only when it's more than a person's name (a place, a people), so given names don't
|
|
115
|
+
// crowd out words.
|
|
116
|
+
const found = (await Promise.all(forms.map(async (c) => ({ word: c, entries: await lookup(c) })))).map((v) => ({
|
|
117
|
+
...v,
|
|
118
|
+
entries: v.entries.filter((e) => v.word === v.word.toLowerCase()
|
|
119
|
+
? !(e.word !== v.word && e.word.toLowerCase() === v.word)
|
|
120
|
+
: e.word === v.word && e.senses.some((s) => !PERSONAL_NAME.test(s.glosses[0] ?? ''))),
|
|
121
|
+
}));
|
|
115
122
|
const frequency = (v) => {
|
|
116
123
|
const known = v.entries.map((e) => e.frequency).filter((f) => f !== undefined);
|
|
117
124
|
return known.length ? Math.max(...known) : parts.length === 1 ? list[v.word.toLowerCase()] || undefined : undefined;
|
package/dist/translate.js
CHANGED
|
@@ -130,6 +130,10 @@ const INFLECTION_VIA = /\b(?:past|participle|plural|singular|present|third-perso
|
|
|
130
130
|
// Everyday speech, not slang: a form's own colloquial meaning ("got": "Have/has.") isn't marginal.
|
|
131
131
|
const EVERYDAY_LABELS = new Set(['informal', 'colloquial']);
|
|
132
132
|
const LABEL_PENALTY = new Set(['slang', 'Internet', 'humorous', 'literary', 'dialectal', 'uncommon', 'rare', 'euphemistic']);
|
|
133
|
+
// Senses with any of these labels (added by the data build, LanguageConfig.senseLabels) rank after
|
|
134
|
+
// everyday and marginal ones: specialist ("excited": "in a state of higher energy", physics) and sexual
|
|
135
|
+
// meanings, which a learner rarely means and shouldn't see first.
|
|
136
|
+
const LATER_LABELS = new Set(['technical', 'sexual']);
|
|
133
137
|
// A target gloss that lists the bridge term itself as an equivalent ("muốn": "to want"; "má": "mother;
|
|
134
138
|
// mom") is a plain translation, but a short gloss can't earn definition overlap, since the term's own
|
|
135
139
|
// words don't count. Wordier glosses did ("mắc": "to want to (go to the bathroom, laugh, etc.); to have
|
|
@@ -859,13 +863,15 @@ export function createTranslator(options = {}) {
|
|
|
859
863
|
}
|
|
860
864
|
return marginal ? -1 : 0;
|
|
861
865
|
};
|
|
866
|
+
const later = (g) => (g.source.labels.some((l) => LATER_LABELS.has(l)) ? 2 : 0);
|
|
862
867
|
const relevance = (g) => (meaningWords
|
|
863
868
|
? 10 * meaningOverlap(meaningWords, g.source.glosses.join(' ')) + 5 * meaningOverlap(meaningWords, g.translations[0]?.gloss ?? '')
|
|
864
869
|
: 0) -
|
|
865
870
|
(ownSenses && g.source.via && SPELLING_VIA.test(g.source.via) ? 2 : 0) +
|
|
866
871
|
(fromRegion && g.source.regionTagged ? 3 : 0) +
|
|
867
872
|
pronounRelevance(g) +
|
|
868
|
-
labelRelevance(g)
|
|
873
|
+
labelRelevance(g) -
|
|
874
|
+
later(g) +
|
|
869
875
|
(!meaning && pickedFirst.has(g.source) ? 5 : 0);
|
|
870
876
|
// A sense with no translations says nothing, so it goes after the ones that do ("got": "Expressing
|
|
871
877
|
// obligation; used with have." finds nothing; "Must; have/has (to)." gives "phải").
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "which-dialect",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Translate words between languages and dialects, meaning by meaning, starting with Vietnamese (Northern, Central, Southern) and English, with Wiktionary data loaded on demand. More languages are planned.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|