@lacspace/nepali-match
v1.1.0
Published
Nepali and English matching for names and keywords in running text: normaliseNe() folds spelling variants (chandrabindu → anusvara, half nasal + consonant → anusvara, long/short vowels, nukta/ZWJ/ZWNJ, NFC, Devanagari digits); whole-word matching that let
Maintainers
Readme
@lacspace/nepali-match
Find names and keywords in Nepali and English text the way a Nepali reader would:
- Spelling variants match: काठमाडौँ, काठमाडौं and काठमाण्डौ, or चन्द्र and चंद्र.
- Words with a postposition match: झापाको is Jhapa and चितवनमा is Chitwan.
- Different words don't match: पर्वतारोही (climber) is not Parbat.
- Short English acronyms are exact-case: "SEE results" counts, "Come and see" doesn't.
It is pure JS with no dependencies, and it is safe for React Native.
import { createMatcher, districtTerms, near, normaliseNe } from "@lacspace/nepali-match";
const areas = createMatcher(districtTerms());
areas.ids("चितवनमा बाढी, झापाको मेचीनगरमा पहिरो"); // ["chitwan", "jhapa"]
areas.test("दुई पर्वतारोही बेपत्ता"); // false
areas.find("काठमाण्डौबाटै आएका")[0];
// { id: "kathmandu", term: "काठमाण्डौ", lang: "ne", index: 0, end: 13, text: "काठमाण्डौबाटै", suffix: "बाटै" }
const exam = ["परीक्षा", "नतिजा", "विज्ञापन", "exam", "result"];
near("लोकसेवा आयोगले निजामती विधेयकमा राय दियो", "लोकसेवा", exam, 60); // null: not exam news
near("लोकसेवा आयोगको खरिदार परीक्षाको नतिजा", "लोकसेवा", exam, 60); // { a, b, gap }
createMatcher([{ id: "see", en: "SEE", ne: "एसईई" }]).test("Come and see"); // false
normaliseNe("काठमाडौँ") === normaliseNe("काठमाडौं"); // trueHow matching works
normaliseNe(text, { loose?, digits? }) applies these steps in order:
- NFC.
- Chandrabindu → anusvara.
- A half nasal before a consonant (ङ्, ञ्, ण्, न्, म्) → anusvara.
- ई/ी → इ/ि and ऊ/ू → उ/ु.
- Nukta, ZWJ, ZWNJ and soft hyphens are removed.
- Devanagari digits → ASCII.
- Whitespace collapses.
loose: true also folds श/ष → स, व → ब and ण → न. hyphenAsSpace: true treats hyphens as spaces, so "Solu Khumbu" matches "Solu-Khumbu" and "Bardaghat-Susta". Latin text is left as it is.
Nepali terms match as whole words:
- Nothing may stand before the word except a space or punctuation.
- After the word, a chain of up to three
POSTPOSITIONSmay follow: मा, को, का, की, ले, लाई, बाट, सँग, देखि, सम्म, तिर, भित्र, हरू, मै, बाटै, नै … Anything else after it means it's a different word. - Turn the built-in list off with
postpositions: false. Add your own withextraSuffixes; withpostpositions: falsethey become the only suffixes allowed. - "Like" (जस्तो) is deliberately not in the list: "काठमाडौंजस्तो ठूलो सहर" is a comparison, not a location.
- With
joinNepali: true, any space in a multi-word Nepali term is optional, so "बर्दघाट सुस्ता पूर्व" also matches "बर्दघाट सुस्तापूर्व".
English terms match as whole words, ignoring case. Exceptions:
- Under the default
caseSensitive: "auto"rule, an all-caps term of 2–5 letters (SEE, NEB, PSC) must match its case exactly. - Set
caseSensitiveper term or per matcher to change this. - An exact-case term longer than 5 letters also matches in ALL CAPS, so "KATHMANDU" in a headline counts. Turn this off with
allCaps: false. - Short acronyms can't tell a shouted headline apart: "COME AND SEE" still matches SEE. Check the case of the surrounding text if your input has shouty headlines.
Overlaps: the longest match wins, so "Nawalparasi West" beats "Nawalparasi". Pass overlaps: true to keep both.
Every match carries index/end offsets into the original text (after NFC), so you can highlight or cut it.
API
createMatcher(terms, options?)returns{ find(text), test(text, id?), ids(text), prepare(text), size }. Compile it once and reuse it.prepare(text, options?)/matcher.prepare(text)normalise text once. Everyfind,test,ids,findTerms,containsandnearaccepts the result in place of a string, so a rules loop over one article normalises it once, not once per rule. A prepared text made with different options is re-prepared automatically.- A term is either a string, or
{ id?, en?, ne?, aliases?, caseSensitive? }.enandneeach take one spelling or an array.
- A term is either a string, or
findTerms(text, terms, options?)andcontains(text, terms, options?)are one-off shortcuts.near(text, a, b, maxChars | options, sameSentence?)returns the closest{ a, b, gap }pair of matches withinmaxChars(default 60), in either order, or null.aandbcan be terms, a compiledMatcher, or aMatch[]you already found in the same text.- By default both must sit in the same sentence. A sentence ends at । ॥ ? ! or a newline, or at "." before a space.
- Dotted abbreviations such as ने.क.पा. and U.S. never end a sentence.
sentenceSpans(text)returns the sentence boundariesnearuses.splitSuffix(word): for example,"जिल्लाहरूमा"→{ stem: "जिल्ला", suffixes: ["हरु", "मा"] }.districtTerms()returns all 77 districts.- Names and ids: names come from
@lacspace/nepali-utils. Ids are slugs such as"nawalparasi-east", andprovinceis a number. - Spellings: each district carries the spellings seen in real copy, plus every alias in
@lacspace/nepali-utils: Kavre/काभ्रे, Rukum East/Rukum Purba/रुकुम पूर्व, Parasi/परासी, Bardaghat Susta West, Kapilbastu, मोरंग/मोरङ… - Case: English district names are exact-case, so "dang it" is not Dang. Names over 5 letters also match in ALL CAPS.
- Known edge: a place that shares a district's name still matches it. For example, Nawalpur in Sarlahi matches the Nawalpur alias of Nawalparasi East.
ambiguous: true: marks Parbat, because पर्वत also means "mountain". Confirm it withnear()or with district/area context before you tag a story.
- Names and ids: names come from
Speed
A ~1,500-character article checked against all 77 districts (258 spellings) takes about 0.16 ms once prepared, and about 0.3 ms from a raw string.
Limits
- The matcher is rule-based and does no stemming beyond postpositions. Verb forms and compounds won't match, which is the point.
- Spellings that differ in more than the folded letters (for example गोर्खा and गोरखा) need an alias.
Built by Lacspace. Licensed under the Lacspace Free Licence v1.0 (see LICENSE).
