which-dialect 0.5.6 → 0.5.8
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 +14 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/translate.js +19 -5
- package/dist/types.d.ts +10 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Translate words **between languages and between dialects**, meaning by meaning: Northern Vietnamese *ngô* into Southern *bắp*, English *cool* (as in "awesome") into Southern Vietnamese *ngầu*, English "I" into the right Vietnamese pronoun for who you're talking to, US *truck* into UK *lorry*. Dictionary data comes from [Wiktionary](https://en.wiktionary.org/) (via [Kaikki.org](https://kaikki.org/)) and loads only when you need it.
|
|
4
4
|
|
|
5
|
-
**Status:** which-dialect is starting with **Vietnamese**, with English as the bridge language, and the current focus is cleaning up Vietnamese translations. **More languages are planned.** The translator and data format are language-general, so adding one is a config file plus a data package (see [Adding a language](#adding-a-language)). Spanish
|
|
5
|
+
**Status:** which-dialect is starting with **Vietnamese**, with English as the bridge language, and the current focus is cleaning up Vietnamese translations. **More languages are planned.** The translator and data format are language-general, so adding one is a config file plus a data package (see [Adding a language](#adding-a-language)). **Spanish** is supported too, by country and region group (Spain, Mexico, Latin America, Río de la Plata, …).
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
import { createTranslator } from 'which-dialect'
|
|
@@ -18,7 +18,7 @@ await tr.translate('cool', { from: 'en', to: 'vi', toRegion: 'Southern', meaning
|
|
|
18
18
|
|
|
19
19
|
Each result is a list of **groups, one per meaning** of the source word, each with its part of speech, its definitions and its translations, so a word's meanings never get mixed up.
|
|
20
20
|
|
|
21
|
-
**Data:** Wiktionary data of 2026-09-25
|
|
21
|
+
**Data:** Wiktionary data of 2026-09-25 (Vietnamese: 2026-09-28). See [`DATA_UPDATES.md`](DATA_UPDATES.md) for each language's dates and update history.
|
|
22
22
|
|
|
23
23
|
You install one small package. Dictionary data is **not bundled**: each lookup fetches one small file (usually 5–100 kB compressed) from the language's data package on the jsDelivr CDN, and caches it.
|
|
24
24
|
|
|
@@ -27,9 +27,10 @@ You install one small package. Dictionary data is **not bundled**: each lookup f
|
|
|
27
27
|
| Language | Code | Data package | Regions | Entries | Region-tagged senses |
|
|
28
28
|
|---|---|---|---|---|---|
|
|
29
29
|
| Vietnamese | `vi` | [`which-dialect-vi`](packages/vi) | Northern, Central, Southern | 35,394 | 1,330 of 42,419 (3.1%) |
|
|
30
|
+
| Spanish | `es` | [`which-dialect-es`](packages/es) | 23 countries (Spain, Mexico, Argentina, Cuba, …), plus groups: Latin America, Central America, Caribbean, South America, Río de la Plata | 123,995 | 11,940 of 153,963 (7.8%) |
|
|
30
31
|
| English | `en` | [`which-dialect-en`](packages/en) | 15 countries/areas (US, UK, Australia, India, …), plus groups: North America, British Isles, Commonwealth, … | 594,544 | 37,339 of 776,085 (4.8%) |
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
Any of these languages can be translated into any other, in both directions and between any of their regions, including between two dialects of one language (Northern → Southern Vietnamese, US → UK English, Spain → Mexico Spanish) and dialect to dialect across languages (Mexican Spanish → Southern Vietnamese). English is also the bridge every language's definitions are written in.
|
|
33
34
|
|
|
34
35
|
More languages are planned. Each one is a config file in [`languages/`](languages) plus a data package in `packages/<code>`; see [Adding a language](#adding-a-language).
|
|
35
36
|
|
|
@@ -92,20 +93,20 @@ Meanings are ordered by `meaning` (if given), then by being tagged for `fromRegi
|
|
|
92
93
|
|
|
93
94
|
### Hand-picked words
|
|
94
95
|
|
|
95
|
-
For common words, a speaker of the target language knows the natural word even where the data ranks another first: *get* "To fetch, bring, take" ranks Vietnamese *đưa* ("to hand, to bring") above *lấy*, because *đưa*'s definition matches more of the English words and no *lấy* definition says "fetch". Each language can list **hand-picked words for English meanings** (`picks` in `languages/<code>.ts`, built into `picks.json`). In a meaning that has picks, the picked words come first (`bridge: 'picked'`), then the ranked words; other meanings are unchanged. Picks can carry a region tag, like translation tables: with `toRegion`, picks tagged for it come first and picks tagged only for other regions are left to the ranking. A pick can also mark its meaning as the one to put first when no `pos` or `meaning` is given.
|
|
96
|
+
For common words, a speaker of the target language knows the natural word even where the data ranks another first: *get* "To fetch, bring, take" ranks Vietnamese *đưa* ("to hand, to bring") above *lấy*, because *đưa*'s definition matches more of the English words and no *lấy* definition says "fetch". Each language can list **hand-picked words for English meanings** (`picks` in `languages/<code>.ts`, built into `picks.json`). In a meaning that has picks, the picked words come first (`bridge: 'picked'`), then the ranked words; other meanings are unchanged. Picks can carry a region tag, like translation tables: with `toRegion`, picks tagged for it come first and picks tagged only for other regions are left to the ranking. A pick can also mark its meaning as the one to put first when no `pos` or `meaning` is given. A pick can be a phrase the dictionary doesn't list (*chưa bao giờ*, "never"; marked `phrase: true`), and a row can `exclude` words that should never be given for its meaning (*mát mặt* for *well* "in good health").
|
|
96
97
|
|
|
97
|
-
Picks are keyed by English meaning, so each language needs one list however many languages there are: translating from any other language reaches them through the English meaning (as with translation tables). They're optional: a language without picks uses the ranking alone. `picks: false` shows the ranking alone, and `npm run evaluate` reports both. Vietnamese picks so far: *get* "fetch" → *lấy*, *mang*; *get* "obtain" → *lấy
|
|
98
|
+
Picks are keyed by English meaning, so each language needs one list however many languages there are: translating from any other language reaches them through the English meaning (as with translation tables). They're optional: a language without picks uses the ranking alone. `picks: false` shows the ranking alone, and `npm run evaluate` reports both. Vietnamese picks so far: *get* "fetch" → *lấy*, *mang*; *get* "obtain" → *lấy*; a few grammar words; and 54 meanings from a native speaker's review of the 300 most common English words (*can* "know how to" → *biết*; *how* → *như thế nào*, Southern *làm sao*; *life* "a living being" → *mạng*).
|
|
98
99
|
|
|
99
100
|
### How accurate is it?
|
|
100
101
|
|
|
101
|
-
`npm run evaluate` runs known-correct translations across language pairs and dialect pairs (English → Vietnamese by region, including pronouns, Northern → Southern Vietnamese, US ↔ UK, Vietnamese → English). A case passes when a correct word is in the top 3 of the first meaning:
|
|
102
|
+
`npm run evaluate` runs known-correct translations across language pairs and dialect pairs (English → Vietnamese by region, including pronouns, English → Spanish by country, Spanish ↔ Vietnamese, Northern → Southern Vietnamese, Spain ↔ Mexico, US ↔ UK, Vietnamese → English). A case passes when a correct word is in the top 3 of the first meaning:
|
|
102
103
|
|
|
103
104
|
| Set | Correct word in top 3 | Correct word first |
|
|
104
105
|
|---|---|---|
|
|
105
|
-
| Tuning set (used while developing) |
|
|
106
|
-
| Held-out set (written afterwards, not tuned against) |
|
|
106
|
+
| Tuning set (used while developing) | 88 / 91 (97%) | 82 / 91 (90%); 81 / 91 without picks |
|
|
107
|
+
| Held-out set (written afterwards, not tuned against) | 29 / 30 (97%) | 28 / 30 (93%) |
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
With Spanish back (2026-10-29), its cases are back too; the misses are Spanish (*cerdo* without a part of speech, and Mexican "popcorn" and "cat"; see [`FUTURE_IMPROVEMENTS.md`](FUTURE_IMPROVEMENTS.md)). The held-out set has since been looked at, so new held-out cases are needed before relying on it (see [`FUTURE_IMPROVEMENTS.md`](FUTURE_IMPROVEMENTS.md)).
|
|
109
110
|
|
|
110
111
|
## Checking text
|
|
111
112
|
|
|
@@ -208,7 +209,7 @@ Each sentence has `original`, `parts`, `corrected`, `changes` (`{ from, to, why,
|
|
|
208
209
|
|
|
209
210
|
### `createDictionary({ lang, baseUrl?, load? })`
|
|
210
211
|
|
|
211
|
-
One language's dictionary. `baseUrl` defaults to `https://cdn.jsdelivr.net/npm/which-dialect-<lang>@0.
|
|
212
|
+
One language's dictionary. `baseUrl` defaults to `https://cdn.jsdelivr.net/npm/which-dialect-<lang>@0.2/data`.
|
|
212
213
|
|
|
213
214
|
- `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
215
|
- `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.
|
|
@@ -216,7 +217,7 @@ One language's dictionary. `baseUrl` defaults to `https://cdn.jsdelivr.net/npm/w
|
|
|
216
217
|
- `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.
|
|
217
218
|
- `dictionary.variants(word, { limit? })`: other words with the same letters but other accents that the dictionary has, most common first, each `{ word, entries, frequency? }`: *muộn* → *muốn*, *mượn*, *muôn*…; for words of several syllables every syllable varies (*hom nay* → *hôm nay*). Capitalized headwords count too, unless they're only a person's name: *nhat* → *nhất*, *Nhật* (Japan), *nhạt*…; *nhat ban* → *Nhật Bản*. For apps that let learners pick the word they meant: a real word with the wrong tone passes the spellchecker. Languages written in syllables only (`[]` otherwise).
|
|
218
219
|
- `dictionary.checker()` / `dictionary.syllables()`: the language's checker settings and syllable list (with frequencies), used by `createChecker` (`{}` for languages without them).
|
|
219
|
-
- `dictionary.picks()`: the language's [hand-picked words](#hand-picked-words), `{ word, pos, gloss, picks: [{ word, tags? }], first? }` per English meaning (empty for languages without any).
|
|
220
|
+
- `dictionary.picks()`: the language's [hand-picked words](#hand-picked-words), `{ word, pos, gloss, picks: [{ word, tags? }], exclude?, first? }` per English meaning (`gloss` is the sense's own definition: the last one, for a sense nested under a heading) (empty for languages without any).
|
|
220
221
|
- `dictionary.meta()`: the language's name, regions, region groups, source, license, build date and counts.
|
|
221
222
|
- `dictionary.pronouns({ listener?, region?, speaker?, exclude? })`: how to say "I", "you", "he/she", "we", plural "you" and "they" depending on who you're talking to (or about), one row per relationship, for languages whose pronouns depend on it (so far Vietnamese; empty for others). Each row has the columns `self`, `addressee`, `third`, `selfPlural`, `addresseePlural` and `thirdPlural`. Each word says where it comes from: a dictionary definition (`source: 'gloss'`, with the `gloss`), a grammar rule for a regular compound the dictionary doesn't list (`source: 'rule'`, e.g. *các anh*, with a `note`), or a hand-written override (`source: 'override'`, with a `note`); and, when it has one, the `gender` of the person it refers to and whether a "we" is `inclusive`.
|
|
222
223
|
|
|
@@ -311,7 +312,7 @@ Generated data isn't committed (it would bloat git history); it's built before p
|
|
|
311
312
|
|
|
312
313
|
### Adding a language
|
|
313
314
|
|
|
314
|
-
1. Add `languages/<code>.ts`: the Kaikki language name, the language's regions (and groups, if any), and how to read regions from Wiktionary's tags. See [`languages/vi.ts`](languages/vi.ts) (dialect areas and a pronoun table) and [`languages/en.ts`](languages/en.ts) (countries with region groups, and trimming a very large language).
|
|
315
|
+
1. Add `languages/<code>.ts`: the Kaikki language name, the language's regions (and groups, if any), and how to read regions from Wiktionary's tags. See [`languages/vi.ts`](languages/vi.ts) (dialect areas and a pronoun table) and [`languages/en.ts`](languages/en.ts) (countries with region groups, and trimming a very large language). [`languages/es.ts`](languages/es.ts) shows countries with region groups (Latin America, Río de la Plata) and `skipFormOf`. Set `placeNames: {}` so its place names translate ("Japan" ↔ its name for Japan). Set `skipFormOf` for heavily inflected languages; for very large ones, see the trimming options in [`scripts/language-config.ts`](scripts/language-config.ts) (`dropTechnical`, `formOfPos`, `keepFormOf`, `maxSensesPerEntry`, `shardLength`, …).
|
|
315
316
|
2. Copy a data package (`packages/vi`) to `packages/<code>` and update its `package.json` and `README.md`.
|
|
316
317
|
3. Add the language's code to `translationLangs` in `languages/en.ts` (so English keeps its translation tables for it) and rebuild English.
|
|
317
318
|
4. `npm run build:data -- <code>`, check the results, add a few real-data tests and evaluation cases, and publish.
|
|
@@ -330,4 +331,4 @@ npm publish -w which-dialect-vi
|
|
|
330
331
|
npm publish -w which-dialect
|
|
331
332
|
```
|
|
332
333
|
|
|
333
|
-
The API loads data versions matching `DATA_VERSION` in `packages/core/src/index.ts` (currently `0.1`). Data-only updates can publish new 0.
|
|
334
|
+
The API loads data versions matching `DATA_VERSION` in `packages/core/src/index.ts` (currently `0.2`; `0.1` up to which-dialect 0.5.6). Data-only updates can publish new 0.2.x data versions without touching the API; bump `DATA_VERSION` when the data format changes.
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export * from './pos.ts';
|
|
|
5
5
|
* Data packages this version of the API reads by default, as a jsDelivr version range.
|
|
6
6
|
* Bump it when the data format changes.
|
|
7
7
|
*/
|
|
8
|
-
export declare const DATA_VERSION = "0.
|
|
8
|
+
export declare const DATA_VERSION = "0.2";
|
|
9
9
|
/** Labels left out of search results unless `exclude` is passed. */
|
|
10
10
|
export declare const DEFAULT_EXCLUDED_LABELS: string[];
|
|
11
11
|
/** Loads one data file by its path inside the data folder, e.g. "meta.json" or "en/no.json". */
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ export * from "./pos.js";
|
|
|
6
6
|
* Data packages this version of the API reads by default, as a jsDelivr version range.
|
|
7
7
|
* Bump it when the data format changes.
|
|
8
8
|
*/
|
|
9
|
-
export const DATA_VERSION = '0.
|
|
9
|
+
export const DATA_VERSION = '0.2';
|
|
10
10
|
/** Labels left out of search results unless `exclude` is passed. */
|
|
11
11
|
export const DEFAULT_EXCLUDED_LABELS = [
|
|
12
12
|
'vulgar', 'offensive', 'derogatory', 'archaic', 'obsolete', 'dated', 'historical', 'rare', 'abbreviation',
|
package/dist/translate.js
CHANGED
|
@@ -445,8 +445,9 @@ export function createTranslator(options = {}) {
|
|
|
445
445
|
return hits;
|
|
446
446
|
}
|
|
447
447
|
async function englishMeanings(sense, from, bridge) {
|
|
448
|
+
// A meaning's gloss is its own (last) definition: a nested sense's first is the heading it shares.
|
|
448
449
|
if (from === 'en')
|
|
449
|
-
return [{ word: sense.lemma, pos: sense.pos, gloss: sense.glosses
|
|
450
|
+
return [{ word: sense.lemma, pos: sense.pos, gloss: sense.glosses.at(-1) ?? '', translations: sense.translations }];
|
|
450
451
|
const found = [];
|
|
451
452
|
for (const term of bridge.slice(0, 3)) {
|
|
452
453
|
for (const entry of await dict('en').lookup(term)) {
|
|
@@ -454,8 +455,9 @@ export function createTranslator(options = {}) {
|
|
|
454
455
|
const own = s.translations?.[from];
|
|
455
456
|
if (!own?.some((t) => t.word === sense.lemma || t.word === sense.word))
|
|
456
457
|
continue;
|
|
457
|
-
|
|
458
|
-
|
|
458
|
+
const gloss = s.glosses.at(-1) ?? '';
|
|
459
|
+
if (!found.some((f) => f.word === entry.word && f.gloss === gloss)) {
|
|
460
|
+
found.push({ word: entry.word, pos: entry.pos, gloss, translations: s.translations });
|
|
459
461
|
}
|
|
460
462
|
}
|
|
461
463
|
}
|
|
@@ -522,6 +524,13 @@ export function createTranslator(options = {}) {
|
|
|
522
524
|
...(entry.frequency !== undefined ? { frequency: entry.frequency } : {}),
|
|
523
525
|
};
|
|
524
526
|
}
|
|
527
|
+
// A hand-picked phrase the dictionary doesn't list: used everywhere, or where its tags say.
|
|
528
|
+
function phraseHit(word, sense, places, meta) {
|
|
529
|
+
return {
|
|
530
|
+
word, pos: sense.pos, gloss: sense.glosses.at(-1) ?? '', regions: places ? [...places] : meta.regions,
|
|
531
|
+
regionTagged: Boolean(places), labels: [], senseIndex: 0, senses: 1, primary: true, phrase: true,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
525
534
|
// The source word's uses in a pronoun table, with the definition each came from.
|
|
526
535
|
function pronounUses(rows, word) {
|
|
527
536
|
const w = word.trim();
|
|
@@ -722,7 +731,8 @@ export function createTranslator(options = {}) {
|
|
|
722
731
|
// Hand-picked words for this meaning go first, in the listed order: a speaker's judgment of the
|
|
723
732
|
// natural word where the ranking puts another first. With a target region, picks tagged for it
|
|
724
733
|
// lead, and picks tagged only for other regions (or words not used there) are left to the
|
|
725
|
-
// ranking. The ranked words follow. (A pronoun-table row, below, still comes before them.)
|
|
734
|
+
// ranking. The ranked words follow. (A pronoun-table row, below, still comes before them.) A pick
|
|
735
|
+
// that isn't a headword is a phrase ("bữa tối"), given as is. Words a row excludes are dropped.
|
|
726
736
|
const rows = pickRows.length
|
|
727
737
|
? await englishMeanings(sense, from, bridge).then((ms) => pickRows.filter((r) => ms.some((m) => m.word === r.word && m.pos === r.pos && m.gloss === r.gloss)))
|
|
728
738
|
: [];
|
|
@@ -737,13 +747,17 @@ export function createTranslator(options = {}) {
|
|
|
737
747
|
continue;
|
|
738
748
|
if (to === from && t.word === sense.lemma && !places?.size)
|
|
739
749
|
continue;
|
|
740
|
-
const hit = scored.get(t.word) ??
|
|
750
|
+
const hit = scored.get(t.word) ??
|
|
751
|
+
(await tableHit(t.word, to, posList, toWanted, exclude, bridge)) ??
|
|
752
|
+
(t.word.includes(' ') ? phraseHit(t.word, sense, places, toMeta) : null);
|
|
741
753
|
if (!hit || hit.labels.some((l) => exclude.includes(l)))
|
|
742
754
|
continue;
|
|
743
755
|
if (!places && !intersects(hit.regions, toWanted))
|
|
744
756
|
continue;
|
|
745
757
|
picked.push({ hit, tagged: Boolean(toWanted && places) });
|
|
746
758
|
}
|
|
759
|
+
for (const w of rows.flatMap((r) => r.exclude ?? []))
|
|
760
|
+
scored.delete(w);
|
|
747
761
|
if (picked.length) {
|
|
748
762
|
const ordered = [...picked.filter((p) => p.tagged), ...picked.filter((p) => !p.tagged)];
|
|
749
763
|
const top = Math.max(0, ...[...scored.values()].map((t) => t.score));
|
package/dist/types.d.ts
CHANGED
|
@@ -63,6 +63,8 @@ export type Hit = {
|
|
|
63
63
|
frequency?: number;
|
|
64
64
|
/** true when the English term is the first meaning listed in the gloss (e.g. "now" in "now, today"). */
|
|
65
65
|
primary: boolean;
|
|
66
|
+
/** true for a hand-picked phrase that isn't a dictionary headword ("bữa tối"); its details are the pick's. */
|
|
67
|
+
phrase?: boolean;
|
|
66
68
|
};
|
|
67
69
|
export type LanguageMeta = {
|
|
68
70
|
/** ISO 639 code, e.g. "vi". */
|
|
@@ -213,12 +215,18 @@ export type CheckerConfig = {
|
|
|
213
215
|
* translating between two other languages reaches it through the English meaning.
|
|
214
216
|
*/
|
|
215
217
|
export type PickRow = {
|
|
216
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* The English headword, part of speech and definition of the meaning, as in the English data. For a
|
|
220
|
+
* nested sense (a heading plus its own definition: "old", "Of a living being…") it's the sense's own,
|
|
221
|
+
* last definition, so senses under one heading are told apart.
|
|
222
|
+
*/
|
|
217
223
|
word: string;
|
|
218
224
|
pos: string;
|
|
219
225
|
gloss: string;
|
|
220
|
-
/** Best first. `tags` name regions like translation-table tags ("Southern"). */
|
|
226
|
+
/** Best first. `tags` name regions like translation-table tags ("Southern"). A pick needn't be a headword ("bữa tối"). */
|
|
221
227
|
picks: TableTranslation[];
|
|
228
|
+
/** Words never given for this meaning (a speaker removed them: wrong or unnatural here). */
|
|
229
|
+
exclude?: string[];
|
|
222
230
|
/** Put this meaning first when the caller gives no `pos` or `meaning` ("got" → "Have/has."). */
|
|
223
231
|
first?: boolean;
|
|
224
232
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "which-dialect",
|
|
3
|
-
"version": "0.5.
|
|
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.",
|
|
3
|
+
"version": "0.5.8",
|
|
4
|
+
"description": "Translate words between languages and dialects, meaning by meaning, starting with Vietnamese (Northern, Central, Southern), Spanish (by country) and English, with Wiktionary data loaded on demand. More languages are planned.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"translation",
|
|
27
27
|
"language-learning",
|
|
28
28
|
"wiktionary",
|
|
29
|
+
"spanish",
|
|
29
30
|
"vietnamese"
|
|
30
31
|
],
|
|
31
32
|
"license": "MIT",
|