belmorph
v1.1.0
Published
Zero-dependency Belarusian morphological analyzer
Maintainers
Readme
belmorph
Zero-dependency Belarusian morphological analyzer
A fast, lightweight library for analyzing Belarusian words and their morphological properties.
Features
- Morphological analysis: Parse words to determine part of speech, case, gender, number, tense, aspect, animacy, comparison degree, and other grammatical properties
- Inflection: Generate different grammatical forms of words
- Lexeme generation: Get all possible forms of a word
- Morphological prediction: Unknown words are analyzed using suffix-based patterns
- Zero dependencies: No external runtime dependencies
- TypeScript support: Full type definitions included
- Efficient storage: Uses DAWG (Directed Acyclic Word Graph) for fast lookups
- Browser compatible: Works in Node.js, browsers, and Deno — no native dependencies
- Dictionary covers 6,574 inflection paradigms derived from GrammarDB
Installation
npm install belmorphQuick Start
Node.js — filesystem (bundled dictionary)
import { MorphAnalyzer } from 'belmorph';
import { loadDict } from 'belmorph/node';
const morph = new MorphAnalyzer(loadDict()); // uses bundled dict/
const results = morph.parse('горад');
console.log(results[0].lemma); // 'горад'
console.log(results[0].tags.pos); // 'N' (noun)
// Inflect to different forms
results[0].inflect({ case: 'I', number: 'P' })?.word; // 'гарадамі'
// Full names and short codes are interchangeable
results[0].inflect({ case: 'instrumental', number: 'plural' })?.word; // 'гарадамі'
// Get all forms
const lexeme = results[0].lexeme;
console.log(lexeme.map(r => r.word));Browser / Deno / Node.js — HTTP
import { MorphAnalyzer } from 'belmorph';
// Serve the dict/ folder as static files and point to it:
const morph = await MorphAnalyzer.init('/dict/');
// or from a CDN:
const morph = await MorphAnalyzer.init('https://cdn.example.com/dict/');
const results = morph.parse('горад');When a word is not found in the dictionary, the analyzer falls back to suffix-based prediction — those results have predicted: true and may be less accurate.
API
Loading
| Method | Environment | Description |
|--------|-------------|-------------|
| new MorphAnalyzer(dict) | all | Construct from a pre-loaded DictData object |
| MorphAnalyzer.init(baseUrl?) | all | Async factory — fetches and decompresses dict files over HTTP. Default base URL: '/dict/' |
| loadDict(dir?) from belmorph/node | Node.js only | Synchronous filesystem load. Defaults to the bundled dict/ |
ParseResult
MorphAnalyzer.parse(word) returns an array of ParseResult objects. Each result has:
word— the inflected word formlemma— the dictionary formtags— grammatical properties (Grammemeinterface, seesrc/tags.ts)predicted— whether the analysis was predictedinflect(target)— returns the form matching the given grammemes, ornullpluralize(count, targetCase = 'N')— returns the form agreeing withcount, following Belarusian numeral agreement rules.targetCaseis the case the whole numeral phrase is governed by — nominative by default, or an oblique case (e.g. dative when governed by a verb or preposition)lexeme— all forms of the word
const book = morph.parse('кніга')[0];
book.pluralize(1)!.word; // 'кніга' (1 кніга)
book.pluralize(2)!.word; // 'кнігі' (2 кнігі)
book.pluralize(5)!.word; // 'кніг' (5 кніг)
// Oblique government, e.g. "давяраю пяці кнігам" (dative, governed by the verb)
book.pluralize(5, 'D')!.word; // 'кнігам'Grammeme values follow GrammarDB codes. Both short codes ('I') and full English names ('instrumental') are accepted by inflect().
Building the Dictionary
The library requires a pre-built dictionary. To build it:
git submodule update --init
npm run build:dictThis will create the necessary dictionary files in the dict/ directory.
Testing
npm test # Run tests once
npm run test:watch # Run tests in watch modeTokenizer
Belmorph includes a streaming tokenizer for Belarusian text. It splits text into tokens (words, numbers, punctuation, spaces, hashtags, mentions, emails, and links).
import { Tokenizer, only, TokenType } from 'belmorph/tokens';
// One-shot tokenization
const tokens = Tokenizer.tokenize('Прывітанне, свет! #мова');
console.log(tokens.map(t => t.toString()));
// Streaming (for large documents)
const tz = new Tokenizer();
tz.append('Прывіта');
tz.append('нне, свет!');
const tokens = tz.done();
// Filter by type
const words = only(tokens, TokenType.WORD);
console.log(words.map(w => w.toString())); // ['Прывітанне', 'свет']Token types
| Type | Description |
|------|-------------|
| WORD | Cyrillic or Latin word (subtype: CYRIL, LATIN, MIXED) |
| NUMBER | Integer or decimal number |
| PUNCT | Punctuation mark |
| SPACE | Whitespace (spaces, tabs) |
| NEWLINE | Line break |
| EMAIL | Email address |
| LINK | URL (with protocol or starting with www) |
| HASHTAG | #hashtag |
| MENTION | @mention |
Tokenizer options
const tz = new Tokenizer({
hashtags: true, // recognize #hashtags
mentions: true, // recognize @mentions
emails: true, // recognize emails
links: true, // recognize URLs
});Text Analysis (TextAnalyzer)
TextAnalyzer combines the tokenizer and morphological analyzer for full text parsing.
import { TextAnalyzer, MorphAnalyzer } from 'belmorph';
const morph = await MorphAnalyzer.init('/dict/');
const analyzer = new TextAnalyzer(morph);
const result = analyzer.analyze('Горад прыгожы!');
// [{ token: Token("Горад"), parse: { lemma: "горад", tags: { pos: "N", ... } } },
// { token: Token(" "), parse: null },
// { token: Token("прыгожы"), parse: { lemma: "прыгожы", tags: { pos: "A", ... } } },
// { token: Token("!"), parse: null }]
// Words only
const words = analyzer.words('кніга і тавар');
words.map(w => w.parse!.lemma); // ['кніга', 'тавар']License
This project is dual-licensed:
- Source Code: MIT License
- Dictionary Data: CC BY-SA 4.0 (derived from GrammarDB)
See the LICENSE file for full details.
