which-dialect 0.1.1 → 0.3.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/dist/text.js ADDED
@@ -0,0 +1,57 @@
1
+ // Splitting text into units, sentences and dictionary words: shared by the checker, sentence frames and
2
+ // the journal review. Language-general: units are runs of letters and digits; a language written in
3
+ // syllables groups them into dictionary words by longest match (CheckerConfig.units / maxWordUnits).
4
+ // A run of letters (with their accents), digits and apostrophes: a word, or a syllable in syllable languages.
5
+ export const UNIT = /[\p{L}\p{M}\p{N}'’]+/gu;
6
+ export const SENTENCE_END = /[.!?…]+|\n+/g;
7
+ export function units(text) {
8
+ return [...text.matchAll(UNIT)].map((m) => ({ text: m[0], start: m.index, end: m.index + m[0].length }));
9
+ }
10
+ /** Units grouped into sentences, which end at . ! ? … or a line break. */
11
+ export function sentencesOf(text, all) {
12
+ const ends = [...text.matchAll(SENTENCE_END)].map((m) => m.index);
13
+ const out = [[]];
14
+ let next = 0;
15
+ for (const u of all) {
16
+ while (next < ends.length && ends[next] < u.start) {
17
+ if (out[out.length - 1].length)
18
+ out.push([]);
19
+ next++;
20
+ }
21
+ out[out.length - 1].push(u);
22
+ }
23
+ return out.filter((s) => s.length);
24
+ }
25
+ /** Units join into one word only when just spaces separate them ("thịt heo", not "thịt, heo"). */
26
+ export const joined = (text, a, b) => /^[^\S\n]+$/.test(text.slice(a.end, b.start));
27
+ /**
28
+ * Longest match against the dictionary: at each position, the longest run of units that's a headword
29
+ * ("kết quả" is one word, so its "quả" isn't checked on its own). Unknown single units become words with
30
+ * no entries.
31
+ */
32
+ export async function segment(text, list, maxUnits, lookup) {
33
+ const words = [];
34
+ for (let i = 0; i < list.length;) {
35
+ let taken = null;
36
+ for (let k = Math.min(maxUnits, list.length - i); k >= 1; k--) {
37
+ const span = list.slice(i, i + k);
38
+ if (span.some((u, j) => j > 0 && !joined(text, span[j - 1], u)))
39
+ continue;
40
+ const phrase = span.map((u) => u.text).join(' ');
41
+ const entries = /\d/.test(phrase) ? [] : await lookup(phrase);
42
+ if (entries.length || k === 1) {
43
+ const start = span[0].start;
44
+ const end = span[k - 1].end;
45
+ taken = { text: text.slice(start, end), lower: phrase.toLowerCase(), start, end, units: span, entries };
46
+ break;
47
+ }
48
+ }
49
+ words.push(taken);
50
+ i += taken.units.length;
51
+ }
52
+ return words;
53
+ }
54
+ /** Without accents: "không" → "khong", "đi" → "di". */
55
+ export const plain = (s) => s.normalize('NFD').replace(/\p{M}/gu, '').replace(/đ/g, 'd').replace(/Đ/g, 'D').normalize('NFC');
56
+ /** Keeps a capital first letter when replacing a capitalized word ("Tôi" → "Con"). */
57
+ export const matchCase = (original, replacement) => original.charAt(0) !== original.charAt(0).toLowerCase() ? replacement.charAt(0).toUpperCase() + replacement.slice(1) : replacement;
package/dist/types.d.ts CHANGED
@@ -110,6 +110,91 @@ export type LanguageMeta = {
110
110
  pronouns?: boolean;
111
111
  /** true when the language has hand-picked translations (picks.json, see PickRow). */
112
112
  picks?: boolean;
113
+ /** true when the language has checker settings (checker.json, see CheckerConfig). */
114
+ checker?: boolean;
115
+ /** true when the language has a syllable list (syllables.json: syllable → Zipf frequency, 0 when unknown). */
116
+ syllables?: boolean;
117
+ /** true when the language has sentence frames (frames.json, see FramesData). */
118
+ frames?: boolean;
119
+ };
120
+ /**
121
+ * Pronoun slots in sentence frames ({I}, {YOU}, …) and the pronoun-table column each one fills from, for
122
+ * the listener (or the table's default row).
123
+ */
124
+ export declare const FRAME_PRONOUNS: Record<string, {
125
+ person: PronounPerson;
126
+ inclusive?: boolean;
127
+ }>;
128
+ /**
129
+ * One choice for a frame word slot ({WHERE} → "đâu", "mô"): from a dictionary definition (region and
130
+ * labels from that sense) or a hand-written override with a note. `when: 'respect'` choices are used only
131
+ * when the listener's pronoun-table row is marked respect (Vietnamese "ạ").
132
+ */
133
+ export type FrameWordChoice = {
134
+ word: string;
135
+ gloss?: string;
136
+ source: 'gloss' | 'override';
137
+ regions: string[];
138
+ regionTagged: boolean;
139
+ labels: string[];
140
+ note?: string;
141
+ when?: 'respect';
142
+ };
143
+ /** A frame word slot: what it means, whether a sentence is fine without it, and its choices in order. */
144
+ export type FrameWords = {
145
+ meaning: string;
146
+ optional?: boolean;
147
+ choices: FrameWordChoice[];
148
+ };
149
+ /**
150
+ * A sentence frame in one language: the catalog's id, topic, English ways to say it and slots, and this
151
+ * language's `text` with {slot} placeholders: content slots (lowercase, from `slots`), pronoun slots
152
+ * (FRAME_PRONOUNS) and frame word slots (uppercase, from FramesData.words).
153
+ */
154
+ export type FrameData = {
155
+ id: string;
156
+ topic: string;
157
+ en: string[];
158
+ slots: Record<string, 'noun' | 'verb' | 'adj'>;
159
+ clause?: boolean;
160
+ text: string;
161
+ };
162
+ export type FramesData = {
163
+ frames: FrameData[];
164
+ words: Record<string, FrameWords>;
165
+ };
166
+ export type StoredFrameWordChoice = Omit<FrameWordChoice, 'regions' | 'regionTagged' | 'labels'> & {
167
+ regions?: string[];
168
+ labels?: string[];
169
+ };
170
+ export type StoredFramesData = {
171
+ frames: FrameData[];
172
+ words: Record<string, Omit<FrameWords, 'choices'> & {
173
+ choices: StoredFrameWordChoice[];
174
+ }>;
175
+ };
176
+ /**
177
+ * How the checker reads one language (languages/<lang>.ts `checker`, stored as data/checker.json). Every
178
+ * field is optional: a language without it is read as space-separated words, and gets the checks that
179
+ * only need its dictionary (dialect mixing).
180
+ */
181
+ export type CheckerConfig = {
182
+ /**
183
+ * What spaces separate: 'words' (English), or 'syllables' that group into dictionary words (Vietnamese
184
+ * "thịt heo", "kết quả"). With 'syllables' the build also writes syllables.json for accent suggestions.
185
+ */
186
+ units?: 'words' | 'syllables';
187
+ /** The longest dictionary word to look for, in units (default 3). */
188
+ maxWordUnits?: number;
189
+ /**
190
+ * Pronouns that are reliably pronouns, by pronoun-table column ("tôi", "tao", "mày"). The pronoun checks
191
+ * look only at these: kinship words like Vietnamese "con" (also "child", a classifier) aren't listed.
192
+ */
193
+ pronouns?: Partial<Record<PronounPerson, string[]>>;
194
+ /** Listed pronouns that are also common nouns ("bạn": you, or friend); checked, but only as suggestions. */
195
+ ambiguousPronouns?: string[];
196
+ /** Sentence endings expected when speaking up (pronoun-table rows marked `respect`), e.g. Vietnamese "ạ". */
197
+ politeEndings?: string[];
113
198
  };
114
199
  /**
115
200
  * Hand-picked first choices for one English meaning, in one target language (picks.json). A speaker's
@@ -181,6 +266,8 @@ export type PronounRow = {
181
266
  thirdPlural: PronounChoice[];
182
267
  /** The row to use when the relationship isn't known (neutral words: Vietnamese "tôi", "bạn"). */
183
268
  default?: boolean;
269
+ /** Speaking up to this person (parents, elders, teachers): polite sentence endings are expected. */
270
+ respect?: boolean;
184
271
  /** A caution for the whole row, e.g. that "tao"/"mày" are rude outside close friendships. */
185
272
  warning?: string;
186
273
  };
package/dist/types.js CHANGED
@@ -1,5 +1,18 @@
1
1
  // Shapes of the data. The build script (scripts/build-language.ts) writes the Stored* types to the
2
2
  // data files; the API in index.ts reads them and returns the full Sense/Entry/Hit types.
3
+ /**
4
+ * Pronoun slots in sentence frames ({I}, {YOU}, …) and the pronoun-table column each one fills from, for
5
+ * the listener (or the table's default row).
6
+ */
7
+ export const FRAME_PRONOUNS = {
8
+ I: { person: 'self' },
9
+ YOU: { person: 'addressee' },
10
+ WE: { person: 'selfPlural', inclusive: false },
11
+ WE_INCL: { person: 'selfPlural', inclusive: true },
12
+ YOU_PL: { person: 'addresseePlural' },
13
+ HE_SHE: { person: 'third' },
14
+ THEY: { person: 'thirdPlural' },
15
+ };
3
16
  /** The pronoun table's columns: I, you, he/she, we, you (plural), they. */
4
17
  export const PRONOUN_PERSONS = ['self', 'addressee', 'third', 'selfPlural', 'addresseePlural', 'thirdPlural'];
5
18
  /** Converts a full sense or hit to its stored form. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "which-dialect",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
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",