farsit 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/LICENSE +21 -0
- package/README.md +84 -2
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/alphabet.d.ts +19 -0
- package/dist/types/constraints.d.ts +16 -0
- package/dist/types/core.d.ts +68 -0
- package/dist/types/diacritics.d.ts +3 -0
- package/dist/types/function-words.d.ts +6 -0
- package/dist/types/generated/data.d.ts +2 -0
- package/dist/types/index.d.ts +49 -0
- package/dist/types/model.d.ts +130 -0
- package/dist/types/morphology.d.ts +21 -0
- package/dist/types/normalize.d.ts +19 -0
- package/dist/types/render.d.ts +11 -0
- package/dist/types/tanvin.d.ts +6 -0
- package/dist/types/wazn.d.ts +39 -0
- package/package.json +76 -4
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persian orthography ↔ internal phoneme alphabet.
|
|
3
|
+
*
|
|
4
|
+
* Internal phonemes (one ASCII char each, borrowed from Persian G2P datasets):
|
|
5
|
+
* vowels a e o (short) A i u (long)
|
|
6
|
+
* S=ش C=چ Z=ژ x=خ q=ق/غ ?=glottal stop (ع/ء/initial ا)
|
|
7
|
+
* Every other phoneme is the plain Latin letter.
|
|
8
|
+
*/
|
|
9
|
+
/** Letters that may carry a phoneme. ZWNJ (\u200c) is a silent in-word boundary. */
|
|
10
|
+
export declare const ZWNJ = "\u200C";
|
|
11
|
+
/** Fixed consonant value of each consonant letter. */
|
|
12
|
+
export declare const CONSONANT: Readonly<Record<string, string>>;
|
|
13
|
+
/**
|
|
14
|
+
* Every way a letter can be realised (before the optional short vowel that may follow it).
|
|
15
|
+
* Order = preference used by the aligner when several alignments are possible.
|
|
16
|
+
*/
|
|
17
|
+
export declare const REALIZATIONS: Readonly<Record<string, readonly string[]>>;
|
|
18
|
+
export declare const SHORT_VOWELS: readonly ["", "a", "e", "o"];
|
|
19
|
+
export declare const isPersianLetter: (ch: string) => boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A constraint narrows the labels the decoder may choose for one letter.
|
|
3
|
+
* Every rule layer (diacritics, morphology, wazn) speaks this one language,
|
|
4
|
+
* so the statistical decoder stays the single place where decisions are made.
|
|
5
|
+
*/
|
|
6
|
+
export interface Constraint {
|
|
7
|
+
/** Which layer produced it — shown by `explain`. */
|
|
8
|
+
source: 'diacritic' | 'morphology' | 'wazn';
|
|
9
|
+
/** Human-readable reason, e.g. "fatha → a". */
|
|
10
|
+
note: string;
|
|
11
|
+
test: (label: string) => boolean;
|
|
12
|
+
}
|
|
13
|
+
export type Constraints = Constraint[][];
|
|
14
|
+
export declare const exact: (label: string) => (l: string) => boolean;
|
|
15
|
+
export declare function toFilters(cs: Constraints): (((l: string) => boolean) | undefined)[];
|
|
16
|
+
export declare function addConstraint(cs: Constraints, i: number, c: Constraint): void;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type Constraints } from './constraints.js';
|
|
2
|
+
import { Model, type ModelData } from './model.js';
|
|
3
|
+
import { type AffixTable, type Segment } from './morphology.js';
|
|
4
|
+
import { type RenderOptions } from './render.js';
|
|
5
|
+
export interface Layers {
|
|
6
|
+
diacritics?: boolean;
|
|
7
|
+
morphology?: boolean;
|
|
8
|
+
wazn?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface WordTrace {
|
|
11
|
+
input: string;
|
|
12
|
+
letters: string;
|
|
13
|
+
/** True when the word is one of the fixed FUNCTION_WORDS. */
|
|
14
|
+
functionWord?: boolean;
|
|
15
|
+
/** True when implicit tanvin (-an) was applied. */
|
|
16
|
+
tanvin?: boolean;
|
|
17
|
+
segments: Segment[];
|
|
18
|
+
wazn?: {
|
|
19
|
+
name: string;
|
|
20
|
+
root: string;
|
|
21
|
+
accepted: boolean;
|
|
22
|
+
scoreGap: number;
|
|
23
|
+
};
|
|
24
|
+
/** Per letter: chosen label, phonemes and what constrained it. */
|
|
25
|
+
steps: {
|
|
26
|
+
letter: string;
|
|
27
|
+
label: string;
|
|
28
|
+
constraints: {
|
|
29
|
+
source: string;
|
|
30
|
+
note: string;
|
|
31
|
+
}[];
|
|
32
|
+
}[];
|
|
33
|
+
phonemes: string;
|
|
34
|
+
output: string;
|
|
35
|
+
score: number;
|
|
36
|
+
}
|
|
37
|
+
export interface CoreData {
|
|
38
|
+
model: ModelData;
|
|
39
|
+
/** Learned ZWNJ affixes (see morphology.ts). */
|
|
40
|
+
affixes: AffixTable;
|
|
41
|
+
/** Accept margin per wazn name (log-prob units). Missing = disabled. */
|
|
42
|
+
waznMargins: Readonly<Record<string, number>>;
|
|
43
|
+
/** Skip wazn when the free decode of a one-segment word is fully familiar (no back-off) and this confident (log-prob per letter). */
|
|
44
|
+
waznGate?: number;
|
|
45
|
+
}
|
|
46
|
+
export declare class Core {
|
|
47
|
+
readonly data: CoreData;
|
|
48
|
+
readonly model: Model;
|
|
49
|
+
constructor(data: CoreData);
|
|
50
|
+
/** Pronounce one Persian word (letters + optional harakat + ZWNJ). `beam` is the Viterbi beam width. */
|
|
51
|
+
word(word: string, layers?: Layers, opt?: RenderOptions, beam?: number): WordTrace;
|
|
52
|
+
/** Confidence gate: leave a one-segment word to the model when its free decode is confident and fully familiar. */
|
|
53
|
+
private gated;
|
|
54
|
+
/** Try every matching pattern on the stem; the best-scoring one within its own dev-tuned margin wins. */
|
|
55
|
+
private bestWazn;
|
|
56
|
+
/** Implicit tanvin on a word ending in bare ا, or undefined when the structural gate says no. */
|
|
57
|
+
private implicitTanvin;
|
|
58
|
+
}
|
|
59
|
+
/** The diacritic + morphology constraints `Core.word` would use (training/tuning helper). */
|
|
60
|
+
export declare function constraintsFor(core: Core, word: string, layers?: Layers): {
|
|
61
|
+
letters: string;
|
|
62
|
+
cs: Constraints;
|
|
63
|
+
};
|
|
64
|
+
/** Split text into Persian words and everything else. */
|
|
65
|
+
export declare function tokenize(text: string): {
|
|
66
|
+
text: string;
|
|
67
|
+
persian: boolean;
|
|
68
|
+
}[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only fixed pronunciations in the library: a small closed class of very common
|
|
3
|
+
* function words (conjunctions, prepositions, pronouns, particles) whose short
|
|
4
|
+
* spelling is ambiguous (و = "va", که = "ke"). Always on. No content words, names or places.
|
|
5
|
+
*/
|
|
6
|
+
export declare const FUNCTION_WORDS: Readonly<Record<string, string>>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* farsit — Persian → Finglish transliteration and URL slugs.
|
|
3
|
+
* Offline, deterministic, zero dependencies.
|
|
4
|
+
*
|
|
5
|
+
* transliterate('استقامت') // "esteghamat"
|
|
6
|
+
* slugify('سفر به اصفهان ۱۴۰۳') // "safar-be-esfahan-1403"
|
|
7
|
+
*/
|
|
8
|
+
import { type Layers, type WordTrace } from './core.js';
|
|
9
|
+
import type { RenderOptions } from './render.js';
|
|
10
|
+
export type { Layers, WordTrace } from './core.js';
|
|
11
|
+
export { normalize } from './normalize.js';
|
|
12
|
+
export { WAZNS } from './wazn.js';
|
|
13
|
+
export interface TransliterateOptions extends RenderOptions {
|
|
14
|
+
/** Switch individual layers off (all on by default). */
|
|
15
|
+
layers?: Layers;
|
|
16
|
+
/** Viterbi beam width (default 8). Higher = slightly more accurate, slower. */
|
|
17
|
+
beam?: number;
|
|
18
|
+
/** Phrase-level ezafe: a word ending in the glide ی (های، درختای، دانشجوی، خانهی) followed by another
|
|
19
|
+
* content word in the same phrase gets the ezafe vowel (haye ziba). Default true. Single words are unaffected. */
|
|
20
|
+
ezafe?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface SlugifyOptions extends TransliterateOptions {
|
|
23
|
+
/** Default "-". */
|
|
24
|
+
separator?: string;
|
|
25
|
+
/** Default true. */
|
|
26
|
+
lowercase?: boolean;
|
|
27
|
+
/** Cut at a word boundary so the slug is at most this long. */
|
|
28
|
+
maxLength?: number;
|
|
29
|
+
/** Keep digits (Persian/Arabic digits become ASCII). Default true. */
|
|
30
|
+
keepDigits?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Transliterate Persian text to Finglish. Non-Persian characters pass through (digits become ASCII). */
|
|
33
|
+
export declare function transliterate(text: string, opt?: TransliterateOptions): string;
|
|
34
|
+
/** Build a URL slug from Persian (or mixed) text. */
|
|
35
|
+
export declare function slugify(text: string, opt?: SlugifyOptions): string;
|
|
36
|
+
export interface Explanation {
|
|
37
|
+
input: string;
|
|
38
|
+
normalized: string;
|
|
39
|
+
output: string;
|
|
40
|
+
tokens: ({
|
|
41
|
+
text: string;
|
|
42
|
+
persian: false;
|
|
43
|
+
} | ({
|
|
44
|
+
persian: true;
|
|
45
|
+
ezafe?: boolean;
|
|
46
|
+
} & WordTrace))[];
|
|
47
|
+
}
|
|
48
|
+
/** Like `transliterate`, but returns every decision each layer made — for debugging and the demo. */
|
|
49
|
+
export declare function explain(text: string, opt?: TransliterateOptions): Explanation;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statistical layer — a compact joint-sequence ("graphone") model decoded with Viterbi.
|
|
3
|
+
*
|
|
4
|
+
* Every letter is emitted together with its label (realization + following short vowel),
|
|
5
|
+
* e.g. ت|te. Two scores are combined for each candidate label:
|
|
6
|
+
*
|
|
7
|
+
* 1. Graphone n-gram P(gᵢ | gᵢ₋ₙ₊₁ … gᵢ₋₁) — left-to-right sequence knowledge
|
|
8
|
+
* 2. Context model P(labelᵢ | letters around i) — sees the letters to the RIGHT too
|
|
9
|
+
*
|
|
10
|
+
* plus hard phonotactic rules (a Persian syllable never starts with two consonants).
|
|
11
|
+
* Callers can restrict the allowed labels per letter (diacritics, wazn, morphology):
|
|
12
|
+
* the decoder then finds the best path that satisfies those constraints.
|
|
13
|
+
*
|
|
14
|
+
* All probabilities are pre-computed offline (tools/) and quantised to one char.
|
|
15
|
+
*/
|
|
16
|
+
export interface ModelData {
|
|
17
|
+
/** n-gram order. */
|
|
18
|
+
order: number;
|
|
19
|
+
/** Label alphabet (phoneme strings). */
|
|
20
|
+
labels: readonly string[];
|
|
21
|
+
/** Graphone inventory: pairs of (letter char, label char), graphone id = index + 1 (0 = word boundary). */
|
|
22
|
+
graphones: string;
|
|
23
|
+
/**
|
|
24
|
+
* n-gram trie in depth-first order. Per node (full-order leaves excepted) its child count,
|
|
25
|
+
* then per child the gap to the previous sibling's graphone id. Back-off weights are not
|
|
26
|
+
* stored: they follow from the probabilities and are recomputed at load.
|
|
27
|
+
*/
|
|
28
|
+
trie: string;
|
|
29
|
+
/** One logprob char per trie child, same order (0 = structural node with no probability). */
|
|
30
|
+
trieProbs: string;
|
|
31
|
+
/** One back-off weight char per history node (depth ≥ 1 with children), in DFS order. */
|
|
32
|
+
trieBows: string;
|
|
33
|
+
/** Context levels [left, right], nested, shortest first. */
|
|
34
|
+
levels: readonly (readonly [number, number])[];
|
|
35
|
+
/** Per level: window chars, count char n, then n × label char. */
|
|
36
|
+
contexts: readonly string[];
|
|
37
|
+
/** Per level: the logprob chars of `contexts`, in order. */
|
|
38
|
+
contextProbs: readonly string[];
|
|
39
|
+
/** Weight of the context model relative to the n-gram. */
|
|
40
|
+
lambda: number;
|
|
41
|
+
/** Quantisation step used for this model's logprobs. */
|
|
42
|
+
q?: number;
|
|
43
|
+
}
|
|
44
|
+
export declare const SYM_BASE = 48;
|
|
45
|
+
/** Log-probabilities are stored as round(-ln p × Q) in one char. */
|
|
46
|
+
export declare let Q: number;
|
|
47
|
+
/** Training-time only: change the quantisation step. */
|
|
48
|
+
export declare const setQ: (q: number) => void;
|
|
49
|
+
export declare const PAD_LEFT = "^";
|
|
50
|
+
export declare const PAD_RIGHT = "$";
|
|
51
|
+
export declare const encSym: (n: number) => string;
|
|
52
|
+
export declare const encBow: (b: number) => string;
|
|
53
|
+
export declare const encLogp: (lp: number, off?: number) => string;
|
|
54
|
+
export type LabelFilter = ((label: string) => boolean) | null | undefined;
|
|
55
|
+
export interface DecodeResult {
|
|
56
|
+
labels: string[];
|
|
57
|
+
score: number;
|
|
58
|
+
}
|
|
59
|
+
export declare class Model {
|
|
60
|
+
readonly data: ModelData;
|
|
61
|
+
/** (node, digit) → child node. Node 0 is the root (empty history). */
|
|
62
|
+
private readonly child;
|
|
63
|
+
/** Per node: log p of its last graphone given its parent (NaN = not stored). */
|
|
64
|
+
private readonly lp;
|
|
65
|
+
/** Per node: back-off weight when used as a history. */
|
|
66
|
+
private readonly bow;
|
|
67
|
+
/** Per node: longest proper suffix that is a history node (-1 for the root). */
|
|
68
|
+
readonly link: Int32Array;
|
|
69
|
+
/** Per node: 1 if it has children (can serve as a decoder state). */
|
|
70
|
+
private readonly hasKids;
|
|
71
|
+
/** Context trie: (node, letter index) → child; per node an optional label distribution. */
|
|
72
|
+
private ctxChild;
|
|
73
|
+
/** Context node → its label distribution as a slice [ctxStart[n], ctxStart[n+1]) of ctxLabel/ctxLp (empty = none). */
|
|
74
|
+
private readonly ctxStart;
|
|
75
|
+
private readonly ctxLabel;
|
|
76
|
+
private readonly ctxLp;
|
|
77
|
+
private distBuf;
|
|
78
|
+
private readonly ctxOffsets;
|
|
79
|
+
private readonly charIdx;
|
|
80
|
+
private codeBuf;
|
|
81
|
+
/** For each letter: candidate graphone digits and label indices. */
|
|
82
|
+
private readonly byLetter;
|
|
83
|
+
private readonly maxL;
|
|
84
|
+
private readonly maxR;
|
|
85
|
+
private readonly shapes;
|
|
86
|
+
private readonly ctxPad;
|
|
87
|
+
/** Reusable back-pointer storage. */
|
|
88
|
+
private backLabel;
|
|
89
|
+
private backPrev;
|
|
90
|
+
/** Recombination table (see decode). */
|
|
91
|
+
private readonly stamp;
|
|
92
|
+
private readonly slotKey;
|
|
93
|
+
private readonly slotIdx;
|
|
94
|
+
private gen;
|
|
95
|
+
private readonly stepKey;
|
|
96
|
+
private readonly stepNext;
|
|
97
|
+
private readonly stepLp;
|
|
98
|
+
/** Reusable hypothesis buffers (current / next), grown on demand. */
|
|
99
|
+
private hyp;
|
|
100
|
+
private hypI;
|
|
101
|
+
private readonly topIdx;
|
|
102
|
+
private topK;
|
|
103
|
+
constructor(data: ModelData);
|
|
104
|
+
hasLetter(ch: string): boolean;
|
|
105
|
+
/** Back-off log-probability of digit `g` after history node `h`. */
|
|
106
|
+
private score;
|
|
107
|
+
/** Decoder state set by `step`. */
|
|
108
|
+
private nextState;
|
|
109
|
+
/**
|
|
110
|
+
* One walk down the back-off chain giving both the log-probability of `g` after state `h`
|
|
111
|
+
* and (in `nextState`) the state after it: the longest known history that is a suffix.
|
|
112
|
+
*/
|
|
113
|
+
private step;
|
|
114
|
+
/**
|
|
115
|
+
* How "familiar" a decoded path is: the fraction of its graphones (end marker included) found
|
|
116
|
+
* without backing off from the full history. Near 1 only for spellings the model has
|
|
117
|
+
* effectively memorised — used to leave such words to the model.
|
|
118
|
+
*/
|
|
119
|
+
familiarity(letters: string, labels: string[]): number;
|
|
120
|
+
/** Decoder state after emitting `g` from state `h`. */
|
|
121
|
+
private advance;
|
|
122
|
+
/** Longest-matching context node (with a distribution) for each position; 0 = none. */
|
|
123
|
+
private contextDists;
|
|
124
|
+
/**
|
|
125
|
+
* Viterbi beam search. `filters[i]` may restrict the labels allowed for letter i.
|
|
126
|
+
* Hypotheses with the same (n-gram state, phonotactic state) are merged; states are reduced
|
|
127
|
+
* to the longest history the model knows, so the merge is exact.
|
|
128
|
+
*/
|
|
129
|
+
decode(letters: string, filters?: LabelFilter[], beam?: number): DecodeResult;
|
|
130
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Constraints } from './constraints.js';
|
|
2
|
+
/**
|
|
3
|
+
* Morphology layer. Persian writes many affixes and compound parts as separate pieces
|
|
4
|
+
* joined by ZWNJ (نیمفاصله): میروم، کتابها، بزرگترین، برنامهنویسی.
|
|
5
|
+
* Pieces with a stable pronunciation become hard constraints, so the statistical model
|
|
6
|
+
* only has to guess the stem. The affix table is learned offline from the training
|
|
7
|
+
* split (tools/train.ts): keys are "P:<prefix>" / "S:<suffix>", values are labels joined by "|".
|
|
8
|
+
*/
|
|
9
|
+
export type AffixTable = Readonly<Record<string, string>>;
|
|
10
|
+
export interface Segment {
|
|
11
|
+
text: string;
|
|
12
|
+
start: number;
|
|
13
|
+
kind: 'prefix' | 'stem' | 'suffix';
|
|
14
|
+
}
|
|
15
|
+
/** Split on ZWNJ, tag known affixes, and constrain their letters. Returns the segments for `explain`. */
|
|
16
|
+
export declare function morphologyConstraints(letters: string, cs: Constraints, table: AffixTable): Segment[];
|
|
17
|
+
/** Learn the affix table from aligned words: frequent ZWNJ pieces with one dominant reading. */
|
|
18
|
+
export declare function learnAffixes(entries: readonly {
|
|
19
|
+
word: string;
|
|
20
|
+
labels: readonly string[];
|
|
21
|
+
}[], minCount?: number, minShare?: number): Record<string, string>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Harakat we understand. Anything else in U+064B..U+065F is dropped. */
|
|
2
|
+
export declare const DIACRITIC: {
|
|
3
|
+
readonly FATHA: "َ";
|
|
4
|
+
readonly KASRA: "ِ";
|
|
5
|
+
readonly DAMMA: "ُ";
|
|
6
|
+
readonly SUKUN: "ْ";
|
|
7
|
+
readonly SHADDA: "ّ";
|
|
8
|
+
readonly FATHATAN: "ً";
|
|
9
|
+
readonly KASRATAN: "ٍ";
|
|
10
|
+
readonly DAMMATAN: "ٌ";
|
|
11
|
+
readonly SUPERSCRIPT_ALEF: "ٰ";
|
|
12
|
+
};
|
|
13
|
+
/** Normalise text: unify letters, convert digits to ASCII, clean ZWNJ, keep useful harakat. */
|
|
14
|
+
export declare function normalize(input: string): string;
|
|
15
|
+
/** Split a normalised word into bare letters and per-letter diacritic marks. */
|
|
16
|
+
export declare function splitDiacritics(word: string): {
|
|
17
|
+
letters: string;
|
|
18
|
+
marks: string[];
|
|
19
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal phonemes → Finglish.
|
|
3
|
+
*
|
|
4
|
+
* Conventions (common Iranian web usage, never "x"):
|
|
5
|
+
* خ kh · ش sh · چ ch · ژ zh · ق/غ gh · آ/ا (long) a · ع/ء dropped · short vowels a e o
|
|
6
|
+
*/
|
|
7
|
+
export interface RenderOptions {
|
|
8
|
+
/** How to spell long /u/: "u" (default, e.g. "nur") or "oo" ("noor"). */
|
|
9
|
+
longU?: 'u' | 'oo';
|
|
10
|
+
}
|
|
11
|
+
export declare function render(phon: string, opt?: RenderOptions): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function tanvinReason(w: string): string | undefined;
|
|
2
|
+
/** Extra label constraints the tanvin reading implies: ta marbuta → …at-an; bare فَعل → CaCC-an. */
|
|
3
|
+
export declare function tanvinShape(w: string): {
|
|
4
|
+
at: number;
|
|
5
|
+
test: (l: string) => boolean;
|
|
6
|
+
}[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Constraints } from './constraints.js';
|
|
2
|
+
/**
|
|
3
|
+
* Arabic wazn (وزن) layer.
|
|
4
|
+
*
|
|
5
|
+
* Most Arabic loanwords in Persian are a three-letter root poured into a pattern:
|
|
6
|
+
* استقامت = است + ق ا م + ت on the pattern استفالهت → "esteghamat".
|
|
7
|
+
* A pattern is written with ف ع ل as root slots; every letter gets a label template:
|
|
8
|
+
* "?e" fixed label · "*" root consonant, no vowel · "*a" root + a · "**e" doubled root + e.
|
|
9
|
+
*
|
|
10
|
+
* Pattern matching only proposes; the statistical model decides. A pattern is accepted
|
|
11
|
+
* when the constrained decode scores within `margin` of the free decode. Margins are
|
|
12
|
+
* tuned on the dev set by tools/train.ts; patterns that do not help are switched off.
|
|
13
|
+
*/
|
|
14
|
+
export interface Wazn {
|
|
15
|
+
name: string;
|
|
16
|
+
/** Pattern letters, root slots as ف ع ل. */
|
|
17
|
+
form: string;
|
|
18
|
+
/** One label template per letter of `form`. */
|
|
19
|
+
labels: readonly string[];
|
|
20
|
+
/** Broken plurals (اخبار، مناطق) never take a second, sound plural ending (ات، ین، ون). */
|
|
21
|
+
brokenPlural?: boolean;
|
|
22
|
+
/** Only used as the stem of an implicit tanvin (فعلاً). */
|
|
23
|
+
tanvinOnly?: boolean;
|
|
24
|
+
/** Root must contain a letter only Arabic loans use (ث ح ذ ص ض ط ظ ع ق غ): keeps Persian فردا out. */
|
|
25
|
+
arabicOnly?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare const WAZNS: readonly Wazn[];
|
|
28
|
+
export interface WaznMatch {
|
|
29
|
+
wazn: Wazn;
|
|
30
|
+
root: string;
|
|
31
|
+
suffix: string;
|
|
32
|
+
labels: string[];
|
|
33
|
+
start: number;
|
|
34
|
+
}
|
|
35
|
+
/** All patterns that fit `stem` (located at `start` in the word). */
|
|
36
|
+
export declare function matchWazn(stem: string, start?: number, enabled?: (w: Wazn) => boolean): WaznMatch[];
|
|
37
|
+
/** Which dev-tuned margin governs a match: tanvin adverbs (کاملا) get their own, shared by all patterns. */
|
|
38
|
+
export declare const marginKey: (m: WaznMatch) => string;
|
|
39
|
+
export declare function waznConstraints(m: WaznMatch, cs: Constraints): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "farsit",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Accurate Persian → Finglish (Latin) transliteration. No AI, no dictionary, zero deps. Slugs included.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"persian",
|
|
7
|
+
"farsi",
|
|
8
|
+
"slug",
|
|
9
|
+
"slugify",
|
|
10
|
+
"transliteration",
|
|
11
|
+
"finglish",
|
|
12
|
+
"pinglish",
|
|
13
|
+
"romanization",
|
|
14
|
+
"g2p",
|
|
15
|
+
"url",
|
|
16
|
+
"seo",
|
|
17
|
+
"zero-dependency",
|
|
18
|
+
"offline",
|
|
19
|
+
"typescript",
|
|
20
|
+
"farsit"
|
|
21
|
+
],
|
|
22
|
+
"homepage": "https://github.com/amiryxe/farsit#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/amiryxe/farsit/issues"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/amiryxe/farsit.git"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"author": "Amir Salehi (amiryxe)",
|
|
32
|
+
"type": "module",
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"main": "./dist/index.cjs",
|
|
35
|
+
"module": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"import": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"default": "./dist/index.js"
|
|
42
|
+
},
|
|
43
|
+
"require": {
|
|
44
|
+
"types": "./dist/index.d.cts",
|
|
45
|
+
"default": "./dist/index.cjs"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"LICENSE",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup && tsc -p tsconfig.build.json && node tools/dts.mjs",
|
|
56
|
+
"train": "tsx tools/train.ts",
|
|
57
|
+
"eval": "tsx tools/eval.ts",
|
|
58
|
+
"bench": "tsx bench/bench.ts",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"typecheck": "tsc --noEmit -p .",
|
|
61
|
+
"demo": "vite demo --port 5180 --host",
|
|
62
|
+
"demo:build": "vite build demo",
|
|
63
|
+
"size": "node tools/size.mjs",
|
|
64
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build && npm run size"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/node": "^26.6.6",
|
|
68
|
+
"@typescript/typescript6": "^6.0.2",
|
|
69
|
+
"tsup": "^9.0.0",
|
|
70
|
+
"tsx": "^4.23.15",
|
|
71
|
+
"typescript": "^7.0.2",
|
|
72
|
+
"vite": "^8.3.5",
|
|
73
|
+
"vitest": "^5.0.5"
|
|
74
|
+
},
|
|
75
|
+
"overrides": {
|
|
76
|
+
"rollup": "4.67.3"
|
|
77
|
+
}
|
|
78
|
+
}
|