Lindera
A morphological analysis library in Rust. Lindera is forked from kuromoji-rs and aims to provide easy installation and concise APIs for tokenizing text in multiple languages.
Key Features
| Feature | Description |
|---|---|
| Morphological Analysis | Viterbi-based segmentation and part-of-speech tagging |
| Multi-language Support | Japanese (IPADIC, IPADIC NEologd, UniDic), Korean (ko-dic), Chinese (CC-CEDICT, Jieba) |
| Dictionary System | Pre-built dictionaries, user dictionaries, and custom dictionary training |
| Text Processing Pipeline | Composable character filters and token filters for flexible text normalization |
| CRF Training | Train custom CRF models for dictionary cost estimation |
| Python Bindings | Use Lindera from Python via PyO3 |
| WebAssembly | Run Lindera in the browser via wasm-bindgen |
| Pure Rust | No C/C++ dependencies; works on any platform Rust supports |
Tokenization Flow
graph LR
subgraph Your Application
T["Text"]
end
subgraph Lindera
CF["Character Filters"]
SEG["Segmenter\n(Dictionary + Viterbi)"]
TF["Token Filters"]
end
T --> CF --> SEG --> TF --> R["Tokens"]
Document Map
| Section | Description |
|---|---|
| Getting Started | Installation, quick start, and examples |
| Dictionaries | Available dictionaries and how to use them |
| Configuration | YAML-based tokenizer configuration |
| User Dictionary | Building and using custom user dictionaries |
| Filters | Character filters and token filters reference |
| CRF Training | Training custom dictionary cost models |
| CLI | Command-line interface reference |
| Architecture | Crate structure and design overview |
| API Reference | Rust API documentation |
| Contributing | How to contribute to Lindera |
Quick Example
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "関西国際空港限定トートバッグ"; let mut tokens = tokenizer.tokenize(text)?; println!("text:\t{}", text); for token in tokens.iter_mut() { let details = token.details().join(","); println!("token:\t{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Run the example:
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize
Output:
text: 関西国際空港限定トートバッグ
token: 関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
token: 限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
token: トートバッグ 名詞,一般,*,*,*,*,*,*,*
License
Lindera is released under the MIT License.
Architecture
Lindera is organized as a Cargo workspace comprising multiple crates. Each crate has a focused responsibility, from low-level CRF computation to high-level CLI and language bindings.
Crate Dependency Graph
graph TB
CRF["lindera-crf\n(CRF Engine)"]
DICT["lindera-dictionary\n(Dictionary Base)"]
TRAINER["lindera-trainer\n(CRF Training)"]
IPADIC["lindera-ipadic"]
UNIDIC["lindera-unidic"]
KODIC["lindera-ko-dic"]
CCCEDICT["lindera-cc-cedict"]
JIEBA["lindera-jieba"]
NEOLOGD["lindera-ipadic-neologd"]
LIB["lindera\n(Segmenter)"]
ANALYSIS["lindera-analysis\n(Analysis Chain)"]
CLI["lindera-cli\n(CLI)"]
BINDINGCORE["lindera-binding-core"]
PY["lindera-python"]
NODEJS["lindera-nodejs"]
RUBY["lindera-ruby"]
PHP["lindera-php"]
WASM["lindera-wasm"]
CRF --> TRAINER
DICT --> TRAINER
TRAINER -.->|"train feature"| LIB
DICT --> IPADIC
DICT --> UNIDIC
DICT --> KODIC
DICT --> CCCEDICT
DICT --> JIEBA
DICT --> NEOLOGD
DICT --> LIB
DICT --> ANALYSIS
DICT --> WASM
IPADIC --> LIB
UNIDIC --> LIB
KODIC --> LIB
CCCEDICT --> LIB
JIEBA --> LIB
NEOLOGD --> LIB
LIB --> ANALYSIS
LIB --> CLI
ANALYSIS --> CLI
LIB --> BINDINGCORE
ANALYSIS --> BINDINGCORE
BINDINGCORE --> PY
BINDINGCORE --> NODEJS
BINDINGCORE --> RUBY
BINDINGCORE --> PHP
BINDINGCORE --> WASM
Crate Overview
| Crate | Type | Description |
|---|---|---|
lindera-crf | Core | Pure Rust CRF (Conditional Random Field) implementation. Supports no_std. Uses rkyv for serialization. |
lindera-dictionary | Core | Dictionary base library. Provides dictionary loading and building. |
lindera-trainer | Core | CRF-based dictionary training pipeline. Builds on lindera-crf and lindera-dictionary; consumed directly or via the lindera facade's train feature. |
lindera | Core | Pure morphological segmenter. Integrates the dictionary crates and provides the Segmenter API. |
lindera-analysis | Core | Lucene-style analysis chain on top of lindera: character filters, token filters, and the Tokenizer that composes them around a Segmenter. |
lindera-cli | Application | Command-line interface for tokenization, dictionary building, and CRF training. |
lindera-binding-core | Core | FFI-independent helpers shared by the five language bindings below. |
lindera-ipadic | Dictionary | Japanese dictionary based on IPADIC. |
lindera-ipadic-neologd | Dictionary | Japanese dictionary based on IPADIC NEologd (includes neologisms). |
lindera-unidic | Dictionary | Japanese dictionary based on UniDic. |
lindera-ko-dic | Dictionary | Korean dictionary based on ko-dic. |
lindera-cc-cedict | Dictionary | Chinese dictionary based on CC-CEDICT. |
lindera-jieba | Dictionary | Chinese dictionary based on Jieba. |
lindera-python | Binding | Python bindings via PyO3. |
lindera-nodejs | Binding | Node.js bindings via NAPI-RS. |
lindera-ruby | Binding | Ruby bindings via Magnus + rb-sys. |
lindera-php | Binding | PHP bindings via ext-php-rs. |
lindera-wasm | Binding | WebAssembly bindings via wasm-bindgen. |
Tokenization Pipeline
Lindera processes text through a multi-stage pipeline:
Input Text
|
v
Character Filters -- Normalize characters (e.g., Unicode normalization, mapping)
|
v
Segmenter -- Segment text into tokens using a dictionary and the Viterbi algorithm
|
v
Token Filters -- Transform tokens (e.g., POS filtering, stop words, stemming)
|
v
Output Tokens
The Segmenter is the core component. It builds a lattice of candidate tokens from the dictionary, then applies the Viterbi algorithm to find the lowest-cost path, producing the most likely segmentation.
Feature Flags
| Feature | Description | Default |
|---|---|---|
mmap | Memory-mapped file support for filesystem-based dictionary loading (opt-in via --mmap/use_mmap; avoids eagerly reading the largest word-list files, not the whole dictionary) | Enabled |
train | CRF-based dictionary training functionality (depends on lindera-crf) | CLI only |
embed-ipadic | Embed the IPADIC dictionary into the binary | Disabled |
embed-cjk | Embed IPADIC + ko-dic + Jieba dictionaries | Disabled |
embed-cjk2 | Embed UniDic + ko-dic + Jieba dictionaries | Disabled |
embed-cjk3 | Embed IPADIC NEologd + ko-dic + Jieba dictionaries | Disabled |
Learn More
- Getting Started -- Installation and first steps
- Core Concepts -- Dictionaries, tokenization, and filters
- Lindera Library -- Segmenter and API
- Lindera Analysis -- Character filters, token filters, and the
Tokenizer - Lindera Dictionary -- Dictionary loading and building
- Lindera Trainer -- CRF-based dictionary training
- Lindera CRF -- The CRF engine
- Lindera CLI -- Command-line interface
- Development Guide -- Build, test, and contribute
Getting Started
This section will guide you through installing Lindera and running your first morphological analysis.
- Installation -- Add Lindera to your project and configure environment variables
- Quick Start -- Tokenize your first text in just a few lines of code
- Examples -- Explore example programs for common use cases
Installation
Put the following in Cargo.toml:
[dependencies]
lindera = "5.0"
[!NOTE] v5.0.0 is the next planned release and has not been published to crates.io yet; the current published version is
4.0.1. This guide describes the v5.0.0 API, which already exists on themainbranch. See Migration v4 to v5 for details.
Dictionary Setup
Lindera requires a pre-built dictionary at runtime. Download a dictionary from GitHub Releases and specify its path when loading:
#![allow(unused)] fn main() { let dictionary = load_dictionary("/path/to/ipadic")?; }
[!TIP] If you want to embed a dictionary directly into the binary (advanced usage), enable the corresponding
embed-*feature flag and load it using theembedded://scheme:#![allow(unused)] fn main() { // Cargo.toml: lindera = { version = "5.0", features = ["embed-ipadic"] } let dictionary = load_dictionary("embedded://ipadic")?; }See Feature Flags for details.
Environment Variables
LINDERA_BUILD_DICTIONARY_CACHE_DIR
The LINDERA_BUILD_DICTIONARY_CACHE_DIR environment variable designates a build-time cache directory for the embedded-dictionary build pipeline. It is read only by the dictionary crates' build scripts and has no effect at runtime.
When set, each build stores two kinds of files under $LINDERA_BUILD_DICTIONARY_CACHE_DIR/<version>/ (where <version> is the dictionary crate version):
- the downloaded distribution archive (validated with MD5; invalid files are re-downloaded)
- the built binary dictionary that gets embedded into the crate
This enables:
- Offline builds: once cached, subsequent builds need no network access
- Faster builds: download and dictionary build are skipped when valid cached files exist
- Reproducible builds: consistent dictionary versions across builds
Usage:
export LINDERA_BUILD_DICTIONARY_CACHE_DIR=/path/to/cache
cargo build --features=embed-ipadic
Notes:
- The directory is managed automatically and is safe to delete; contents are re-downloaded and rebuilt as needed
- Version subdirectories accumulate across upgrades and are not garbage-collected; old ones can be removed freely
- Setting this variable causes dictionary crates to download and build their dictionaries even when no
embed-*feature is enabled (useful for pre-populating the cache)
Deprecated: the previous name
LINDERA_DICTIONARIES_PATHstill works as a fallback (the new name wins when both are set) and will be removed in v6.0.0.
LINDERA_CONFIG_PATH
The LINDERA_CONFIG_PATH environment variable specifies the path to a YAML configuration file for the tokenizer. This allows you to configure tokenizer behavior without modifying Rust code.
export LINDERA_CONFIG_PATH=./resources/config/lindera.yml
See the Configuration section for details on the configuration format.
DOCS_RS
The DOCS_RS environment variable is automatically set by docs.rs when building documentation. When this variable is detected, Lindera creates dummy dictionary files instead of downloading actual dictionary data, allowing documentation to be built without network access or large file downloads.
This is primarily used internally by docs.rs and typically doesn't need to be set by users.
LINDERA_WORKDIR
The LINDERA_WORKDIR environment variable is automatically set during the build process by the lindera-dictionary crate. It points to the directory containing the built dictionary data files and is used internally by dictionary crates to locate their data files.
This variable is set automatically and should not be modified by users.
Quick Start
This example covers the basic usage of Lindera.
It will:
- Create a segmenter in normal mode
- Segment the input text
- Output the tokens
This example uses the embed-ipadic feature, which downloads the IPADIC dictionary and embeds it into the binary automatically at build time — no manual dictionary download is required.
use std::borrow::Cow; use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let text = "関西国際空港限定トートバッグ"; let mut tokens = segmenter.segment(Cow::Borrowed(text))?; println!("text:\t{}", text); for token in tokens.iter_mut() { let details = token.details().join(","); println!("token:\t{}\t{}", token.surface.as_ref(), details); } Ok(()) }
The above example can be run as follows:
% cargo run --features embed-ipadic --example=segment
[!TIP] If you prefer not to embed the dictionary into the binary, download a pre-built IPADIC dictionary from GitHub Releases, extract it to a local directory (e.g.,
/path/to/ipadic), and callload_dictionary("/path/to/ipadic")instead — noembed-ipadicfeature needed in that case. See Feature Flags for details.
You can see the result as follows:
text: 関西国際空港限定トートバッグ
token: 関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
token: 限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
token: トートバッグ 名詞,一般,*,*,*,*,*,*,*
[!NOTE] Character filters, token filters, and the
TokenizerAPI are provided by the companionlindera-analysiscrate (as of v5.0). Addlindera-analysis = "5.0"to your dependencies if you need the analysis chain.
Examples
Lindera includes several example programs that demonstrate common use cases. The source code is available in the examples directory on GitHub.
The tokenize* examples use the Tokenizer and filter APIs, which are
provided by the lindera-analysis crate (as of v5.0).
All examples below are run with the embed-ipadic feature enabled, which downloads the IPADIC dictionary and embeds it into the binary automatically at build time — no manual dictionary download is required.
Available Examples
segment
Basic morphological segmentation with the Segmenter API — the lindera
crate alone is enough.
cargo run -p lindera --features=embed-ipadic --example=segment
tokenize
Basic tokenization using an external IPADIC dictionary. Segments input text and prints each token with its part-of-speech details.
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize
tokenize_with_user_dict
Tokenization with a user dictionary. Shows how to supplement the dictionary with custom entries for domain-specific terms.
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize_with_user_dict
tokenize_with_filters
Tokenization with character filters and token filters. Demonstrates the text processing pipeline, including Unicode normalization, part-of-speech filtering, and other transformations.
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize_with_filters
tokenize_with_config
Tokenization using a YAML configuration file. Shows how to configure the tokenizer declaratively instead of programmatically.
cargo run -p lindera-analysis --features=embed-ipadic --example=tokenize_with_config
Core Concepts
This section explains the fundamental concepts behind Lindera's morphological analysis system.
- Morphological Analysis - How Lindera segments and analyzes text.
- Dictionaries - Dictionary formats supported by Lindera.
- Tokenization - Tokenization modes and N-Best analysis.
- User Dictionary - Adding custom words with user dictionaries.
- Filters - Pre-processing text and post-processing tokens with character and token filters.
Morphological Analysis
What is morphological analysis?
Morphological analysis is the process of breaking down text into its smallest meaningful units (morphemes) and identifying their grammatical properties. For languages like Japanese, Chinese, and Korean -- where words are not separated by spaces -- morphological analysis is an essential first step for natural language processing tasks such as search indexing, text classification, and machine translation.
How Lindera works
Lindera is a dictionary-based morphological analyzer. It uses a pre-compiled system dictionary containing known words along with their costs, and applies the Viterbi algorithm to find the optimal segmentation of input text.
The analysis process works as follows:
- Lattice construction: Lindera scans the input text and looks up all possible words in the dictionary at every position, building a directed acyclic graph (lattice) of candidate segmentations.
- Cost assignment: Each candidate word has an associated word cost (from the dictionary), and each pair of adjacent words has a connection cost (from the connection cost matrix).
- Optimal path search: The Viterbi algorithm finds the path through the lattice with the minimum total cost, producing the best segmentation.
Key terminology
| Term | Description |
|---|---|
| Surface form | The actual text as it appears in the input (e.g., "食べ"). |
| Part-of-speech (POS) | The grammatical category of a word (e.g., noun, verb, particle). Lindera dictionaries provide hierarchical POS tags with up to four levels of subcategories. |
| Reading | The pronunciation of a word, typically in Katakana for Japanese dictionaries. |
| Base form | The uninflected (dictionary) form of a word (e.g., "食べる" for the surface "食べ"). |
| Conjugation | Inflection information for words that conjugate, consisting of a conjugation type and a conjugation form. |
Cost-based segmentation
The Viterbi algorithm selects the segmentation path with the minimum total cost. The total cost of a path is the sum of:
- Word costs: Each word in the dictionary has an associated cost. Lower cost means the word is more likely to appear. Common words tend to have lower costs, while rare words have higher costs.
- Connection costs: The cost of connecting two adjacent words, determined by the right context ID of the left word and the left context ID of the right word.
The algorithm computes:
Total cost = sum of word costs + sum of connection costs
By minimizing this total cost, Lindera finds the most natural segmentation of the input text.
Connection cost matrix
The connection cost matrix stores the cost of transitioning from one word to another. It is a two-dimensional matrix indexed by:
- The right context ID of the preceding word
- The left context ID of the following word
These context IDs encode grammatical information about word boundaries. For example, the connection cost between a noun and a particle is typically low (natural sequence), while the connection cost between two verbs in base form might be high (unnatural sequence).
The connection cost matrix is compiled into binary format as part of the dictionary build process and is loaded at runtime for efficient lookup.
Dictionaries
Lindera supports various dictionaries for Japanese, Korean, and Chinese morphological analysis. Each dictionary is provided as a separate crate.
| Dictionary | Language | Crate | Description |
|---|---|---|---|
| IPADIC | Japanese | lindera-ipadic | The most common dictionary for Japanese |
| IPADIC NEologd | Japanese | lindera-ipadic-neologd | IPADIC with neologisms (new words) |
| UniDic | Japanese | lindera-unidic | Uniform word unit definitions |
| ko-dic | Korean | lindera-ko-dic | Korean morphological analysis |
| CC-CEDICT | Chinese | lindera-cc-cedict | Chinese-English dictionary |
| Jieba | Chinese | lindera-jieba | Jieba-based Chinese dictionary |
Obtaining Dictionaries
Pre-built dictionaries are available for download from GitHub Releases. Download the dictionary archive for your target language and extract it to a local directory.
#![allow(unused)] fn main() { // Load an external dictionary from a local path let dictionary = load_dictionary("/path/to/ipadic")?; }
[!TIP] If you need a self-contained binary without external dictionary files, you can embed dictionaries using the
embed-*feature flags and load them using theembedded://scheme:#![allow(unused)] fn main() { let dictionary = load_dictionary("embedded://ipadic")?; }See Feature Flags for details.
See each dictionary crate's documentation for format details, build instructions, and usage examples.
Tokenization
Lindera provides multiple tokenization modes and supports N-Best analysis for enumerating alternative segmentation candidates.
Tokenization modes
Normal mode
Normal mode performs standard tokenization based on dictionary entries. Compound words that exist as single entries in the dictionary are kept as-is.
Example -- tokenizing "関西国際空港限定トートバッグ" in Normal mode:
関西国際空港 | 限定 | トートバッグ
The compound noun "関西国際空港" (Kansai International Airport) is preserved as a single token because it exists as one entry in the dictionary.
Decompose mode
Decompose mode further breaks down compound nouns into their constituent parts, even when the compound exists as a dictionary entry.
Example -- tokenizing "関西国際空港限定トートバッグ" in Decompose mode:
関西 | 国際 | 空港 | 限定 | トートバッグ
The compound "関西国際空港" is decomposed into "関西", "国際", and "空港".
Selecting a mode
In Rust, specify the mode when creating a Segmenter:
#![allow(unused)] fn main() { use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera::dictionary::load_dictionary; let dictionary = load_dictionary("embedded://ipadic")?; // Normal mode let segmenter = Segmenter::new(Mode::Normal, dictionary, None); // Decompose mode let segmenter = Segmenter::new(Mode::Decompose(Default::default()), dictionary, None); }
With the CLI, use the --mode flag:
echo "関西国際空港限定トートバッグ" | lindera tokenize --dict embedded://ipadic --mode normal
echo "関西国際空港限定トートバッグ" | lindera tokenize --dict embedded://ipadic --mode decompose
[!NOTE] The
embedded://scheme requireslindera-clito be built with the correspondingembed-*feature (e.g.cargo install lindera-cli --features=embed-ipadic). A default build oflindera-clidoes not enable anyembed-*feature, and passing--dict embedded://ipadicwithout it fails withInvalid dictionary scheme: embedded.
N-Best tokenization
N-Best tokenization enumerates the top N tokenization candidates ordered by total path cost (lower cost = better segmentation). This is useful when the best result is ambiguous, or when you want to explore alternative interpretations of the input text.
Algorithm
N-Best tokenization is based on the Forward-DP Backward-A* algorithm, which is compatible with MeCab's N-Best implementation. The forward pass computes optimal costs using dynamic programming, and the backward pass uses A* search to enumerate paths in order of increasing total cost.
Parameters
The tokenize_nbest method accepts the following parameters:
| Parameter | Type | Description |
|---|---|---|
text | &str | The text to tokenize. |
n | usize | Number of N-best results to return. |
unique | bool | When true, deduplicates results that produce the same word boundary positions. |
cost_threshold | Option<i64> | When Some(threshold), only returns paths with cost within best_cost + threshold. |
Rust API example
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "すもももももももものうち"; // Get top 3 tokenization results let results = tokenizer.tokenize_nbest(text, 3, false, None)?; for (rank, (tokens, cost)) in results.iter().enumerate() { println!("--- NBEST {} (cost={}) ---", rank + 1, cost); for token in tokens { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } } Ok(()) }
Output:
--- NBEST 1 (cost=21245) ---
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
--- NBEST 2 (cost=24541) ---
...
CLI example
echo "すもももももももものうち" | lindera tokenize --dict embedded://ipadic -N 3
Lattice reuse
For repeated tokenization, you can reuse a Lattice to reduce memory allocations:
#![allow(unused)] fn main() { use lindera_dictionary::viterbi::Lattice; let mut lattice = Lattice::default(); let results = tokenizer.tokenize_nbest_with_lattice(text, &mut lattice, 3, false, None)?; }
User Dictionary
A user dictionary is a supplementary dictionary that allows you to register custom words alongside the system dictionary. This is useful for domain-specific terms, brand names, proper nouns, or any words that are not in the default system dictionary.
CSV format
The simplest user dictionary format is a CSV file with three columns:
<surface>,<part_of_speech>,<reading>
Example CSV content
東京スカイツリー,カスタム名詞,トウキョウスカイツリー
東武スカイツリーライン,カスタム名詞,トウブスカイツリーライン
とうきょうスカイツリー駅,カスタム名詞,トウキョウスカイツリーエキ
Each dictionary type (IPADIC, UniDic, ko-dic, etc.) also supports a detailed CSV format with full control over context IDs, costs, and all feature fields. See the Dictionaries section for the detailed format of each dictionary type.
Rust API example
use std::fs::File; use std::path::PathBuf; use lindera::dictionary::{Metadata, load_dictionary, load_user_dictionary}; use lindera::error::LinderaErrorKind; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let user_dict_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../resources") .join("user_dict") .join("ipadic_simple_userdic.csv"); let metadata_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../lindera-ipadic") .join("metadata.json"); let metadata: Metadata = serde_json::from_reader( File::open(metadata_file) .map_err(|err| LinderaErrorKind::Io.with_error(anyhow::anyhow!(err))) .unwrap(), ) .map_err(|err| LinderaErrorKind::Io.with_error(anyhow::anyhow!(err))) .unwrap(); let dictionary = load_dictionary("embedded://ipadic")?; let user_dictionary = load_user_dictionary(user_dict_path.to_str().unwrap(), &metadata)?; let segmenter = Segmenter::new( Mode::Normal, dictionary, Some(user_dictionary), // Using the loaded user dictionary ); // Create a tokenizer. let tokenizer = Tokenizer::new(segmenter); // Tokenize a text. let text = "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です"; let mut tokens = tokenizer.tokenize(text)?; // Print the text and tokens. println!("text:\t{}", text); for token in tokens.iter_mut() { let details = token.details().join(","); println!("token:\t{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Output:
text: 東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です
token: 東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
token: の 助詞,連体化,*,*,*,*,の,ノ,ノ
token: 最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
token: は 助詞,係助詞,*,*,*,*,は,ハ,ワ
token: とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
token: です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
Note that the base_form field (the 7th CSV/detail field) is * rather than the
surface form. The simple three-column user-dictionary schema only supplies
surface, part_of_speech, and reading — every other field, including
base_form, is filled with the dictionary's metadata.default_field_value
(* for IPADIC).
Building a user dictionary with CLI
You can build a user dictionary from CSV to binary format using the CLI:
lindera build --src <source_dir> --dest <dest_dir> --metadata <metadata.json> --user
Binary vs CSV user dictionary
- CSV format: Loaded and parsed at runtime. Convenient for development and small dictionaries.
- Binary format: Pre-compiled for faster loading. Recommended for production use with large user dictionaries.
Both formats can be specified when creating a Segmenter. The binary format skips the CSV parsing step, resulting in faster startup times.
Filters
Lindera's analysis pipeline (the Tokenizer provided by the lindera-analysis crate) has two extension points around the segmenter: character filters, which transform the raw input text before tokenization, and token filters, which transform the list of tokens after tokenization.
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
Character filters
Character filters preprocess the input text before it reaches the segmenter. They're typically used to normalize text so that tokenization is more consistent -- for example, converting full-width characters to half-width, canonicalizing Unicode representations, or expanding Japanese iteration marks into their repeated form. Because a character filter can change the length of the text, Lindera tracks every transformation it makes and corrects each token's byte offsets back to positions in the original, unfiltered text.
Token filters
Token filters postprocess the list of tokens produced by the segmenter. They're typically used to normalize or reduce the token list for search and analysis -- for example, replacing a token with its base (dictionary) form, converting a token's surface text between hiragana and katakana, removing tokens by part-of-speech tag, or removing stop words.
Configuring filters
Every filter -- character or token -- is identified by a kind string and configured with a JSON args object of filter-specific parameters. Filters run in the order they're added, and there are two ways to add them:
-
YAML configuration file: list filters under the
character_filtersandtoken_filterskeys.character_filters: - kind: unicode_normalize args: kind: nfkc token_filters: - kind: japanese_base_form -
Rust API: append filters to a
Tokenizer, in order, withappend_character_filterandappend_token_filter.#![allow(unused)] fn main() { // Character filters run first, transforming the raw input text; // token filters run last, transforming the resulting token list. tokenizer .append_character_filter(BoxCharacterFilter::from(unicode_normalize_char_filter)) .append_token_filter(BoxTokenFilter::from(japanese_base_form_filter)); }
Available filters
Lindera ships 4 character filters and 18 token filters, covering Japanese, Korean, and general-purpose text normalization:
| Category | Filters |
|---|---|
| Character filters | unicode_normalize, japanese_iteration_mark, mapping, regex |
| Token filters -- normalization | japanese_base_form, japanese_reading_form, korean_reading_form, japanese_kana, japanese_katakana_stem, japanese_number, mapping, remove_diacritical_mark, lowercase, uppercase |
| Token filters -- tag-based filtering | japanese_keep_tags, japanese_stop_tags, korean_keep_tags, korean_stop_tags |
| Token filters -- word-based filtering | keep_words, stop_words |
| Token filters -- structural transformation | japanese_compound_word, length |
See the Filters reference for what each filter does, its parameters, and runnable YAML and Rust API examples.
Lindera CRF
Lindera CRF is a pure Rust implementation of Conditional Random Fields (CRFs), forked from rucrf. It provides a trainer and an estimator for CRFs with support for lattice structures.
Key Features
- Lattices with variable length edges
- L1, L2, and Elastic Net regularization
- Multi-threaded training
- Zero-copy deserialization with rkyv
no_stdsupport via--no-default-features --features alloc
Contents
- Architecture -- Internal structure and key components
- API Reference -- API documentation
Changes from rucrf
- Serialization backend: Switched from
bincodetorkyvfor zero-copy deserialization - Elastic Net regularization: Added
Regularization::ElasticNetcombining L1 and L2 penalties - Rust 2024 edition: Updated to Rust 2024 edition
- Dependency updates: Updated
argmin,argmin-math,hashbrown, etc.
Architecture
Module Structure
lindera-crf/src/
├── lib.rs # Public API re-exports
├── feature.rs # FeatureSet, FeatureProvider
├── lattice.rs # Edge, Node, Lattice
├── model.rs # RawModel, MergedModel, Model trait
├── trainer.rs # Trainer, Regularization enum
├── errors.rs # Error types
├── forward_backward.rs # Forward-backward algorithm
├── math.rs # Mathematical utilities (logsumexp)
├── optimizers.rs # Declares the optimizers module
├── optimizers/
│ └── lbfgs.rs # L-BFGS optimization
└── utils.rs # Utility traits
Key Components
FeatureProvider / FeatureSet
Manage per-label feature sets. Each FeatureSet holds unigram features and left/right bigram features for a given label (feature IDs only, no weights). FeatureProvider maps label IDs to FeatureSet instances. Weights are held separately, on RawModel (weights, unigram_weight_indices, bigram_weight_indices).
Lattice / Edge / Node
Lattice structure with variable-length edges for sequence labeling. Edge represents a candidate span with a label, while Node aggregates edges at a given position. The Lattice is constructed from input data and used by the model to find the best path.
Trainer
Trains a CRF model using L-BFGS optimization with configurable regularization. The trainer accepts labeled lattice examples, computes gradients via the forward-backward algorithm, and iteratively updates model weights.
Regularization
Configurable regularization strategies:
- L1: Sparse models via L1 penalty
- L2: Smooth models via L2 penalty
- ElasticNet: Combines L1 and L2 with a configurable
l1_ratio
Model (trait)
Interface for searching the best path through a lattice. Two implementations are provided:
- RawModel: Stores weights in a flat vector indexed by feature ID
- MergedModel: Optimized for inference; merges feature weights into a compact representation serializable with rkyv, using
MergedFeatureSetas its per-label element type
MergedFeatureSet holds the pre-summed unigram weight for a label, along with its left_id/right_id bigram connection IDs into MergedModel's bigram weight matrix.
Forward-backward Algorithm
Computes alpha (forward) and beta (backward) values over the lattice. Used during training to calculate expected feature counts and gradients.
Feature Flags
| Feature | Description | Default |
|---|---|---|
alloc | Alloc support for no_std | No |
std | Standard library support (implies alloc) | No |
train | Training functionality (L-BFGS, multi-threading, logging) | Yes |
API Reference
The API reference is available. Please see following URL:
Lindera Dictionary
Lindera Dictionary is the base library for morphological analysis dictionaries. It provides dictionary loading, building, and Viterbi-based segmentation. CRF-based dictionary training lives in the separate lindera-trainer crate.
Key Features
- Dictionary loading from filesystem or embedded data
- Dictionary building from MeCab-format CSV source files
- Viterbi algorithm for optimal segmentation
- N-best path generation (Forward-DP Backward-A*)
- Memory-mapped file support
Contents
- Architecture -- Internal structure and key components
- API Reference -- API documentation
Architecture
Module Structure
lindera-dictionary/src/
├── lib.rs # Public API
├── dictionary.rs # Dictionary, UserDictionary
├── builder.rs # DictionaryBuilder
├── loader.rs # DictionaryLoader trait, FSDictionaryLoader
├── viterbi.rs # Lattice, Edge, Viterbi segmentation
├── nbest.rs # NBestGenerator (Forward-DP Backward-A*)
├── mode.rs # Mode (Normal/Decompose), Penalty
├── error.rs # LinderaError, LinderaErrorKind
├── assets.rs # Download and file management
├── macros.rs # embedded_dictionary! macro shared by dictionary crates
├── dictionary/
│ ├── character_definition.rs # Character type definitions
│ ├── connection_cost_matrix.rs # Connection cost matrix
│ ├── context_id_map.rs # ContextIdMap: connection-cost context-ID remap
│ ├── prefix_dictionary.rs # Double-array trie dictionary
│ ├── unknown_dictionary.rs # Unknown word handling
│ ├── metadata.rs # Dictionary metadata
│ └── schema.rs # Schema definitions
Key Components
Dictionary / UserDictionary
Main data structures holding the compiled dictionary data. A Dictionary contains the character definitions, connection cost matrix, prefix dictionary (double-array trie), and unknown word dictionary. UserDictionary allows users to add custom vocabulary on top of the system dictionary.
DictionaryBuilder
Fluent API for building dictionaries from source CSV files. It compiles MeCab-format dictionary sources into the binary format used at runtime. Its four build stages (metadata, unknown dictionary, prefix dictionary, connection cost matrix) run concurrently on scoped threads on non-wasm targets, falling back to a sequential build on wasm (which has no OS threads); the concurrent path has higher peak memory, since all four stages' working sets are held at once.
DictionaryLoader / FSDictionaryLoader
DictionaryLoader is a trait for loading compiled dictionaries. FSDictionaryLoader is the filesystem-based implementation that reads dictionary files from a directory, with optional memory-mapped file support.
Embedded Dictionary Macro
The embedded_dictionary! macro (lindera-dictionary/src/macros.rs, #[macro_export]) generates the boilerplate each dictionary crate needs to bake its compiled dictionary into the binary: a load() function that reads the dictionary components via include_bytes! and a loader struct implementing DictionaryLoader. Every dictionary crate's embedded.rs (lindera-ipadic, lindera-ipadic-neologd, lindera-unidic, lindera-ko-dic, lindera-cc-cedict, lindera-jieba) invokes this macro instead of duplicating the loading logic.
Viterbi (Lattice, Edge)
Builds a lattice of candidate tokens from the input text and finds the optimal segmentation path using the Viterbi algorithm. Each Edge in the lattice represents a candidate token with associated costs (word cost + connection cost).
NBestGenerator
Generates N-best segmentation paths using the Forward-DP Backward-A* algorithm. This enables applications to consider alternative segmentations beyond the single best path.
Mode
Controls tokenization behavior:
- Normal: Standard tokenization using the optimal Viterbi path
- Decompose: Further splits compound nouns based on configurable
Penaltythresholds
Context ID Remapping
Dictionary metadata (lindera-dictionary/src/dictionary/metadata.rs) carries a connection_id_mapping: bool flag and an optional context_id_map: Option<ContextIdMap>. When a dictionary crate (e.g. lindera-unidic) enables connection_id_mapping, DictionaryBuilder relabels the connection matrix's left/right context IDs by access frequency at build time, so that frequently-used connection-cost cells cluster together for better cache locality; DictionaryBuilder::with_context_id_freq attaches an optional bundled frequency histogram used to rank the IDs, and the remap itself is computed in lindera-dictionary/src/builder/context_id_remap.rs. ContextIdMap (lindera-dictionary/src/dictionary/context_id_map.rs) holds the resulting left/right permutation and is persisted into the built metadata.json so the same mapping can be reapplied later. Because user dictionaries are always compiled in the original, un-remapped ID space, UserDictionary::remap_context_ids (lindera-dictionary/src/dictionary.rs) uses the persisted ContextIdMap to relabel a user dictionary's context IDs into the same space as the system dictionary it is attached to. The remap is a bijective relabeling, so it does not change tokenization output, only lookup locality.
Training
The CRF-based dictionary training pipeline lives in the separate lindera-trainer crate, which builds on this crate's runtime types. See the training pipeline documentation for details.
Feature Flags
| Feature | Description | Default |
|---|---|---|
mmap | Memory-mapped file support for filesystem-based dictionary loading (only the word-list files stay lazily paged; the connection-cost matrix and trie are always fully materialized) | Yes |
build_rs | HTTP download for dictionary sources | No |
ctxfreq | Experimental: instruments connection-matrix access-frequency profiling, used to build the context-ID frequency remap | No |
API Reference
The API reference is available. Please see following URL:
Lindera Trainer
Lindera Trainer implements the CRF-based dictionary training pipeline (lindera train), turning an annotated corpus and a seed lexicon into a trained model that can be exported to MeCab-format dictionary source files and compiled into a binary dictionary. It builds on the runtime types of lindera-dictionary and the CRF core of lindera-crf. The crate can be used directly, or through the lindera facade's train feature, which re-exports it as lindera::dictionary::trainer.
Key Features
- CRF-based weight learning via
lindera-crf, with L1, L2, and Elastic Net regularization - MeCab-compatible feature template parsing (
feature.def:%F[n],%L[n],%R[n],%w,%u,%l,%r, and their optional?variants) - MeCab-compatible 3-section feature rewriting (
rewrite.def: unigram / left / right rewrite rules) - Dictionary-format agnostic: works with any lexicon whose columns follow
surface,left_id,right_id,cost,feature...(IPADIC, UniDic, ko-dic, CC-CEDICT, etc.) - Automatic unknown-word categorization driven by
char.defcharacter categories - Connection cost matrix and MeCab-compatible cost conversion (
tocost) derived from learned CRF weights - Zero-copy
rkyvbinary model serialization, with legacy JSON fallback on read - Direct export of Lindera/MeCab dictionary source files (
lex.csv,matrix.def,unk.def,char.def,feature.def,rewrite.def,left-id.def,right-id.def)
Contents
- Architecture -- Internal structure and key components
- API Reference -- API documentation
Architecture
Module Structure
lindera-trainer/src/
├── lib.rs # Trainer: CRF training orchestration; public API re-exports
├── config.rs # TrainerConfig: parses seed lexicon, char.def, feature.def, rewrite.def
├── corpus.rs # Corpus, Example, Word: training data representation
├── feature_extractor.rs # FeatureExtractor: feature template parsing and feature ID management
├── feature_rewriter.rs # DictionaryRewriter: MeCab-compatible 3-section rewrite
└── model.rs # Model, SerializableModel: trained model storage, serialization, dictionary output
Key Components
TrainerConfig
Parses the five training input files -- seed lexicon (lex.csv), character definition (char.def), unknown-word definition (unk.def), feature template (feature.def), and rewrite rules (rewrite.def) -- into the configuration consumed by Trainer. TrainerConfig::from_readers (or from_paths, its file-path convenience wrapper) extracts the surface/feature vocabulary from the seed lexicon, builds a FeatureExtractor from the parsed feature templates, builds a DictionaryRewriter from the rewrite rules, and assembles a minimal in-memory lindera_dictionary::dictionary::Dictionary (character definition, unknown-word categories, and a system lexicon) from the same input. It also exposes surfaces(), surface_features(), get_features(), a user lexicon accessible via user_lexicon() / add_user_lexicon_entry() / load_user_lexicon_from_content(), and metadata().
Corpus / Example / Word
Represent annotated training data. Word pairs a surface form with its (comma-joined) feature string. Example is one training sentence, built from a Vec<Word> whose surfaces are concatenated to reconstruct the original sentence text. Corpus is a collection of Examples; Corpus::from_reader parses tab-separated surface<TAB>features lines, treating blank lines or literal EOS lines as sentence boundaries.
FeatureExtractor
Parses MeCab-compatible feature templates and manages the mapping from generated feature strings to interned, NonZeroU32 feature IDs. Supported template placeholders are %F[n] / %F?[n] (feature field at index n, the ? form skipped when the value is *), %t (character category), %w (surface form, unigram only), %u / %l / %r (full rewritten unigram/left/right feature string), and %L[n] / %L?[n] / %R[n] / %R?[n] for bigram left/right context fields. extract_unigram_feature_ids[_with_ctx], extract_left_feature_ids[_with_ctx], and extract_right_feature_ids[_with_ctx] apply the parsed templates to a feature vector (plus, for bigram extraction, an optional TemplateContext carrying surface/ufeature/lfeature/rfeature) and return the resulting feature IDs, creating new IDs on first use.
DictionaryRewriter
Implements MeCab's 3-section rewrite.def format: [unigram rewrite], [left rewrite], and [right rewrite] sections, each holding an ordered list of pattern<TAB>replacement rules built into a FeatureRewriter prefix trie (via the internal FeatureRewriterBuilder). DictionaryRewriter::from_reader parses all three sections (falling back to treating an unsectioned file as legacy right-rewrite-only content, for backward compatibility with older Lindera rewrite.def files). rewrite() applies all three rewriters to a feature string and returns (ufeature, lfeature, rfeature), passing a section through unchanged when no rule matches; rewrite_cached() memoizes results per input feature string (MeCab's rewrite2 equivalent).
Model / SerializableModel
Model is the trained model produced by Trainer::train. It owns the trained lindera_crf::RawModel, the TrainerConfig used to train it, the extracted feature weights and labels, and any user lexicon entries added afterward via read_user_lexicon. It converts learned CRF weights into MeCab-compatible integer costs (tocost(weight, cost_factor) = clamp(-weight * cost_factor, -32767, 32767)), with the cost factor computed to make full use of the i16 range (calculate_cost_factor). Model can serialize itself (write_model) and export dictionary source files directly.
SerializableModel is the plain-data, rkyv-serializable counterpart returned by Model::read_model when loading a previously trained model back from a reader (as done by the lindera export CLI command). It carries the same trained information as Model -- feature weights, labels, POS info, connection cost matrix, unknown-word categories, preserved char.def/feature.def/rewrite.def content, cost factor, and left/right context ID maps -- as owned data, without a TrainerConfig, and exposes its own writer methods for producing the dictionary export files.
Model public methods
| Method | Description |
|---|---|
read_user_lexicon<R: Read>(&mut self, rdr: R) -> Result<()> | Loads a user-defined lexicon (same surface,left_id,right_id,cost,feature... CSV format as the seed lexicon) into the model so that later exports can assign it inferred connection IDs and costs. Must be called before writing the dictionary if a user lexicon is needed. |
write_model<W: Write>(&self, writer: &mut W) -> Result<()> | Serializes the full trained model (feature weights, labels, POS info, connection matrix, unknown-word categories, preserved definition file contents, cost factor, left/right ID maps) to rkyv binary format. |
read_model<R: Read>(reader: R) -> Result<SerializableModel> | Associated function. Deserializes a SerializableModel from data written by write_model, trying rkyv first and falling back to legacy JSON; backfills feature_sets from feature_weights for older models that predate that field. |
write_dictionary<W1, W2, W3, W4>(&self, lexicon_wtr, connector_wtr, unk_handler_wtr, user_lexicon_wtr) -> Result<()> | Convenience method that writes the lexicon, connection cost matrix, unknown-word dictionary, and user lexicon in one call. |
write_lexicon<W: Write>(&self, writer: &mut W) -> Result<()> | Writes lex.csv-style entries (surface,left_id,right_id,cost,features...) for every seed vocabulary entry, using the connection IDs and costs from the merged CRF model. |
write_connection_costs<W: Write>(&self, writer: &mut W) -> Result<()> | Writes a dense matrix.def-style connection cost matrix over every (right_id, left_id) pair (including BOS/EOS); pairs never seen during training get the maximum penalty cost (i16::MAX) so Viterbi avoids untrained transitions. |
write_unknown_dictionary<W: Write>(&self, writer: &mut W) -> Result<()> | Writes unk.def-style entries for each unknown-word character category, using the learned connection IDs/costs together with the category's feature string from unk.def. |
get_unknown_word_cost(&self, category: usize) -> i32 | Returns the configured cost for an unknown-word category index (from unk.def's cost column), defaulting to 2000 if the category has no configured cost. |
num_features(&self) -> usize | Number of feature weights held by the trained model. |
num_labels(&self) -> usize | Number of labels (vocabulary surfaces plus unknown-word categories) in the trained model. |
raw_model(&self) -> &lindera_crf::RawModel | Access to the underlying lindera-crf raw model, for advanced or lower-level operations. |
write_bigram_details<L: Write, R: Write, C: Write>(&self, left_wtr, right_wtr, cost_wtr) -> Result<()> | Writes three diagnostic files describing bigram connection features and costs (left feature list, right feature list, and left/right feature-pair costs), for dictionary optimization and debugging. |
evaluate(&self, test_lattices: &[lindera_crf::Lattice]) -> f64 | Returns the mean absolute value of the raw model's feature weights as a simple evaluation score. The test_lattices argument is currently unused; it does not yet score the model against held-out data. |
write_dictionary_buffers(&self, lexicon, connector, unk_handler, user_lexicon: &mut Vec<u8>) -> Result<()> | Serializes labels, feature weights, user-entry count, and surfaces into four raw rkyv byte buffers -- a lower-level alternative to the CSV-oriented write_dictionary. |
write_left_id_def<W: Write>(&self, writer: &mut W) -> Result<()> | Writes left-id.def, mapping each learned left-context ID to its feature string, with 0 BOS/EOS as the first line. |
write_right_id_def<W: Write>(&self, writer: &mut W) -> Result<()> | Writes right-id.def, mapping each learned right-context ID to its feature string, with 0 BOS/EOS as the first line. |
SerializableModel public methods
Available on the value returned by Model::read_model, for re-exporting dictionary source files from a previously trained and serialized model (used by the lindera export CLI command):
| Method | Description |
|---|---|
write_lexicon<W: Write>(&self, writer: &mut W) -> Result<()> | Writes lex.csv-style entries from the stored feature_sets, labels, and pos_info, skipping the trailing unknown-word category labels. |
write_connection_costs<W: Write>(&self, writer: &mut W) -> Result<()> | Writes the dense matrix.def-style connection cost matrix from the stored connection_matrix, max_left_id, and max_right_id. |
write_unknown_dictionary<W: Write>(&self, writer: &mut W) -> Result<()> | Writes unk.def-style entries for each stored unknown-word category. |
write_char_def<W: Write>(&self, writer: &mut W) -> Result<()> | Writes the char.def content preserved verbatim from the original training input. |
write_feature_def<W: Write>(&self, writer: &mut W) -> Result<()> | Writes the feature.def content preserved verbatim from the original training input. |
write_rewrite_def<W: Write>(&self, writer: &mut W) -> Result<()> | Writes the rewrite.def content preserved verbatim from the original training input. |
write_left_id_def<W: Write>(&self, writer: &mut W) -> Result<()> | Writes left-id.def from the stored left_id_map, with 0 BOS/EOS as the first line. |
write_right_id_def<W: Write>(&self, writer: &mut W) -> Result<()> | Writes right-id.def from the stored right_id_map, with 0 BOS/EOS as the first line. |
update_metadata_json<W: Write>(&self, base_metadata_path: &Path, writer: &mut W) -> Result<()> | Reads a base metadata.json, updates it with training-derived values (default_word_cost estimated from the median feature weight, plus a model_info section with feature/label counts, context ID ranges, and training metadata), and writes the result. |
ModelMetadata and FeatureSetInfo
ModelMetadata records summary information about a training run: version, regularization (the cost coefficient used), iterations (max iterations configured), feature_count, and label_count. It is embedded in SerializableModel::metadata.
FeatureSetInfo holds the per-label connection information extracted from the merged CRF model after training: left_id, right_id, and weight. SerializableModel::feature_sets stores one entry per label, in the same order as labels.
API Reference
The API reference is available. Please see following URL:
Lindera Library
The lindera crate is a pure morphological segmenter: it integrates the dictionary crates and provides the Segmenter API. It does not depend on lindera-analysis, lindera-crf, or lindera-trainer by default. This section covers segmentation, error handling, and API reference.
If you need the Tokenizer, character filters, or token filters (a Lucene-style analysis chain built on top of Segmenter), see the separate Lindera Analysis crate, including its Configuration and Filters pages.
- Segmenter - Core segmentation component using the Viterbi algorithm
- Error Handling - Error types and handling patterns
- API Reference - Links to generated API documentation
Segmenter
The Segmenter is the core component that performs morphological analysis. It uses the Viterbi algorithm to find the optimal segmentation of input text based on a dictionary and cost model.
Creating a Segmenter
A Segmenter requires three components:
- Mode - the tokenization strategy (
NormalorDecompose) - Dictionary - a system dictionary for morphological analysis
- UserDictionary (optional) - a supplementary dictionary for custom words
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); }
Tokenization Modes
Mode::Normal
Standard tokenization based on the dictionary entries. Words are segmented faithfully according to what is registered in the dictionary.
#![allow(unused)] fn main() { use lindera::mode::Mode; let mode = Mode::Normal; }
Mode::Decompose
Decomposes compound nouns into their constituent parts. This mode applies a configurable penalty to long compound words, encouraging the segmenter to split them into shorter components.
For example, with Mode::Normal, the compound word "関西国際空港" in the sentence "関西国際空港限定トートバッグ" remains part of a single token, while with Mode::Decompose, it is split into "関西", "国際", and "空港" (the surrounding context affects whether a compound is split; the same string in isolation may not split the same way).
#![allow(unused)] fn main() { use lindera::mode::Mode; let mode = Mode::Decompose(Default::default()); }
Dictionary Loading
Lindera provides the load_dictionary function to load dictionaries from various sources.
Embedded Dictionaries
When built with the appropriate feature flag (e.g., embed-ipadic), dictionaries can be loaded directly from the binary:
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; let dictionary = load_dictionary("embedded://ipadic")?; }
Available embedded dictionary URIs:
embedded://ipadic- IPADIC (Japanese)embedded://ipadic-neologd- IPADIC NEologd (Japanese)embedded://unidic- UniDic (Japanese)embedded://ko-dic- ko-dic (Korean)embedded://cc-cedict- CC-CEDICT (Chinese)embedded://jieba- Jieba (Chinese)
External Dictionaries
Pre-built dictionary directories can be loaded from the filesystem:
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; let dictionary = load_dictionary("/path/to/dictionary")?; }
Using with Tokenizer
The Segmenter is typically used through the Tokenizer, which adds support for character filters and token filters:
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "日本語の形態素解析を行うことができます。"; let tokens = tokenizer.tokenize(text)?; for mut token in tokens { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Note the mut binding on token: Token::details takes &mut self, so iterating with a plain for token in tokens fails to compile (E0596: cannot borrow as mutable).
Building from Config
Segmenter::from_config builds a Segmenter from a SegmenterConfig (a serde_json::Value), the same configuration format used by Tokenizer/TokenizerBuilder (see Configuration) but scoped to just the segmenter: section:
#![allow(unused)] fn main() { use serde_json::json; use lindera::segmenter::{Segmenter, SegmenterConfig}; let config: SegmenterConfig = json!({ "mode": "normal", "dictionary": "embedded://ipadic", "keep_whitespace": false, "use_mmap": false }); let segmenter = Segmenter::from_config(&config)?; }
Memory-Mapped Loading
For a filesystem-based (not embedded://) dictionary, set use_mmap to
true to route the connection-cost matrix and prefix dictionary through
memory-mapped reads instead of a plain file read. Only the dictionary's
largest word-list files stay lazily paged this way; the connection-cost
matrix and the double-array trie are always fully materialized into owned
memory regardless. use_mmap is silently ignored for embedded://
dictionaries, since their data is already a static, zero-copy byte slice.
Requires the mmap cargo feature (enabled by default).
Whitespace Handling
By default, whitespace-only tokens are dropped from the output for MeCab compatibility. Call keep_whitespace(true) on a Segmenter to keep them:
#![allow(unused)] fn main() { let segmenter = Segmenter::new(Mode::Normal, dictionary, None).keep_whitespace(true); }
N-Best Segmentation
segment_nbest returns the top-n segmentations ordered by total path cost, each paired with its cost. Set unique to deduplicate results that share the same word boundaries but differ only in POS tags, and cost_threshold to discard paths whose cost exceeds best_cost + threshold:
#![allow(unused)] fn main() { let results = segmenter.segment_nbest(Cow::Borrowed("すもももももももものうち"), 3, false, None)?; for (tokens, cost) in results { println!("cost={cost}"); for token in tokens { println!(" {}", token.surface.as_ref()); } } }
segment_nbest_with_lattice is the same operation but lets you pass in a reusable Lattice buffer to avoid reallocating one per call.
Error Handling
Lindera uses a structured error system based on anyhow and thiserror for ergonomic error handling throughout the library.
LinderaResult
The LinderaResult<T> type alias is the standard return type for fallible operations in Lindera:
#![allow(unused)] fn main() { pub type LinderaResult<T> = Result<T, LinderaError>; }
LinderaError
LinderaError is the main error type, containing an error kind and a source error with full context:
#![allow(unused)] fn main() { pub struct LinderaError { pub kind: LinderaErrorKind, source: anyhow::Error, } }
The add_context method allows attaching additional context to an error:
#![allow(unused)] fn main() { let error = error.add_context("failed to load dictionary from /path/to/dict"); }
LinderaErrorKind
LinderaErrorKind is an enum that categorizes errors:
| Kind | Description |
|---|---|
Io | I/O errors (file read/write, network) |
Parse | Parsing errors (invalid input format) |
Serialize | Serialization errors |
Deserialize | Deserialization errors |
Content | Invalid content or data errors |
Args | Invalid argument errors |
Decode | Decoding errors |
NotFound | Resource not found (e.g., dictionary file missing) |
Build | Dictionary build errors |
Dictionary | Dictionary-related errors |
Mode | Invalid tokenization mode errors |
FeatureDisabled | Attempted to use a feature that is not enabled |
Creating Errors
Use LinderaErrorKind::with_error to create an error from a kind and a source:
#![allow(unused)] fn main() { use lindera::error::LinderaErrorKind; let error = LinderaErrorKind::Io.with_error(anyhow::anyhow!("file not found: config.yml")); }
Using the ? Operator
Since Lindera functions return LinderaResult, the ? operator can propagate errors naturally:
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn analyze(text: &str) -> LinderaResult<Vec<String>> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let tokens = tokenizer.tokenize(text)?; Ok(tokens.iter().map(|t| t.surface.as_ref().to_string()).collect()) } }
Error Handling Patterns
Matching on Error Kind
#![allow(unused)] fn main() { use lindera::dictionary::load_dictionary; use lindera::error::LinderaErrorKind; match load_dictionary("/path/to/dictionary") { Ok(dict) => { /* use dictionary */ } Err(e) if e.kind() == LinderaErrorKind::NotFound => { eprintln!("Dictionary not found: {}", e); } Err(e) if e.kind() == LinderaErrorKind::Io => { eprintln!("I/O error loading dictionary: {}", e); } Err(e) => { eprintln!("Unexpected error: {}", e); } } }
Converting from External Errors
#![allow(unused)] fn main() { use lindera::error::LinderaErrorKind; let content = std::fs::read_to_string("config.yml") .map_err(|err| LinderaErrorKind::Io.with_error(anyhow::anyhow!(err)))?; }
API Reference
The API reference is available. Please see following URL:
Lindera Analysis
Lindera Analysis layers a Lucene-style text analysis chain on top of the pure morphological segmenter provided by the lindera crate. It composes character filters, a Segmenter, and token filters into a single Tokenizer pipeline that can be built programmatically or loaded entirely from a YAML configuration file.
Key Features
- Character filters that transform input text before segmentation, with automatic byte-offset correction back to the original text
- Token filters that transform, merge, filter, or reorder the tokens produced by the segmenter
Tokenizer/TokenizerBuilderto assemble a full analysis pipeline, either in Rust code or from a YAML file (LINDERA_CONFIG_PATH)- Built-in filters covering Japanese, Korean, and general-purpose text normalization
Contents
- Configuration -- YAML configuration file format for the
Tokenizer - Filters -- Reference for all character filters and token filters
- Architecture -- Internal structure and key components
- API Reference -- API documentation
Configuration
Lindera is able to read YAML format configuration files.
Specify the path to the following file in the environment variable LINDERA_CONFIG_PATH. You can use it easily without having to code the behavior of the tokenizer in Rust code.
segmenter:
mode: "normal"
dictionary: "embedded://ipadic"
# user_dictionary: "./resources/user_dict/ipadic_simple_userdic.csv"
# keep_whitespace: false
# use_mmap: false # only meaningful for filesystem (non-embedded://) dictionaries
character_filters:
- kind: "unicode_normalize"
args:
kind: "nfkc"
- kind: "japanese_iteration_mark"
args:
normalize_kanji: true
normalize_kana: true
- kind: mapping
args:
mapping:
リンデラ: Lindera
token_filters:
- kind: "japanese_compound_word"
args:
tags:
- "名詞,数"
- "名詞,接尾,助数詞"
new_tag: "名詞,数"
- kind: "japanese_number"
args:
tags:
- "名詞,数"
- kind: "japanese_stop_tags"
args:
tags:
- "接続詞"
- "助詞"
- "助詞,格助詞"
- "助詞,格助詞,一般"
- "助詞,格助詞,引用"
- "助詞,格助詞,連語"
- "助詞,係助詞"
- "助詞,副助詞"
- "助詞,間投助詞"
- "助詞,並立助詞"
- "助詞,終助詞"
- "助詞,副助詞/並立助詞/終助詞"
- "助詞,連体化"
- "助詞,副詞化"
- "助詞,特殊"
- "助動詞"
- "記号"
- "記号,一般"
- "記号,読点"
- "記号,句点"
- "記号,空白"
- "記号,括弧閉"
- "その他,間投"
- "フィラー"
- "非言語音"
- kind: "japanese_katakana_stem"
args:
min: 3
- kind: "remove_diacritical_mark"
args:
japanese: false
% export LINDERA_CONFIG_PATH=./resources/config/lindera.yml
use std::path::PathBuf; use lindera_analysis::tokenizer::TokenizerBuilder; use lindera::LinderaResult; fn main() -> LinderaResult<()> { // Load tokenizer configuration from file let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../resources") .join("config") .join("lindera.yml"); let builder = TokenizerBuilder::from_file(&path)?; let tokenizer = builder.build()?; let text = "Linderaは形態素解析エンジンです。ユーザー辞書も利用可能です。".to_string(); println!("text: {text}"); let tokens = tokenizer.tokenize(&text)?; for token in tokens { println!( "token: {:?}, start: {:?}, end: {:?}, details: {:?}", token.surface, token.byte_start, token.byte_end, token.details ); } Ok(()) }
Filters
Character filters and token filters are the two pre/post-processing stages of the lindera-analysis Tokenizer pipeline.
- Character filters transform the input text before segmentation. Byte offsets are corrected automatically so that the resulting tokens still report positions relative to the original, unfiltered text.
- Token filters transform the list of tokens produced by the segmenter after segmentation.
Both kinds of filters are configured the same way: a kind string (also used with the CLI's --character-filter / --token-filter flags in the form kind:{"json": "args"}) and a JSON args object with filter-specific parameters.
Character Filters
Character filters are configured under the character_filters key of the YAML configuration file. Each entry is applied in order, and each filter's output feeds into the next.
unicode_normalize
Normalizes the input text using one of the four standard Unicode normalization forms.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
kind | string | Yes | One of nfc, nfd, nfkc, or nfkd |
Example:
{
"kind": "unicode_normalize",
"args": {
"kind": "nfkc"
}
}
japanese_iteration_mark
Normalizes Japanese iteration marks (々, ゝ, ゞ, ヽ, ヾ) by replacing each mark with the character it repeats, adding or removing the voiced sound mark (dakuten) as needed for the hiragana/katakana variants.
Parameters:
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
normalize_kanji | bool | No | false | Normalize the kanji iteration mark 々 |
normalize_kana | bool | No | false | Normalize the hiragana/katakana iteration marks ゝ, ゞ, ヽ, ヾ |
Example:
{
"kind": "japanese_iteration_mark",
"args": {
"normalize_kanji": true,
"normalize_kana": true
}
}
mapping (character filter)
Replaces occurrences of the keys in mapping with their corresponding values, using longest-match search over the input text (built with an Aho-Corasick automaton).
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
mapping | object (string to string) | Yes | Substrings to replace, mapped to their replacements |
Example:
{
"kind": "mapping",
"args": {
"mapping": {
"リンデラ": "Lindera"
}
}
}
regex
Replaces every match of a regular expression in the input text with a literal replacement string. Capture groups are not interpolated into the replacement.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
pattern | string | Yes | A regular expression (using the regex crate's syntax) |
replacement | string | Yes | The literal string that replaces every match of pattern |
Example:
{
"kind": "regex",
"args": {
"pattern": "\\s{2,}",
"replacement": " "
}
}
Token Filters
Token filters are configured under the token_filters key of the YAML configuration file. Each filter is applied in order to the token list produced by the segmenter.
japanese_base_form
Replaces the token's surface text with its base (dictionary) form, as registered in the base_form or orthographic_base_form field of the dictionary. Acts as a lemmatizer for verbs and adjectives. Tokens whose first detail is UNK (unknown words) are left unchanged.
This filter takes no configuration parameters.
Example:
{
"kind": "japanese_base_form"
}
japanese_compound_word
Merges consecutive tokens whose part-of-speech tag matches one of tags into a single compound token.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
tags | array<string> | Yes | Part-of-speech tags (up to 4 comma-separated levels) that mark tokens eligible for merging |
new_tag | string | No | Part-of-speech tag assigned to the merged token. When omitted, the merged token is tagged 複合語 |
Example:
{
"kind": "japanese_compound_word",
"args": {
"tags": [
"名詞,数",
"名詞,接尾,助数詞"
],
"new_tag": "名詞,数"
}
}
japanese_kana
Converts token text between hiragana and katakana.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
kind | string | Yes | "hiragana" converts katakana to hiragana; "katakana" converts hiragana to katakana |
Example:
{
"kind": "japanese_kana",
"args": {
"kind": "hiragana"
}
}
japanese_katakana_stem
Removes a trailing prolonged sound mark (ー, U+30FC) from katakana tokens, but only when the token is longer than min characters.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
min | positive integer | Yes | Minimum katakana token length (in characters) required before the trailing prolonged sound mark is stemmed |
Example:
{
"kind": "japanese_katakana_stem",
"args": {
"min": 3
}
}
japanese_keep_tags
Keeps only tokens whose part-of-speech tag matches one of tags, removing all others.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
tags | array<string> | Yes | Part-of-speech tags (up to 4 comma-separated levels) to keep |
Example:
{
"kind": "japanese_keep_tags",
"args": {
"tags": [
"名詞",
"名詞,一般",
"名詞,固有名詞"
]
}
}
japanese_number
Converts Japanese numeral representations (kanji numerals, formal/legal kanji numerals, and fullwidth digits) in the token's surface text to Arabic numerals.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
tags | array<string> or null | No | Part-of-speech tags (up to 4 comma-separated levels) to restrict the conversion to. When omitted or null, every token is converted |
Example:
{
"kind": "japanese_number",
"args": {
"tags": [
"名詞,数"
]
}
}
japanese_reading_form
Replaces the token's surface text with its reading, in katakana, as registered in the dictionary's reading field. Tokens whose first detail is UNK (unknown words) are left unchanged.
This filter takes no configuration parameters.
Example:
{
"kind": "japanese_reading_form"
}
japanese_stop_tags
Removes tokens whose part-of-speech tag matches one of tags.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
tags | array<string> | Yes | Part-of-speech tags (up to 4 comma-separated levels) to remove |
Example:
{
"kind": "japanese_stop_tags",
"args": {
"tags": [
"助詞",
"助動詞",
"記号"
]
}
}
keep_words
Keeps only tokens whose surface text exactly matches one of words.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
words | array<string> | Yes | Surface forms to keep |
Example:
{
"kind": "keep_words",
"args": {
"words": [
"すもも",
"もも"
]
}
}
korean_keep_tags
Keeps only Korean tokens whose first part-of-speech tag matches one of tags.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
tags | array<string> | Yes | Part-of-speech tags to keep |
Example:
{
"kind": "korean_keep_tags",
"args": {
"tags": [
"NNG"
]
}
}
korean_reading_form
Replaces the token's surface text with its reading, as registered in the dictionary's reading field. Tokens whose first detail is UNK (unknown words) are left unchanged.
This filter takes no configuration parameters.
Example:
{
"kind": "korean_reading_form"
}
korean_stop_tags
Removes Korean tokens whose first part-of-speech tag matches one of tags.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
tags | array<string> | Yes | Part-of-speech tags to remove |
Example:
{
"kind": "korean_stop_tags",
"args": {
"tags": [
"EP",
"EF",
"JKG"
]
}
}
length
Keeps only tokens whose surface text length (in characters) falls within [min, max].
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
min | unsigned integer | No | Minimum character length (inclusive) |
max | unsigned integer | No | Maximum character length (inclusive) |
Example:
{
"kind": "length",
"args": {
"min": 2,
"max": 3
}
}
lowercase
Converts token surface text to lowercase.
This filter takes no configuration parameters.
Example:
{
"kind": "lowercase"
}
mapping (token filter)
Replaces occurrences of the keys in mapping with their corresponding values in each token's surface text, using longest-match search (built with an Aho-Corasick automaton). This is the token-level counterpart of the mapping character filter.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
mapping | object (string to string) | Yes | Substrings to replace, mapped to their replacements |
Example:
{
"kind": "mapping",
"args": {
"mapping": {
"籠": "篭"
}
}
}
remove_diacritical_mark
Removes diacritical (combining) marks from token surface text, re-applying the text's original Unicode normalization form afterward.
Parameters:
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
japanese | bool | No | false | Also remove Japanese (han-)dakuten combining marks (e.g. from decomposed voiced/semi-voiced kana) |
Example:
{
"kind": "remove_diacritical_mark",
"args": {
"japanese": false
}
}
stop_words
Removes tokens whose surface text exactly matches one of words.
Parameters:
| Argument | Type | Required | Description |
|---|---|---|---|
words | array<string> | Yes | Surface forms to remove |
Example:
{
"kind": "stop_words",
"args": {
"words": [
"も",
"の"
]
}
}
uppercase
Converts token surface text to uppercase.
This filter takes no configuration parameters.
Example:
{
"kind": "uppercase"
}
YAML Configuration
Character filters and token filters are configured together with the segmenter in a single YAML file. See Configuration for the full file format; the relevant excerpt looks like this:
character_filters:
- kind: "unicode_normalize"
args:
kind: "nfkc"
- kind: "japanese_iteration_mark"
args:
normalize_kanji: true
normalize_kana: true
token_filters:
- kind: "japanese_stop_tags"
args:
tags:
- "助詞"
- "助動詞"
- "記号"
- kind: "japanese_katakana_stem"
args:
min: 3
- kind: "lowercase"
- kind: "length"
args:
min: 2
Rust API
Character filters and token filters can also be created and applied programmatically:
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::character_filter::BoxCharacterFilter; use lindera_analysis::character_filter::unicode_normalize::{ UnicodeNormalizeCharacterFilter, UnicodeNormalizeKind, }; use lindera_analysis::token_filter::BoxTokenFilter; use lindera_analysis::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter; use lindera_analysis::token_filter::japanese_katakana_stem::JapaneseKatakanaStemTokenFilter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let mut tokenizer = Tokenizer::new(segmenter); // Add a character filter let normalize_filter = UnicodeNormalizeCharacterFilter::new(UnicodeNormalizeKind::NFKC); tokenizer.append_character_filter(BoxCharacterFilter::from(normalize_filter)); // Add token filters let stop_tags_filter = JapaneseStopTagsTokenFilter::new( vec![ "助詞".to_string(), "助動詞".to_string(), "記号".to_string(), ] .into_iter() .collect(), ); tokenizer.append_token_filter(BoxTokenFilter::from(stop_tags_filter)); let katakana_stem_filter = JapaneseKatakanaStemTokenFilter::new(std::num::NonZeroUsize::new(3).unwrap()); tokenizer.append_token_filter(BoxTokenFilter::from(katakana_stem_filter)); // Tokenize with filters applied let tokens = tokenizer.tokenize("Linderaは形態素解析エンジンです。")?; for token in tokens { println!( "token: {:?}, details: {:?}", token.surface, token.details ); } Ok(()) }
The append_character_filter and append_token_filter methods add filters in order. Character filters are applied sequentially to the text before segmentation; token filters are applied sequentially to the token list after segmentation.
Architecture
Module Structure
lindera-analysis/src/
├── lib.rs # Public API re-exports, CLI flag parsing helper
├── character_filter.rs # CharacterFilter trait, OffsetMapping, CharacterFilterLoader
├── character_filter/
│ ├── unicode_normalize.rs # Unicode normalization (NFC/NFD/NFKC/NFKD)
│ ├── japanese_iteration_mark.rs # Japanese iteration mark normalization
│ ├── mapping.rs # Mapping-based text replacement
│ └── regex.rs # Regex-based text replacement
├── token_filter.rs # TokenFilter trait, TokenFilterLoader
├── token_filter/
│ ├── japanese_base_form.rs
│ ├── japanese_compound_word.rs
│ ├── japanese_kana.rs
│ ├── japanese_katakana_stem.rs
│ ├── japanese_keep_tags.rs
│ ├── japanese_number.rs
│ ├── japanese_reading_form.rs
│ ├── japanese_stop_tags.rs
│ ├── keep_words.rs
│ ├── korean_keep_tags.rs
│ ├── korean_reading_form.rs
│ ├── korean_stop_tags.rs
│ ├── length.rs
│ ├── lowercase.rs
│ ├── mapping.rs
│ ├── remove_diacritical_mark.rs
│ ├── stop_words.rs
│ ├── tags.rs # Shared keep/stop-tag filtering helpers (private)
│ └── uppercase.rs
└── tokenizer.rs # Tokenizer, TokenizerBuilder
Key Components
CharacterFilter
A trait for filters that preprocess text before segmentation. Each implementation provides a name() and an apply(&self, text: &mut String) -> LinderaResult<OffsetMapping> method that rewrites text in place and returns an OffsetMapping describing every transformation it performed.
The OffsetMapping (built from a list of Transformation records) lets the Tokenizer translate token byte offsets computed against the filtered text back to byte offsets in the original input, even after multiple filters have run in sequence. BoxCharacterFilter wraps any CharacterFilter implementation as a boxed, cloneable trait object, and CharacterFilterLoader builds one from a kind string plus a serde_json::Value of arguments (used both by YAML configuration loading and by CLI flag parsing).
TokenFilter
A trait for filters that post-process the tokens produced by the segmenter. Each implementation provides a name() and an apply(&self, tokens: &mut Vec<Token<'_>>) -> LinderaResult<()> method that modifies, merges, reorders, or removes tokens in place. BoxTokenFilter wraps any TokenFilter implementation as a boxed, cloneable trait object, and TokenFilterLoader builds one from a kind string plus a serde_json::Value of arguments, mirroring CharacterFilterLoader.
Tokenizer / TokenizerBuilder
Tokenizer composes character filters, a lindera::segmenter::Segmenter, and token filters into a single analysis pipeline. Calling tokenize runs the character filters over the input text, segments the filtered text, applies the token filters to the resulting tokens, and finally corrects each token's byte offsets back to the original text via the recorded OffsetMappings.
TokenizerBuilder assembles a Tokenizer from a TokenizerConfig (a serde_json::Value), which can be constructed programmatically, loaded from a YAML file (via TokenizerBuilder::from_file, or automatically from the LINDERA_CONFIG_PATH environment variable via TokenizerBuilder::new), or built up incrementally with set_segmenter_mode, set_segmenter_dictionary, append_character_filter, and append_token_filter. See Configuration for the YAML file format and Filters for the full filter reference.
Feature Flags
| Feature | Description | Default |
|---|---|---|
embed-ipadic | Embed the IPADIC dictionary in the binary (forwards to lindera/embed-ipadic) | No |
embed-ipadic-neologd | Embed the IPADIC-NEologd dictionary in the binary (forwards to lindera/embed-ipadic-neologd) | No |
embed-unidic | Embed the UniDic dictionary in the binary (forwards to lindera/embed-unidic) | No |
embed-ko-dic | Embed the ko-dic dictionary in the binary (forwards to lindera/embed-ko-dic) | No |
embed-cc-cedict | Embed the CC-CEDICT dictionary in the binary (forwards to lindera/embed-cc-cedict) | No |
embed-jieba | Embed the Jieba dictionary in the binary (forwards to lindera/embed-jieba) | No |
API Reference
The API reference is available. Please see following URL:
Lindera CLI
A morphological analysis command-line interface for Lindera.
- Installation - Install or build the CLI
- Commands - Command reference for list, tokenize, build, train, and export
- Tutorial - Step-by-step guide to get started
Installation
Install via Cargo
You can install the binary via cargo:
% cargo install lindera-cli
Download from GitHub Releases
Alternatively, you can download a pre-built binary from the release page:
Obtaining Dictionaries
Lindera does not bundle dictionaries with the binary. You need to download a pre-built dictionary separately from the GitHub Releases page:
# Example: download and extract the IPADIC dictionary
% curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
% unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
Then specify the dictionary path when using the CLI:
% echo "関西国際空港限定トートバッグ" | lindera tokenize --dict /path/to/ipadic
Build from Source
Build without dictionaries (default)
Build a binary containing only the tokenizer and trainer without embedded dictionaries:
% cargo build --release
Build with all features
% cargo build --release --all-features
Build with Embedded Dictionaries (Advanced)
For advanced users who want to embed dictionaries directly into the binary, use the embed-* feature flags. This eliminates the need for external dictionary files at runtime but increases the binary size.
IPADIC (Japanese dictionary)
% cargo build --release --features=embed-ipadic
IPADIC NEologd (Japanese dictionary)
% cargo build --release --features=embed-ipadic-neologd
UniDic (Japanese dictionary)
% cargo build --release --features=embed-unidic
ko-dic (Korean dictionary)
% cargo build --release --features=embed-ko-dic
CC-CEDICT (Chinese dictionary)
% cargo build --release --features=embed-cc-cedict
Jieba (Chinese dictionary)
% cargo build --release --features=embed-jieba
[!TIP] After building with an
embed-*feature flag, use theembedded://scheme to load the embedded dictionary:% echo "関西国際空港限定トートバッグ" | lindera tokenize --dict embedded://ipadicSee Feature Flags for details.
Commands
The Lindera CLI provides five main commands:
- list - List the morphological analysis dictionaries embedded in the binary
- tokenize - Perform morphological analysis on text
- build - Build a dictionary from source CSV files
- train - Train a CRF model from annotated corpus data
- export - Export a trained model to dictionary format
list
List the morphological analysis dictionaries that were embedded in the binary at build time (via the embed-* feature flags).
List parameters
This command takes no arguments.
List usage
% lindera list
ipadic
The output contains one dictionary name per line, limited to whichever embed-* features were enabled when the binary was built (e.g. --features=embed-ipadic). If no embed-* feature was enabled, the command produces no output.
tokenize
Perform morphological analysis (tokenization) on Japanese, Chinese, or Korean text using various dictionaries.
Parameters
--dict/-d: Dictionary path or URI (required)- File path:
/path/to/dictionary - Embedded:
embedded://ipadic,embedded://unidic, etc.
- File path:
--output/-o: Output format (default: mecab)mecab: MeCab-compatible format with part-of-speech infowakati: Space-separated tokens onlyjson: Detailed JSON format with all token information
--user-dict/-u: User dictionary path (optional)--mode/-m: Tokenization mode (default: normal)normal: Standard tokenizationdecompose: Decompose compound words
--char-filter/-c: Character filter configuration (JSON)--token-filter/-t: Token filter configuration (JSON)--keep-whitespace: Keep whitespace tokens in the output (by default, whitespace is dropped for MeCab compatibility)--mmap: Use memory-mapped file loading for the dictionary directory's word list. Ignored forembedded://dictionaries and when themmapfeature is disabled. Rebuilding or truncating the dictionary directory while a process holds it mapped can cause a SIGBUS on the next lookup.--nbest/-N: Number of N-best results to return (default: 1). When set to 2 or more, N-best output is enabled.--nbest-unique: Deduplicate N-best results by removing paths that produce the same segmentation.--nbest-cost-threshold: Maximum cost difference from the best path. Only paths with cost withinbest_cost + thresholdare returned.- Input file: Optional file path (default: stdin)
Basic usage
# Tokenize text using a dictionary directory
echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /path/to/dictionary
# Tokenize text using embedded dictionary
echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic
# Tokenize with different output format
echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic \
--output json
# Tokenize text from file
lindera tokenize \
--dict /path/to/dictionary \
--output wakati \
input.txt
Examples with external dictionaries
Tokenize with external IPADIC (Japanese dictionary)
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-ipadic-2.7.0-20250920
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
Tokenize with external IPADIC Neologd (Japanese dictionary)
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-ipadic-neologd-0.0.7-20200820
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素解析 名詞,固有名詞,一般,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
Tokenize with external UniDic (Japanese dictionary)
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-unidic-2.1.2
日本 名詞,固有名詞,地名,国,*,*,ニッポン,日本,日本,ニッポン,日本,ニッポン,固,*,*,*,*
語 名詞,普通名詞,一般,*,*,*,ゴ,語,語,ゴ,語,ゴ,漢,*,*,*,*
の 助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*
形態 名詞,普通名詞,一般,*,*,*,ケイタイ,形態,形態,ケータイ,形態,ケータイ,漢,*,*,*,*
素 接尾辞,名詞的,一般,*,*,*,ソ,素,素,ソ,素,ソ,漢,*,*,*,*
解析 名詞,普通名詞,サ変可能,*,*,*,カイセキ,解析,解析,カイセキ,解析,カイセキ,漢,*,*,*,*
を 助詞,格助詞,*,*,*,*,ヲ,を,を,オ,を,オ,和,*,*,*,*
行う 動詞,一般,*,*,五段-ワア行,連体形-一般,オコナウ,行う,行う,オコナウ,行う,オコナウ,和,*,*,*,*
こと 名詞,普通名詞,一般,*,*,*,コト,事,こと,コト,こと,コト,和,コ濁,基本形,*,*
が 助詞,格助詞,*,*,*,*,ガ,が,が,ガ,が,ガ,和,*,*,*,*
でき 動詞,非自立可能,*,*,上一段-カ行,連用形-一般,デキル,出来る,でき,デキ,できる,デキル,和,*,*,*,*
ます 助動詞,*,*,*,助動詞-マス,終止形-一般,マス,ます,ます,マス,ます,マス,和,*,*,*,*
。 補助記号,句点,*,*,*,*,,。,。,,。,,記号,*,*,*,*
EOS
Tokenize with external ko-dic (Korean dictionary)
% echo "한국어의형태해석을실시할수있습니다." | lindera tokenize \
--dict /tmp/lindera-ko-dic-2.1.1-20180720
한국어 NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
의 JKG,*,F,의,*,*,*,*
형태 NNG,*,F,형태,*,*,*,*
해석 NNG,행위,T,해석,*,*,*,*
을 JKO,*,T,을,*,*,*,*
실시 NNG,행위,F,실시,*,*,*,*
할 XSV+ETM,*,T,할,Inflect,XSV,ETM,하/XSV/*+ᆯ/ETM/*
수 NNB,*,F,수,*,*,*,*
있 VV,*,T,있,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*
. SF,*,*,*,*,*,*,*
EOS
Tokenize with external CC-CEDICT (Chinese dictionary)
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict /tmp/lindera-cc-cedict-0.1.0-20200409
可以 *,*,*,*,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good/
进行 *,*,*,*,jin4 xing2,進行,进行,to advance/to conduct/underway/in progress/to do/to carry out/to carry on/to execute/
中文 *,*,*,*,Zhong1 wen2,中文,中文,Chinese language/
形态学 *,*,*,*,xing2 tai4 xue2,形態學,形态学,morphology (in biology or linguistics)/
分析 *,*,*,*,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4]/
。 *,*,*,*,*,*,*,*
EOS
Tokenize with external Jieba (Chinese dictionary)
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict /tmp/lindera-jieba-0.1.1
Examples with embedded dictionaries
Lindera can include dictionaries directly in the binary when built with specific feature flags. This allows tokenization without external dictionary files.
Tokenize with embedded IPADIC (Japanese dictionary)
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
NOTE: To include IPADIC dictionary in the binary, you must build with the --features=embed-ipadic option.
Tokenize with embedded UniDic (Japanese dictionary)
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://unidic
日本 名詞,固有名詞,地名,国,*,*,ニッポン,日本,日本,ニッポン,日本,ニッポン,固,*,*,*,*
語 名詞,普通名詞,一般,*,*,*,ゴ,語,語,ゴ,語,ゴ,漢,*,*,*,*
の 助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*
形態 名詞,普通名詞,一般,*,*,*,ケイタイ,形態,形態,ケータイ,形態,ケータイ,漢,*,*,*,*
素 接尾辞,名詞的,一般,*,*,*,ソ,素,素,ソ,素,ソ,漢,*,*,*,*
解析 名詞,普通名詞,サ変可能,*,*,*,カイセキ,解析,解析,カイセキ,解析,カイセキ,漢,*,*,*,*
を 助詞,格助詞,*,*,*,*,ヲ,を,を,オ,を,オ,和,*,*,*,*
行う 動詞,一般,*,*,五段-ワア行,連体形-一般,オコナウ,行う,行う,オコナウ,行う,オコナウ,和,*,*,*,*
こと 名詞,普通名詞,一般,*,*,*,コト,事,こと,コト,こと,コト,和,コ濁,基本形,*,*
が 助詞,格助詞,*,*,*,*,ガ,が,が,ガ,が,ガ,和,*,*,*,*
でき 動詞,非自立可能,*,*,上一段-カ行,連用形-一般,デキル,出来る,でき,デキ,できる,デキル,和,*,*,*,*
ます 助動詞,*,*,*,助動詞-マス,終止形-一般,マス,ます,ます,マス,ます,マス,和,*,*,*,*
。 補助記号,句点,*,*,*,*,,。,。,,。,,記号,*,*,*,*
EOS
NOTE: To include UniDic dictionary in the binary, you must build with the --features=embed-unidic option.
Tokenize with embedded IPADIC NEologd (Japanese dictionary)
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic-neologd
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素解析 名詞,固有名詞,一般,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
NOTE: To include UniDic dictionary in the binary, you must build with the --features=embed-ipadic-neologd option.
Tokenize with embedded ko-dic (Korean dictionary)
% echo "한국어의형태해석을실시할수있습니다." | lindera tokenize \
--dict embedded://ko-dic
한국어 NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
의 JKG,*,F,의,*,*,*,*
형태 NNG,*,F,형태,*,*,*,*
해석 NNG,행위,T,해석,*,*,*,*
을 JKO,*,T,을,*,*,*,*
실시 NNG,행위,F,실시,*,*,*,*
할 XSV+ETM,*,T,할,Inflect,XSV,ETM,하/XSV/*+ᆯ/ETM/*
수 NNB,*,F,수,*,*,*,*
있 VV,*,T,있,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*
. SF,*,*,*,*,*,*,*
EOS
NOTE: To include ko-dic dictionary in the binary, you must build with the --features=embed-ko-dic option.
Tokenize with embedded CC-CEDICT (Chinese dictionary)
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict embedded://cc-cedict
可以 *,*,*,*,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good/
进行 *,*,*,*,jin4 xing2,進行,进行,to advance/to conduct/underway/in progress/to do/to carry out/to carry on/to execute/
中文 *,*,*,*,Zhong1 wen2,中文,中文,Chinese language/
形态学 *,*,*,*,xing2 tai4 xue2,形態學,形态学,morphology (in biology or linguistics)/
分析 *,*,*,*,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4]/
。 *,*,*,*,*,*,*,*
EOS
NOTE: To include CC-CEDICT dictionary in the binary, you must build with the --features=embed-cc-cedict option.
Tokenize with embedded Jieba (Chinese dictionary)
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict embedded://jieba
NOTE: To include Jieba dictionary in the binary, you must build with the --features=embed-jieba option.
User dictionary examples
Lindera supports user dictionaries to add custom words alongside system dictionaries. User dictionaries can be in CSV or binary format.
Use user dictionary (CSV format)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict embedded://ipadic \
--user-dict ./resources/user_dict/ipadic_simple_userdic.csv
東京スカイツリー カスタム名詞,*,*,*,*,*,東京スカイツリー,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,とうきょうスカイツリー駅,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
Use user dictionary (Binary format)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict /tmp/lindera-ipadic-2.7.0-20250920 \
--user-dict ./resources/user_dict/ipadic_simple_userdic.bin
東京スカイツリー カスタム名詞,*,*,*,*,*,東京スカイツリー,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,とうきょうスカイツリー駅,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
Tokenization modes
Lindera provides two tokenization modes: normal and decompose.
Normal mode (default)
Tokenizes faithfully based on words registered in the dictionary:
% echo "関西国際空港限定トートバッグ" | lindera tokenize \
--dict embedded://ipadic \
--mode normal
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
EOS
Decompose mode
Tokenizes compound noun words additionally:
% echo "関西国際空港限定トートバッグ" | lindera tokenize \
--dict embedded://ipadic \
--mode decompose
関西 名詞,固有名詞,地域,一般,*,*,関西,カンサイ,カンサイ
国際 名詞,一般,*,*,*,*,国際,コクサイ,コクサイ
空港 名詞,一般,*,*,*,*,空港,クウコウ,クーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
EOS
Output formats
Lindera provides three output formats: mecab, wakati and json.
MeCab format (default)
Outputs results in MeCab-compatible format with part-of-speech information:
% echo "お待ちしております。" | lindera tokenize \
--dict embedded://ipadic \
--output mecab
お待ち 名詞,サ変接続,*,*,*,*,お待ち,オマチ,オマチ
し 動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
て 助詞,接続助詞,*,*,*,*,て,テ,テ
おり 動詞,非自立,*,*,五段・ラ行,連用形,おる,オリ,オリ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
Wakati format
Outputs only the token text separated by spaces:
% echo "お待ちしております。" | lindera tokenize \
--dict embedded://ipadic \
--output wakati
お待ち し て おり ます 。
JSON format
Outputs detailed token information in JSON format:
% echo "お待ちしております。" | lindera tokenize \
--dict embedded://ipadic \
--output json
[
{
"base_form": "お待ち",
"byte_end": 9,
"byte_start": 0,
"conjugation_form": "*",
"conjugation_type": "*",
"part_of_speech": "名詞",
"part_of_speech_subcategory_1": "サ変接続",
"part_of_speech_subcategory_2": "*",
"part_of_speech_subcategory_3": "*",
"pronunciation": "オマチ",
"reading": "オマチ",
"surface": "お待ち",
"word_id": 14698
},
...
]
N-Best tokenization
Lindera supports N-Best tokenization, which returns the top N tokenization candidates ordered by cost (lower cost = better). This is based on the Forward-DP Backward-A* algorithm, compatible with MeCab's N-Best implementation.
Basic N-Best example
% echo "すもももももももものうち" | lindera tokenize \
--dict embedded://ipadic \
-N 3
NBEST 1 (cost=7546)
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
NBEST 2 (cost=7914)
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
NBEST 3 (cost=10060)
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
も 助詞,係助詞,*,*,*,*,も,モ,モ
も 助詞,係助詞,*,*,*,*,も,モ,モ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
N-Best with unique results
When the same segmentation appears in multiple paths (differing only in internal Viterbi states), use --nbest-unique to deduplicate:
% echo "営業部長谷川です" | lindera tokenize \
--dict embedded://ipadic \
-N 5 --nbest-unique -o wakati
NBEST 1 (cost=15760)
営業 部長 谷川 です
NBEST 2 (cost=17758)
営業 部長 谷 川 です
NBEST 3 (cost=18816)
営業 部 長谷川 です
NBEST 4 (cost=19320)
営業 部長 谷川 で す
NBEST 5 (cost=20814)
営業 部 長谷 川 です
N-Best with cost threshold
Use --nbest-cost-threshold to limit results to paths within a certain cost range of the best path:
% echo "営業部長谷川です" | lindera tokenize \
--dict embedded://ipadic \
-N 10 --nbest-unique --nbest-cost-threshold 5000 -o wakati
NBEST 1 (cost=15760)
営業 部長 谷川 です
NBEST 2 (cost=17758)
営業 部長 谷 川 です
NBEST 3 (cost=18816)
営業 部 長谷川 です
Only 3 results are returned because the remaining candidates exceed 15760 + 5000 = 20760.
Advanced tokenization with filters
Lindera provides an analytical framework that combines character filters, tokenizers, and token filters for advanced text processing. Filters are configured using JSON.
% echo "すもももももももものうち" | lindera tokenize \
--dict embedded://ipadic \
--char-filter 'unicode_normalize:{"kind":"nfkc"}' \
--token-filter 'japanese_keep_tags:{"tags":["名詞,一般"]}'
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
もも 名詞,一般,*,*,*,*,もも,モモ,モモ
EOS
build
Build (compile) a morphological analysis dictionary from source CSV files for use with Lindera.
Build parameters
--src/-s: Source directory containing dictionary CSV files (or single CSV file for user dictionary)--dest/-d: Destination directory for compiled dictionary output--metadata/-m: Metadata configuration file (metadata.json) that defines dictionary structure--user/-u: Build user dictionary instead of system dictionary (optional flag)--context-id-freq/-f: Context-ID access-frequency file used to reorder connection-cost IDs (optional; only meaningful when the dictionary'smetadata.jsonsetsconnection_id_mapping: true)
Dictionary types
System dictionary
A full morphological analysis dictionary containing:
- Lexicon entries (word definitions)
- Connection cost matrix
- Unknown word handling rules
- Character type definitions
User dictionary
A supplementary dictionary for custom words that works alongside a system dictionary.
Examples
Build IPADIC (Japanese dictionary)
# Download and extract IPADIC source files
% curl -L -o /tmp/mecab-ipadic-2.7.0-20250920.tar.gz "https://Lindera.dev/mecab-ipadic-2.7.0-20250920.tar.gz"
% tar zxvf /tmp/mecab-ipadic-2.7.0-20250920.tar.gz -C /tmp
# Build the dictionary
% lindera build \
--src /tmp/mecab-ipadic-2.7.0-20250920 \
--dest /tmp/lindera-ipadic-2.7.0-20250920 \
--metadata ./lindera-ipadic/metadata.json
Build IPADIC NEologd (Japanese dictionary)
% curl -L -o /tmp/mecab-ipadic-neologd-0.0.7-20200820.tar.gz "https://lindera.dev/mecab-ipadic-neologd-0.0.7-20200820.tar.gz"
% tar zxvf /tmp/mecab-ipadic-neologd-0.0.7-20200820.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-ipadic-neologd-0.0.7-20200820 \
--dest /tmp/lindera-ipadic-neologd-0.0.7-20200820 \
--metadata ./lindera-ipadic-neologd/metadata.json
Build UniDic (Japanese dictionary)
% curl -L -o /tmp/unidic-mecab-2.1.2.tar.gz "https://Lindera.dev/unidic-mecab-2.1.2.tar.gz"
% tar zxvf /tmp/unidic-mecab-2.1.2.tar.gz -C /tmp
% lindera build \
--src /tmp/unidic-mecab-2.1.2 \
--dest /tmp/lindera-unidic-2.1.2 \
--metadata ./lindera-unidic/metadata.json
Build CC-CEDICT (Chinese dictionary)
% curl -L -o /tmp/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz "https://lindera.dev/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz"
% tar zxvf /tmp/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz -C /tmp
% lindera build \
--src /tmp/CC-CEDICT-MeCab-0.1.0-20200409 \
--dest /tmp/lindera-cc-cedict-0.1.0-20200409 \
--metadata ./lindera-cc-cedict/metadata.json
Build Jieba (Chinese dictionary)
% curl -L -o /tmp/mecab-jieba-0.1.1.tar.gz "https://lindera.dev/mecab-jieba-0.1.1.tar.gz"
% tar zxvf /tmp/mecab-jieba-0.1.1.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-jieba-0.1.1/dict-src \
--dest /tmp/lindera-jieba-0.1.1 \
--metadata ./lindera-jieba/metadata.json
Build ko-dic (Korean dictionary)
% curl -L -o /tmp/mecab-ko-dic-2.1.1-20180720.tar.gz "https://Lindera.dev/mecab-ko-dic-2.1.1-20180720.tar.gz"
% tar zxvf /tmp/mecab-ko-dic-2.1.1-20180720.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-ko-dic-2.1.1-20180720 \
--dest /tmp/lindera-ko-dic-2.1.1-20180720 \
--metadata ./lindera-ko-dic/metadata.json
Build user dictionaries
Build IPADIC user dictionary (Japanese)
For more details about user dictionary format please refer to the following URL:
% lindera build \
--src ./resources/user_dict/ipadic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ipadic/metadata.json \
--user
Build UniDic user dictionary (Japanese)
For more details about user dictionary format please refer to the following URL:
% lindera build \
--src ./resources/user_dict/unidic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-unidic/metadata.json \
--user
Build CC-CEDICT user dictionary (Chinese)
For more details about user dictionary format please refer to the following URL:
% lindera build \
--src ./resources/user_dict/cc-cedict_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-cc-cedict/metadata.json \
--user
Build Jieba user dictionary (Chinese)
For more details about user dictionary format please refer to the following URL:
% lindera build \
--src ./resources/user_dict/jieba_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-jieba/metadata.json \
--user
Build ko-dic user dictionary (Korean)
For more details about user dictionary format please refer to the following URL:
% lindera build \
--src ./resources/user_dict/ko-dic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ko-dic/metadata.json \
--user
train
Train a new morphological analysis model from annotated corpus data. To use this feature, you must build with the train feature flag enabled. (The train feature flag is enabled by default.)
Train parameters
--seed/-s: Seed lexicon file (CSV format) to be weighted--corpus/-c: Training corpus (annotated text)--char-def/-C: Character definition file (char.def)--unk-def/-u: Unknown word definition file (unk.def) to be weighted--feature-def/-f: Feature definition file (feature.def)--rewrite-def/-r: Rewrite rule definition file (rewrite.def)--output/-o: Output model file--lambda/-l: Regularization coefficient (0.0-1.0) (default: 0.01)--regularization/-R: Regularization type:l1,l2, orelasticnet(default:l1)--elastic-net-l1-ratio: L1 ratio for Elastic Net regularization (0.0-1.0), only used with--regularization elasticnet(default: 0.5)--max-iterations/-i: Maximum number of iterations for training (default: 100)--max-threads/-t: Maximum number of threads (defaults to CPU core count)
Basic workflow
1. Prepare training files
Seed lexicon file (seed.csv):
The seed lexicon file contains initial dictionary entries used for training the CRF model. Each line represents a word entry with comma-separated fields:
- Surface
- Left context ID
- Right context ID
- Word cost
- Part-of-speech tags (multiple fields)
- Base form
- Reading (katakana)
- Pronunciation
Note: The exact field definitions differ between dictionary formats (IPADIC, UniDic, ko-dic, CC-CEDICT). Please refer to each dictionary's format specification for details.
外国,0,0,0,名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人,0,0,0,名詞,接尾,一般,*,*,*,人,ジン,ジン
Training corpus (corpus.txt):
The training corpus file contains annotated text data used to train the CRF model. Each line consists of:
- A surface form (word) followed by a tab character
- Comma-separated morphological features (part-of-speech tags, base form, reading, pronunciation)
- Sentences are separated by "EOS" (End Of Sentence) markers
外国 名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人 名詞,接尾,一般,*,*,*,人,ジン,ジン
参政 名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
権 名詞,接尾,一般,*,*,*,権,ケン,ケン
EOS
For detailed information about file formats and advanced features, see Training Pipeline.
2. Train model
lindera train \
--seed ./resources/training/seed.csv \
--corpus ./resources/training/corpus.txt \
--unk-def ./resources/training/unk.def \
--char-def ./resources/training/char.def \
--feature-def ./resources/training/feature.def \
--rewrite-def ./resources/training/rewrite.def \
--output /tmp/lindera/training/model.dat \
--lambda 0.01 \
--max-iterations 100
3. Training results
The trained model will contain:
- Existing words: All seed dictionary records with newly learned weights
- New words: Words from the corpus not in the seed dictionary, added with appropriate weights
export
Export a trained model file to Lindera dictionary format files. This feature requires building with the train feature flag enabled.
Export parameters
--model/-m: Path to the trained model file (.dat format)--output/-o: Directory to output the dictionary files--metadata: Optional metadata.json file to update with trained model information--cost-factor: Override cost factor for weight-to-cost conversion (default: value from trained model, typically 700)
Output files
The export command creates the following dictionary files in the output directory:
lex.csv: Lexicon file with learned weights (MeCab-compatible cost viatocost())matrix.def: Dense connection cost matrix covering all (right_id, left_id) pairsunk.def: Unknown word definitionschar.def: Character type definitionsfeature.def: Feature template definitions (copied from trained model)rewrite.def: Feature rewrite rules (copied from trained model)left-id.def: Left context ID to feature string mappingright-id.def: Right context ID to feature string mappingmetadata.json: Updated metadata file (if--metadataoption is provided)
Complete workflow example
1. Train model
lindera train \
--seed ./resources/training/seed.csv \
--corpus ./resources/training/corpus.txt \
--unk-def ./resources/training/unk.def \
--char-def ./resources/training/char.def \
--feature-def ./resources/training/feature.def \
--rewrite-def ./resources/training/rewrite.def \
--output /tmp/lindera/training/model.dat \
--lambda 0.01 \
--max-iterations 100
2. Export to dictionary format
lindera export \
--model /tmp/lindera/training/model.dat \
--metadata ./resources/training/metadata.json \
--output /tmp/lindera/training/dictionary
3. Build dictionary
lindera build \
--src /tmp/lindera/training/dictionary \
--dest /tmp/lindera/training/compiled_dictionary \
--metadata /tmp/lindera/training/dictionary/metadata.json
4. Use trained dictionary
echo "これは外国人参政権です。" | lindera tokenize \
-d /tmp/lindera/training/compiled_dictionary
Metadata update feature
When the --metadata option is provided, the export command will:
- Read the base metadata.json file to preserve existing configuration
- Update specific fields with values from the trained model:
default_left_context_id: Maximum left context ID from trained modeldefault_right_context_id: Maximum right context ID from trained modeldefault_word_cost: Calculated from feature weight medianmodel_info: Training statistics including feature count, label count, matrix size, iterations, regularization, version, and timestamp
- Preserve existing settings such as dictionary name, character encoding, schema definitions, and other user-defined configuration
Tutorial
This tutorial walks you through the basic usage of the Lindera CLI, from installation to advanced text processing.
1. Install the CLI
Install Lindera CLI with the embedded IPADIC dictionary:
% cargo install lindera-cli --features=embed-ipadic
Verify the installation:
% lindera --help
2. Basic tokenization with embedded dictionary
Tokenize Japanese text using the embedded IPADIC dictionary:
% echo "東京は日本の首都です。" | lindera tokenize \
--dict embedded://ipadic
Expected output:
東京 名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
日本 名詞,固有名詞,地域,国,*,*,日本,ニホン,ニホン
の 助詞,連体化,*,*,*,*,の,ノ,ノ
首都 名詞,一般,*,*,*,*,首都,シュト,シュト
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。 記号,句点,*,*,*,*,。,。,。
EOS
3. Try different output formats
Wakati format (word segmentation only)
% echo "東京は日本の首都です。" | lindera tokenize \
--dict embedded://ipadic \
--output wakati
Expected output:
東京 は 日本 の 首都 です 。
JSON format (detailed information)
% echo "東京は日本の首都です。" | lindera tokenize \
--dict embedded://ipadic \
--output json
This produces a JSON array with detailed token information including byte offsets, part-of-speech tags, readings, and more.
4. Use decompose mode
Decompose mode splits compound nouns into their constituent parts:
% echo "関西国際空港限定トートバッグ" | lindera tokenize \
--dict embedded://ipadic \
--mode decompose
Expected output:
関西 名詞,固有名詞,地域,一般,*,*,関西,カンサイ,カンサイ
国際 名詞,一般,*,*,*,*,国際,コクサイ,コクサイ
空港 名詞,一般,*,*,*,*,空港,クウコウ,クーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
EOS
Compare with normal mode, where "関西国際空港" remains as a single token.
5. Apply character and token filters
Use Unicode normalization and keep only common nouns:
% echo "Linderaは形態素解析エンジンです。" | lindera tokenize \
--dict embedded://ipadic \
--char-filter 'unicode_normalize:{"kind":"nfkc"}' \
--token-filter 'japanese_keep_tags:{"tags":["名詞,一般","名詞,固有名詞,組織"]}'
Expected output:
Lindera 名詞,固有名詞,組織,*,*,*,*,*,*
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
エンジン 名詞,一般,*,*,*,*,エンジン,エンジン,エンジン
EOS
The Unicode normalization converts full-width characters to half-width, and the token filter keeps only tokens matching the specified part-of-speech tags.
You can also combine multiple filters:
% echo "すもももももももものうち" | lindera tokenize \
--dict embedded://ipadic \
--token-filter 'japanese_stop_tags:{"tags":["助詞","助詞,係助詞","助詞,連体化"]}'
6. Use user dictionary
Create a CSV file with custom word entries (e.g., my_dict.csv):
東京スカイツリー,カスタム名詞,トウキョウスカイツリー
Tokenize with the user dictionary:
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict embedded://ipadic \
--user-dict ./my_dict.csv
Without the user dictionary, "東京スカイツリー" would be split into multiple tokens. With the user dictionary, it is recognized as a single token.
For pre-built user dictionary examples, see:
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict embedded://ipadic \
--user-dict ./resources/user_dict/ipadic_simple_userdic.csv
Expected output:
東京スカイツリー カスタム名詞,*,*,*,*,*,東京スカイツリー,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,とうきょうスカイツリー駅,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
Lindera Python
Lindera Python provides Python bindings for the Lindera morphological analysis engine, built with PyO3. It brings Lindera's high-performance tokenization capabilities to the Python ecosystem with support for Python 3.10 and later.
Features
- Multi-language support: Tokenize Japanese (IPADIC, IPADIC NEologd, UniDic), Korean (ko-dic), and Chinese (CC-CEDICT, Jieba) text
- Text processing pipeline: Compose character filters and token filters for flexible preprocessing and postprocessing
- CRF-based dictionary training: Train custom morphological analysis models from annotated corpora (requires
trainfeature) - Multiple tokenization modes: Normal and decompose modes for different analysis granularity
- N-best tokenization: Retrieve multiple tokenization candidates ranked by cost
- User dictionaries: Extend system dictionaries with custom vocabulary
Documentation
- Installation -- Prerequisites, build instructions, and feature flags
- Quick Start -- A minimal example to get started
- Tokenizer API --
TokenizerBuilder,Tokenizer, andTokenclass reference - Dictionary Management -- Loading, building, and managing dictionaries
- Text Processing Pipeline -- Character filters and token filters
- Training -- Training custom CRF models and exporting dictionaries
Installation
Installing from PyPI
Pre-built wheels are available on PyPI:
pip install lindera-python
[!NOTE] The PyPI package does not include dictionaries. See Obtaining Dictionaries below.
Obtaining Dictionaries
Lindera does not bundle dictionaries with the package. You need to obtain a pre-built dictionary separately.
Download from GitHub Releases
Pre-built dictionaries are available on the GitHub Releases page. Download and extract the dictionary archive to a local directory:
# Example: download and extract the IPADIC dictionary
curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
Building from Source
If you need to build from source (e.g., to enable specific feature flags), the following prerequisites are required:
- Python 3.10 or later (up to 3.14)
- Rust toolchain -- Install via rustup
- maturin -- Python package for building Rust-based Python extensions
Install maturin with pip:
pip install maturin
Development Build
Build and install lindera-python in development mode:
cd lindera-python
maturin develop
Or use the project Makefile:
make python-develop
Build with Training Support
The train feature enables CRF-based dictionary training functionality. It is enabled by default:
maturin develop --features train
Feature Flags
| Feature | Description | Default |
|---|---|---|
train | CRF training functionality | Enabled |
embed-ipadic | Embed Japanese dictionary (IPADIC) into the binary | Disabled |
embed-unidic | Embed Japanese dictionary (UniDic) into the binary | Disabled |
embed-ipadic-neologd | Embed Japanese dictionary (IPADIC NEologd) into the binary | Disabled |
embed-ko-dic | Embed Korean dictionary (ko-dic) into the binary | Disabled |
embed-cc-cedict | Embed Chinese dictionary (CC-CEDICT) into the binary | Disabled |
embed-jieba | Embed Chinese dictionary (Jieba) into the binary | Disabled |
embed-cjk | Embed all CJK dictionaries (IPADIC, ko-dic, Jieba) into the binary | Disabled |
Multiple features can be combined:
maturin develop --features "train,embed-ipadic,embed-ko-dic"
[!TIP] If you want to embed a dictionary directly into the binary (advanced usage), enable the corresponding
embed-*feature flag and load it using theembedded://scheme:dictionary = load_dictionary("embedded://ipadic")See Feature Flags for details.
Verifying the Installation
After installation, verify that lindera is available in Python:
import lindera
print(lindera.version())
Quick Start
This guide shows how to tokenize text using lindera-python.
Basic Tokenization
The recommended way to create a tokenizer is through TokenizerBuilder:
from lindera import TokenizerBuilder
builder = TokenizerBuilder()
builder.set_mode("normal")
builder.set_dictionary("/path/to/ipadic")
tokenizer = builder.build()
tokens = tokenizer.tokenize("関西国際空港限定トートバッグ")
for token in tokens:
print(f"{token.surface}\t{','.join(token.details)}")
Note: Download a pre-built dictionary from GitHub Releases and specify the path to the extracted directory.
Expected output:
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ UNK
Method Chaining
TokenizerBuilder supports method chaining for concise configuration:
from lindera import TokenizerBuilder
tokenizer = (
TokenizerBuilder()
.set_mode("normal")
.set_dictionary("/path/to/ipadic")
.build()
)
tokens = tokenizer.tokenize("すもももももももものうち")
for token in tokens:
print(f"{token.surface}\t{token.get_detail(0)}")
Accessing Token Properties
Each token exposes the following properties:
from lindera import TokenizerBuilder
tokenizer = TokenizerBuilder().set_dictionary("/path/to/ipadic").build()
tokens = tokenizer.tokenize("東京タワー")
for token in tokens:
print(f"Surface: {token.surface}")
print(f"Byte range: {token.byte_start}..{token.byte_end}")
print(f"Position: {token.position}")
print(f"Word ID: {token.word_id}")
print(f"Unknown: {token.is_unknown}")
print(f"Details: {token.details}")
print()
N-best Tokenization
Retrieve multiple tokenization candidates ranked by cost:
from lindera import TokenizerBuilder
tokenizer = TokenizerBuilder().set_dictionary("/path/to/ipadic").build()
results = tokenizer.tokenize_nbest("すもももももももものうち", n=3)
for tokens, cost in results:
surfaces = [t.surface for t in tokens]
print(f"Cost {cost}: {' / '.join(surfaces)}")
Tokenizer API
TokenizerBuilder
TokenizerBuilder configures and constructs a Tokenizer instance using the builder pattern.
Constructors
TokenizerBuilder()
Creates a new builder with default configuration.
from lindera import TokenizerBuilder
builder = TokenizerBuilder()
TokenizerBuilder().from_file(file_path)
Loads configuration from a YAML file and returns a new builder. See
lindera-python/resources/lindera.yml
for a complete example covering segmenter, character_filters, and token_filters.
builder = TokenizerBuilder().from_file("lindera.yml")
Configuration Methods
All setter methods return self for method chaining.
set_mode(mode)
Sets the tokenization mode.
"normal"-- Standard tokenization (default)"decompose"-- Decomposes compound words into smaller units
builder.set_mode("normal")
set_dictionary(path)
Sets the system dictionary path or URI.
# Use an embedded dictionary
builder.set_dictionary("embedded://ipadic")
# Use an external dictionary
builder.set_dictionary("/path/to/dictionary")
set_user_dictionary(uri)
Sets the user dictionary URI.
builder.set_user_dictionary("/path/to/user_dictionary")
set_keep_whitespace(keep)
Controls whether whitespace tokens appear in the output.
builder.set_keep_whitespace(True)
append_character_filter(kind, args=None)
Appends a character filter to the preprocessing pipeline.
builder.append_character_filter("unicode_normalize", {"kind": "nfkc"})
append_token_filter(kind, args=None)
Appends a token filter to the postprocessing pipeline.
builder.append_token_filter("lowercase", {})
Build
build()
Builds and returns a Tokenizer with the configured settings.
tokenizer = builder.build()
Tokenizer
Tokenizer performs morphological analysis on text.
Creating a Tokenizer
Tokenizer(dictionary, mode="normal", user_dictionary=None)
Creates a tokenizer directly from a loaded dictionary.
from lindera import Tokenizer, load_dictionary
dictionary = load_dictionary("embedded://ipadic")
tokenizer = Tokenizer(dictionary, mode="normal")
Tokenizer Methods
tokenize(text)
Tokenizes the input text and returns a list of Token objects.
tokens = tokenizer.tokenize("形態素解析")
Parameters:
| Name | Type | Description |
|---|---|---|
text | str | Text to tokenize |
Returns: list[Token]
tokenize_nbest(text, n, unique=False, cost_threshold=None)
Returns the N-best tokenization results, each paired with its total path cost.
results = tokenizer.tokenize_nbest("すもももももももものうち", n=3)
for tokens, cost in results:
print(cost, [t.surface for t in tokens])
Parameters:
| Name | Type | Description |
|---|---|---|
text | str | Text to tokenize |
n | int | Number of results to return |
unique | bool | Deduplicate results (default: False) |
cost_threshold | int or None | Maximum cost difference from the best path (default: None) |
Returns: list[tuple[list[Token], int]]
Mode
Mode represents a tokenization mode. It is provided as a standalone helper for
inspecting or comparing modes; TokenizerBuilder.set_mode() and the Tokenizer
constructor currently accept only a plain mode string ("normal" or
"decompose"), not a Mode instance (see the limitation noted under Penalty
below).
Creating a Mode
Mode(mode_str=None)
Creates a Mode. Accepts "normal" / "Normal" (the default when omitted) or
"decompose" / "Decompose"; any other value raises ValueError.
from lindera import Mode
mode = Mode("normal")
mode = Mode("decompose")
mode = Mode() # defaults to "normal"
Methods
| Method | Returns | Description |
|---|---|---|
__str__() | str | "normal" or "decompose" |
__repr__() | str | e.g. "Mode.Normal" |
is_normal() | bool | True if the mode is "normal" |
is_decompose() | bool | True if the mode is "decompose" |
mode = Mode("decompose")
str(mode) # "decompose"
repr(mode) # "Mode.Decompose"
mode.is_normal() # False
mode.is_decompose() # True
Penalty
Penalty configures the length-based penalty thresholds used by "decompose"
mode segmentation.
Creating a Penalty
Penalty(kanji_penalty_length_threshold=2, kanji_penalty_length_penalty=3000, other_penalty_length_threshold=7, other_penalty_length_penalty=1700)
All arguments are optional and default to the values shown above.
from lindera import Penalty
penalty = Penalty(
kanji_penalty_length_threshold=2,
kanji_penalty_length_penalty=3000,
other_penalty_length_threshold=7,
other_penalty_length_penalty=1700,
)
Penalty Properties
All four fields support both getting and setting:
| Property | Type | Default | Description |
|---|---|---|---|
kanji_penalty_length_threshold | int | 2 | Kanji-only surface length above which the penalty applies |
kanji_penalty_length_penalty | int | 3000 | Cost penalty added for kanji-only surfaces longer than the threshold |
other_penalty_length_threshold | int | 7 | Surface length above which the penalty applies for non-kanji-only surfaces |
other_penalty_length_penalty | int | 1700 | Cost penalty added for non-kanji-only surfaces longer than the threshold |
penalty.kanji_penalty_length_threshold = 3
print(penalty.kanji_penalty_length_threshold) # 3
Current limitation: there is currently no way to pass a Penalty into a
Tokenizer or TokenizerBuilder. set_mode() and the Tokenizer constructor
only accept a plain mode string, and internally "decompose" mode always uses
Penalty's default values -- constructing a custom Penalty instance has no
effect on tokenization yet.
Token
Token represents a single morphological token.
Properties
| Property | Type | Description |
|---|---|---|
surface | str | Surface form of the token |
byte_start | int | Start byte position in the original text |
byte_end | int | End byte position in the original text |
position | int | Token position index |
word_id | int | Dictionary word ID |
is_unknown | bool | True if the word is not in the dictionary |
details | list[str] or None | Morphological details (part of speech, reading, etc.) |
Token Methods
get_detail(index)
Returns the detail string at the specified index, or None if the index is out of range.
token = tokenizer.tokenize("東京")[0]
pos = token.get_detail(0) # e.g., "名詞"
subpos = token.get_detail(1) # e.g., "固有名詞"
reading = token.get_detail(7) # e.g., "トウキョウ"
Parameters:
| Name | Type | Description |
|---|---|---|
index | int | Zero-based index into the details list |
Returns: str or None
The structure of details depends on the dictionary:
- IPADIC:
[品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3, 活用型, 活用形, 原形, 読み, 発音] - UniDic: Detailed morphological features following the UniDic specification
- ko-dic / CC-CEDICT / Jieba: Dictionary-specific detail formats
Error Handling
Lindera Python functions raise standard Python exceptions rather than a custom exception type:
IOError(an alias ofOSError) -- for I/O-related failures, such as a missing or unreadable fileValueError-- for everything else, such as invalid configuration, parse errors, or tokenization failures
from lindera import load_dictionary
try:
dictionary = load_dictionary("/path/that/does/not/exist")
except ValueError as e:
print(f"Failed to load dictionary: {e}")
A LinderaError class is also registered as lindera.LinderaError, but no
function in this crate currently raises it -- it can only be constructed and
raised manually. Catch IOError/ValueError (or the general Exception) when
handling errors from this library, not LinderaError.
Dictionary Management
Lindera Python provides functions for loading, building, and managing dictionaries used in morphological analysis.
Loading Dictionaries
System Dictionaries
Use load_dictionary(uri) to load a system dictionary. Download a pre-built dictionary from GitHub Releases and specify the path to the extracted directory:
from lindera import load_dictionary
dictionary = load_dictionary("/path/to/ipadic")
Embedded dictionaries (advanced) -- if you built with an embed-* feature flag, you can load an embedded dictionary:
dictionary = load_dictionary("embedded://ipadic")
A loaded Dictionary also exposes its own metadata:
print(dictionary.metadata_name()) # e.g. "ipadic"
print(dictionary.metadata_encoding()) # e.g. "UTF-8"
metadata = dictionary.metadata() # the full Metadata object
This is useful for loading a user dictionary that must share the same
metadata (schema, encoding, etc.) as the system dictionary it augments, as in
lindera-python/examples/tokenize_with_userdict.py:
from lindera import Tokenizer, load_dictionary, load_user_dictionary
dictionary = load_dictionary("embedded://ipadic")
metadata = dictionary.metadata()
user_dictionary = load_user_dictionary("/path/to/user_dictionary.csv", metadata)
tokenizer = Tokenizer(dictionary, mode="normal", user_dictionary=user_dictionary)
User Dictionaries
User dictionaries add custom vocabulary on top of a system dictionary.
from lindera import load_user_dictionary, Metadata
metadata = Metadata()
user_dict = load_user_dictionary("/path/to/user_dictionary", metadata)
Pass the user dictionary when building a tokenizer:
from lindera import Tokenizer, load_dictionary, load_user_dictionary, Metadata
dictionary = load_dictionary("/path/to/ipadic")
metadata = Metadata()
user_dict = load_user_dictionary("/path/to/user_dictionary", metadata)
tokenizer = Tokenizer(dictionary, mode="normal", user_dictionary=user_dict)
Or via the builder:
from lindera import TokenizerBuilder
tokenizer = (
TokenizerBuilder()
.set_dictionary("/path/to/ipadic")
.set_user_dictionary("/path/to/user_dictionary")
.build()
)
Building Dictionaries
System Dictionary
Build a system dictionary from source files:
from lindera import build_dictionary, Metadata
metadata = Metadata(name="custom", encoding="UTF-8")
build_dictionary("/path/to/input_dir", "/path/to/output_dir", metadata)
The input directory should contain the dictionary source files (CSV lexicon, matrix.def, etc.).
User Dictionary
Build a user dictionary from a CSV file:
from lindera import build_user_dictionary, Metadata
metadata = Metadata()
build_user_dictionary("ipadic", "user_words.csv", "/path/to/output_dir", metadata)
The metadata parameter is optional. When omitted, default metadata values are used:
build_user_dictionary("ipadic", "user_words.csv", "/path/to/output_dir")
Note: the first argument ("ipadic" above) is currently unused and reserved
for future use -- it does not select or configure the build in any way. The
build behavior is controlled entirely by metadata (in particular
metadata.user_dictionary_schema). Any string may be passed today.
Metadata
The Metadata class configures dictionary parameters.
Creating Metadata
from lindera import Metadata
# Default metadata
metadata = Metadata()
# Custom metadata
metadata = Metadata(
name="my_dictionary",
encoding="UTF-8",
default_word_cost=-10000,
)
Loading from JSON
metadata = Metadata.from_json_file("metadata.json")
Properties
| Property | Type | Default | Description |
|---|---|---|---|
name | str | "default" | Dictionary name |
encoding | str | "UTF-8" | Character encoding |
default_word_cost | int | -10000 | Default cost for unknown words |
default_left_context_id | int | 1288 | Default left context ID |
default_right_context_id | int | 1288 | Default right context ID |
default_field_value | str | "*" | Default value for missing fields |
flexible_csv | bool | False | Allow flexible CSV parsing |
skip_invalid_cost_or_id | bool | False | Skip entries with invalid cost or ID |
normalize_details | bool | False | Normalize morphological details |
dictionary_schema | Schema | IPADIC schema | Schema for the main dictionary |
user_dictionary_schema | Schema | Minimal schema | Schema for user dictionaries |
All properties support both getting and setting:
metadata = Metadata()
metadata.name = "custom_dict"
metadata.encoding = "EUC-JP"
print(metadata.name) # "custom_dict"
to_dict()
Returns a dictionary representation of the metadata:
metadata = Metadata(name="test")
print(metadata.to_dict())
Schema
Schema, FieldDefinition, and FieldType describe the field layout of a
dictionary entry. A schema is used by Metadata.dictionary_schema and
Metadata.user_dictionary_schema (see the table above).
FieldType
FieldType enumerates the category of a single field:
FieldType.Surface-- surface form (word text)FieldType.LeftContextId-- left context IDFieldType.RightContextId-- right context IDFieldType.Cost-- word costFieldType.Custom-- any other, dictionary-specific field
FieldDefinition
FieldDefinition describes a single field within a schema.
FieldDefinition(index, name, field_type, description=None)
from lindera import FieldDefinition, FieldType
field = FieldDefinition(0, "surface", FieldType.Surface, "Surface form")
Properties (read-only):
| Property | Type | Description |
|---|---|---|
index | int | Zero-based position of the field within the schema |
name | str | Field name |
field_type | FieldType | Field type |
description | str or None | Optional human-readable description |
Creating a Schema
Schema holds an ordered list of field names and provides lookups between
field name and index.
Schema(fields)
Creates a schema from a list of field names.
from lindera import Schema
schema = Schema([
"surface",
"left_context_id",
"right_context_id",
"cost",
"major_pos",
"reading",
])
Schema.create_default()
A static method that returns the built-in default schema: 13 fields matching
the IPADIC-style layout (surface, left_context_id, right_context_id,
cost, major_pos, pos_detail_1, pos_detail_2, pos_detail_3,
conjugation_type, conjugation_form, base_form, reading,
pronunciation).
schema = Schema.create_default()
Schema Methods and Properties
| Member | Returns | Description |
|---|---|---|
fields (property) | list[str] | All field names, in order |
field_count() | int | Total number of fields |
get_field_index(name) | int or None | Index of the field named name |
get_field_name(index) | str or None | Field name at index |
get_custom_fields() | list[str] | Field names after the four fixed fields (surface, left_context_id, right_context_id, cost) |
get_field_by_name(name) | FieldDefinition or None | Full field definition for name |
validate_record(record) | None | Raises ValueError if record does not match the schema |
__len__() | int | Same as field_count() |
schema = Schema.create_default()
schema.field_count() # 13
schema.get_field_index("cost") # 3
schema.get_field_name(0) # "surface"
schema.get_custom_fields() # ["major_pos", "pos_detail_1", ..., "pronunciation"]
len(schema) # 13
field = schema.get_field_by_name("surface")
print(field.index, field.name, field.field_type) # 0 surface FieldType.Surface
schema.validate_record([
"東京", "1288", "1288", "100",
"名詞", "固有名詞", "地域", "一般", "*", "*",
"東京", "トウキョウ", "トーキョー",
])
Text Processing Pipeline
Lindera Python supports a composable text processing pipeline that applies character filters before tokenization and token filters after tokenization. Filters are added to the TokenizerBuilder and executed in the order they are appended.
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
Character Filters
Character filters transform the input text before tokenization.
unicode_normalize
Applies Unicode normalization to the input text.
from lindera import TokenizerBuilder
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_character_filter("unicode_normalize", {"kind": "nfkc"})
.build()
)
Supported normalization forms: "nfc", "nfkc", "nfd", "nfkd".
mapping
Replaces characters or strings according to a mapping table.
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_character_filter("mapping", {
"mapping": {
"\u30fc": "-",
"\uff5e": "~",
}
})
.build()
)
japanese_iteration_mark
Resolves Japanese iteration marks (odoriji) into their full forms.
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_character_filter("japanese_iteration_mark", {
"normalize_kanji": True,
"normalize_kana": True,
})
.build()
)
Token Filters
Token filters transform or remove tokens after tokenization.
lowercase
Converts token surface forms to lowercase.
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_token_filter("lowercase", {})
.build()
)
japanese_base_form
Replaces inflected forms with their base (dictionary) form using the morphological details from the dictionary.
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_token_filter("japanese_base_form", {})
.build()
)
japanese_stop_tags
Removes tokens whose part-of-speech matches any of the specified tags.
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_token_filter("japanese_stop_tags", {
"tags": ["助詞", "助動詞"],
})
.build()
)
japanese_keep_tags
Keeps only tokens whose part-of-speech matches one of the specified tags. All other tokens are removed.
tokenizer = (
TokenizerBuilder()
.set_dictionary("embedded://ipadic")
.append_token_filter("japanese_keep_tags", {
"tags": ["名詞"],
})
.build()
)
Complete Pipeline Example
The following example combines multiple character filters and token filters into a single pipeline:
from lindera import TokenizerBuilder
tokenizer = (
TokenizerBuilder()
.set_mode("normal")
.set_dictionary("embedded://ipadic")
# Preprocessing
.append_character_filter("unicode_normalize", {"kind": "nfkc"})
.append_character_filter("japanese_iteration_mark", {
"normalize_kanji": True,
"normalize_kana": True,
})
# Postprocessing
.append_token_filter("japanese_base_form", {})
.append_token_filter("japanese_stop_tags", {
"tags": ["助詞", "助動詞", "記号"],
})
.append_token_filter("lowercase", {})
.build()
)
tokens = tokenizer.tokenize("Linderaは形態素解析を行うライブラリです。")
for token in tokens:
print(f"{token.surface}\t{','.join(token.details)}")
In this pipeline:
unicode_normalizeconverts full-width characters to half-width (NFKC normalization)japanese_iteration_markresolves iteration marksjapanese_base_formconverts inflected tokens to base formjapanese_stop_tagsremoves particles, auxiliary verbs, and symbolslowercasenormalizes alphabetic characters to lowercase
Training
Lindera Python supports training custom CRF-based morphological analysis models from annotated corpora. This functionality requires the train feature.
Prerequisites
Build lindera-python with the train feature enabled (enabled by default):
maturin develop --features train
Training a Model
Use lindera.train() to train a CRF model from a seed lexicon and annotated corpus:
import lindera
lindera.train(
seed="resources/training/seed.csv",
corpus="resources/training/corpus.txt",
char_def="resources/training/char.def",
unk_def="resources/training/unk.def",
feature_def="resources/training/feature.def",
rewrite_def="resources/training/rewrite.def",
output="/tmp/model.dat",
lambda_=0.01,
max_iter=100,
max_threads=4,
)
Training Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
seed | str | required | Path to the seed lexicon file (CSV format) |
corpus | str | required | Path to the annotated training corpus |
char_def | str | required | Path to the character definition file (char.def) |
unk_def | str | required | Path to the unknown word definition file (unk.def) |
feature_def | str | required | Path to the feature definition file (feature.def) |
rewrite_def | str | required | Path to the rewrite rule definition file (rewrite.def) |
output | str | required | Output path for the trained model file |
lambda_ | float | 0.01 | L1 regularization cost (0.0--1.0) |
max_iter | int | 100 | Maximum number of training iterations |
max_threads | int or None | None | Number of threads (None = auto-detect CPU cores) |
Exporting a Trained Model
After training, export the model to dictionary source files using lindera.export():
import lindera
lindera.export(
model="/tmp/model.dat",
output="/tmp/dictionary_source",
metadata="resources/training/metadata.json",
)
Export Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
model | str | required | Path to the trained model file (.dat) |
output | str | required | Output directory for dictionary source files |
metadata | str or None | None | Path to a base metadata.json file |
The export creates the following files in the output directory:
lex.csv-- Lexicon entries with trained costsmatrix.def-- Connection cost matrixunk.def-- Unknown word definitionschar.def-- Character category definitionsmetadata.json-- Updated metadata (whenmetadataparameter is provided)
Complete Workflow
The full workflow for training and using a custom dictionary:
import lindera
# Step 1: Train the CRF model
lindera.train(
seed="resources/training/seed.csv",
corpus="resources/training/corpus.txt",
char_def="resources/training/char.def",
unk_def="resources/training/unk.def",
feature_def="resources/training/feature.def",
rewrite_def="resources/training/rewrite.def",
output="/tmp/model.dat",
lambda_=0.01,
max_iter=100,
)
# Step 2: Export to dictionary source files
lindera.export(
model="/tmp/model.dat",
output="/tmp/dictionary_source",
metadata="resources/training/metadata.json",
)
# Step 3: Build the dictionary from exported source files
metadata = lindera.Metadata.from_json_file("/tmp/dictionary_source/metadata.json")
lindera.build_dictionary("/tmp/dictionary_source", "/tmp/dictionary", metadata)
# Step 4: Use the trained dictionary
tokenizer = (
lindera.TokenizerBuilder()
.set_dictionary("/tmp/dictionary")
.set_mode("normal")
.build()
)
tokens = tokenizer.tokenize("形態素解析のテスト")
for token in tokens:
print(f"{token.surface}\t{','.join(token.details)}")
Lindera Node.js
Lindera Node.js provides Node.js bindings for the Lindera morphological analysis engine, built with NAPI-RS. It brings Lindera's high-performance tokenization capabilities to the Node.js ecosystem with support for Node.js 18 and later.
Features
- Multi-language support: Tokenize Japanese (IPADIC, IPADIC NEologd, UniDic), Korean (ko-dic), and Chinese (CC-CEDICT, Jieba) text
- Text processing pipeline: Compose character filters and token filters for flexible preprocessing and postprocessing
- CRF-based dictionary training: Train custom morphological analysis models from annotated corpora (requires
trainfeature) - Multiple tokenization modes: Normal and decompose modes for different analysis granularity
- N-best tokenization: Retrieve multiple tokenization candidates ranked by cost
- User dictionaries: Extend system dictionaries with custom vocabulary
- TypeScript support: Full type definitions included out of the box
Documentation
- Installation -- Prerequisites, build instructions, and feature flags
- Quick Start -- A minimal example to get started
- Tokenizer API --
TokenizerBuilder,Tokenizer, andTokenclass reference - Dictionary Management -- Loading, building, and managing dictionaries
- Text Processing Pipeline -- Character filters and token filters
- Training -- Training custom CRF models and exporting dictionaries
Installation
Installing from npm
Pre-built packages will be available on npm:
npm install lindera-nodejs
[!NOTE] The npm package does not include dictionaries. See Obtaining Dictionaries below. For browser/WASM usage, see lindera-wasm.
Building from Source
Prerequisites
- Node.js 18 or later (LTS versions recommended)
- Rust toolchain -- Install via rustup
- NAPI-RS CLI -- CLI tool for building native Node.js addons in Rust
Install the NAPI-RS CLI globally:
npm install -g @napi-rs/cli
Obtaining Dictionaries
Lindera does not bundle dictionaries with the package. You need to obtain a pre-built dictionary separately.
Download from GitHub Releases
Pre-built dictionaries are available on the GitHub Releases page. Download and extract the dictionary archive to a local directory:
# Example: download and extract the IPADIC dictionary
curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
Development Build
Build lindera-nodejs in development mode:
cd lindera-nodejs
npm install
npm run build
Or use the project Makefile:
make nodejs-develop
Build with Training Support
The train feature enables CRF-based dictionary training functionality. It is enabled by default:
npm run build -- --features train
Feature Flags
| Feature | Description | Default |
|---|---|---|
train | CRF training functionality | Enabled |
embed-ipadic | Embed Japanese dictionary (IPADIC) into the binary | Disabled |
embed-unidic | Embed Japanese dictionary (UniDic) into the binary | Disabled |
embed-ipadic-neologd | Embed Japanese dictionary (IPADIC NEologd) into the binary | Disabled |
embed-ko-dic | Embed Korean dictionary (ko-dic) into the binary | Disabled |
embed-cc-cedict | Embed Chinese dictionary (CC-CEDICT) into the binary | Disabled |
embed-jieba | Embed Chinese dictionary (Jieba) into the binary | Disabled |
embed-cjk | Embed all CJK dictionaries (IPADIC, ko-dic, Jieba) into the binary | Disabled |
Multiple features can be combined:
npm run build -- --features "train,embed-ipadic,embed-ko-dic"
[!TIP] If you want to embed a dictionary directly into the binary (advanced usage), enable the corresponding
embed-*feature flag and load it using theembedded://scheme:const dictionary = loadDictionary("embedded://ipadic");See Feature Flags for details.
Verifying the Installation
After installation, verify that lindera is available in Node.js:
const lindera = require("lindera-nodejs");
console.log(lindera.version());
[!NOTE]
lindera-nodejs'spackage.jsoncurrently declares only arequirecondition in itsexportsmap (noimportcondition), soimport { version } from "lindera-nodejs"fails withERR_PACKAGE_PATH_NOT_EXPORTED. From an ES module, load it with Node'screateRequireinstead:
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const lindera = require("lindera-nodejs");
console.log(lindera.version());
Quick Start
This guide shows how to tokenize text using lindera-nodejs.
Basic Tokenization
The recommended way to create a tokenizer is through TokenizerBuilder:
const { TokenizerBuilder } = require("lindera-nodejs");
const builder = new TokenizerBuilder();
builder.setMode("normal");
builder.setDictionary("/path/to/ipadic");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("関西国際空港限定トートバッグ");
for (const token of tokens) {
console.log(`${token.surface}\t${token.details.join(",")}`);
}
Note: Download a pre-built dictionary from GitHub Releases and specify the path to the extracted directory.
Expected output:
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ UNK
Method Chaining
TokenizerBuilder supports method chaining for concise configuration:
const { TokenizerBuilder } = require("lindera-nodejs");
const tokenizer = new TokenizerBuilder()
.setMode("normal")
.setDictionary("/path/to/ipadic")
.build();
const tokens = tokenizer.tokenize("すもももももももものうち");
for (const token of tokens) {
console.log(`${token.surface}\t${token.getDetail(0)}`);
}
Accessing Token Properties
Each token exposes the following properties:
const { TokenizerBuilder } = require("lindera-nodejs");
const tokenizer = new TokenizerBuilder()
.setDictionary("/path/to/ipadic")
.build();
const tokens = tokenizer.tokenize("東京タワー");
for (const token of tokens) {
console.log(`Surface: ${token.surface}`);
console.log(`Byte range: ${token.byteStart}..${token.byteEnd}`);
console.log(`Position: ${token.position}`);
console.log(`Word ID: ${token.wordId}`);
console.log(`Unknown: ${token.isUnknown}`);
console.log(`Details: ${token.details}`);
console.log();
}
N-best Tokenization
Retrieve multiple tokenization candidates ranked by cost:
const { TokenizerBuilder } = require("lindera-nodejs");
const tokenizer = new TokenizerBuilder()
.setDictionary("/path/to/ipadic")
.build();
const results = tokenizer.tokenizeNbest("すもももももももものうち", 3);
for (const { tokens, cost } of results) {
const surfaces = tokens.map((t) => t.surface);
console.log(`Cost ${cost}: ${surfaces.join(" / ")}`);
}
TypeScript
Lindera Node.js includes TypeScript type definitions. All classes and functions are fully typed:
import type { Token } from "lindera-nodejs";
import { createRequire } from "node:module";
// `lindera-nodejs` only exposes a CommonJS `require` entry point (see Installation),
// so ESM projects load the runtime values through `createRequire` while still getting
// full type information via `import type`.
const require = createRequire(import.meta.url);
const { TokenizerBuilder } = require("lindera-nodejs");
const tokenizer = new TokenizerBuilder()
.setMode("normal")
.setDictionary("/path/to/ipadic")
.build();
const tokens: Token[] = tokenizer.tokenize("形態素解析");
for (const token of tokens) {
console.log(`${token.surface}: ${token.details.join(",")}`);
}
Tokenizer API
TokenizerBuilder
TokenizerBuilder configures and constructs a Tokenizer instance using the builder pattern.
Constructors
new TokenizerBuilder()
Creates a new builder with default configuration.
const { TokenizerBuilder } = require("lindera-nodejs");
const builder = new TokenizerBuilder();
new TokenizerBuilder().fromFile(filePath)
Loads configuration from a YAML file and returns a new builder.
const builder = new TokenizerBuilder().fromFile("lindera.yml");
See lindera-nodejs/resources/lindera.yml for a complete example configuration file, including dictionary, character filter, and token filter settings.
Configuration Methods
All setter methods return this for method chaining.
setMode(mode)
Sets the tokenization mode.
"normal"-- Standard tokenization (default)"decompose"-- Decomposes compound words into smaller units
builder.setMode("normal");
setDictionary(path)
Sets the system dictionary path or URI.
// Use an embedded dictionary
builder.setDictionary("embedded://ipadic");
// Use an external dictionary
builder.setDictionary("/path/to/dictionary");
setUserDictionary(uri)
Sets the user dictionary URI.
builder.setUserDictionary("/path/to/user_dictionary");
setKeepWhitespace(keep)
Controls whether whitespace tokens appear in the output.
builder.setKeepWhitespace(true);
appendCharacterFilter(kind, args?)
Appends a character filter to the preprocessing pipeline.
builder.appendCharacterFilter("unicode_normalize", { kind: "nfkc" });
appendTokenFilter(kind, args?)
Appends a token filter to the postprocessing pipeline.
builder.appendTokenFilter("lowercase", {});
Build
build()
Builds and returns a Tokenizer with the configured settings.
const tokenizer = builder.build();
Tokenizer
Tokenizer performs morphological analysis on text.
Creating a Tokenizer
new Tokenizer(dictionary, mode?, userDictionary?)
Creates a tokenizer directly from a loaded dictionary.
const { Tokenizer, loadDictionary } = require("lindera-nodejs");
const dictionary = loadDictionary("embedded://ipadic");
const tokenizer = new Tokenizer(dictionary, "normal");
Tokenizer Methods
tokenize(text)
Tokenizes the input text and returns an array of Token objects.
const tokens = tokenizer.tokenize("形態素解析");
Parameters:
| Name | Type | Description |
|---|---|---|
text | string | Text to tokenize |
Returns: Token[]
tokenizeNbest(text, n, unique?, costThreshold?)
Returns the N-best tokenization results, each containing tokens and total path cost.
const results = tokenizer.tokenizeNbest("すもももももももものうち", 3);
for (const { tokens, cost } of results) {
console.log(cost, tokens.map((t) => t.surface));
}
Parameters:
| Name | Type | Description |
|---|---|---|
text | string | Text to tokenize |
n | number | Number of results to return |
unique | boolean | Deduplicate results (default: false) |
costThreshold | number | undefined | Maximum cost difference from the best path (default: undefined) |
Returns: Array<{ tokens: Token[], cost: number }>
Token
Token represents a single morphological token.
Properties
| Property | Type | Description |
|---|---|---|
surface | string | Surface form of the token |
byteStart | number | Start byte position in the original text |
byteEnd | number | End byte position in the original text |
position | number | Token position index |
wordId | number | Dictionary word ID |
isUnknown | boolean | true if the word is not in the dictionary |
details | string[] | Morphological details (part of speech, reading, etc.) |
Token Methods
getDetail(index)
Returns the detail string at the specified index, or null if the index is out of range.
const token = tokenizer.tokenize("東京")[0];
const pos = token.getDetail(0); // e.g., "名詞"
const subpos = token.getDetail(1); // e.g., "固有名詞"
const reading = token.getDetail(7); // e.g., "トウキョウ"
Parameters:
| Name | Type | Description |
|---|---|---|
index | number | Zero-based index into the details array |
Returns: string | null
The structure of details depends on the dictionary:
- IPADIC:
[品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3, 活用型, 活用形, 原形, 読み, 発音] - UniDic: Detailed morphological features following the UniDic specification
- ko-dic / CC-CEDICT / Jieba: Dictionary-specific detail formats
Mode and Penalty
Mode and Penalty are exported from lindera-nodejs, but they are not currently wired into
any public API: TokenizerBuilder.setMode() / Tokenizer's constructor accept only a plain
mode string ("normal" or "decompose"), and decompose mode always uses the default penalty
configuration internally. These types are documented here for completeness; they cannot yet be
used to customize penalty behavior from JavaScript.
Mode
A string enum with two values:
Mode.Normal-- standard dictionary-cost-based tokenizationMode.Decompose-- penalty-based decomposition of compound words
Penalty
An object shape describing the penalty parameters used by decompose mode:
| Property | Type | Description |
|---|---|---|
kanjiPenaltyLengthThreshold | number | Length threshold for kanji sequences before a penalty is applied (default: 2) |
kanjiPenaltyLengthPenalty | number | Penalty value for long kanji sequences (default: 3000) |
otherPenaltyLengthThreshold | number | Length threshold for other character sequences before a penalty is applied (default: 7) |
otherPenaltyLengthPenalty | number | Penalty value for long other-character sequences (default: 1700) |
Dictionary Management
Lindera Node.js provides functions for loading, building, and managing dictionaries used in morphological analysis.
Loading Dictionaries
System Dictionaries
Use loadDictionary(uri) to load a system dictionary. Download a pre-built dictionary from GitHub Releases and specify the path to the extracted directory:
const { loadDictionary } = require("lindera-nodejs");
const dictionary = loadDictionary("/path/to/ipadic");
Embedded dictionaries (advanced) -- if you built with an embed-* feature flag, you can load an embedded dictionary:
const dictionary = loadDictionary("embedded://ipadic");
Dictionary exposes a few read-only accessors for inspecting the loaded dictionary's metadata:
console.log(dictionary.metadataName()); // e.g. "ipadic"
console.log(dictionary.metadataEncoding()); // e.g. "UTF-8"
const metadata = dictionary.metadata(); // full Metadata object
console.log(metadata.defaultWordCost);
User Dictionaries
User dictionaries add custom vocabulary on top of a system dictionary.
const { loadUserDictionary, Metadata } = require("lindera-nodejs");
const metadata = new Metadata();
const userDict = loadUserDictionary("/path/to/user_dictionary", metadata);
Pass the user dictionary when building a tokenizer:
const { Tokenizer, loadDictionary, loadUserDictionary, Metadata } = require("lindera-nodejs");
const dictionary = loadDictionary("/path/to/ipadic");
const metadata = new Metadata();
const userDict = loadUserDictionary("/path/to/user_dictionary", metadata);
const tokenizer = new Tokenizer(dictionary, "normal", userDict);
Or via the builder:
const { TokenizerBuilder } = require("lindera-nodejs");
const tokenizer = new TokenizerBuilder()
.setDictionary("/path/to/ipadic")
.setUserDictionary("/path/to/user_dictionary")
.build();
Building Dictionaries
System Dictionary
Build a system dictionary from source files:
const { buildDictionary, Metadata } = require("lindera-nodejs");
const metadata = new Metadata({ name: "custom", encoding: "UTF-8" });
buildDictionary("/path/to/input_dir", "/path/to/output_dir", metadata);
The input directory should contain the dictionary source files (CSV lexicon, matrix.def, etc.).
User Dictionary
Build a user dictionary from a CSV file:
const { buildUserDictionary, Metadata } = require("lindera-nodejs");
const metadata = new Metadata();
buildUserDictionary("ipadic", "user_words.csv", "/path/to/output_dir", metadata);
The metadata parameter is optional. When omitted, default metadata values are used:
buildUserDictionary("ipadic", "user_words.csv", "/path/to/output_dir");
[!NOTE] The first argument (
kind) is currently unused -- it is reserved for future use and has no effect on the build. Any string value can be passed for it today.
Metadata
The Metadata class configures dictionary parameters.
Creating Metadata
const { Metadata } = require("lindera-nodejs");
// Default metadata
const metadata = new Metadata();
// Custom metadata
const metadata = new Metadata({
name: "my_dictionary",
encoding: "UTF-8",
defaultWordCost: -10000,
});
Loading from JSON
const metadata = Metadata.fromJsonFile("metadata.json");
Properties
| Property | Type | Default | Description |
|---|---|---|---|
name | string | "default" | Dictionary name |
encoding | string | "UTF-8" | Character encoding |
defaultWordCost | number | -10000 | Default cost for unknown words |
defaultLeftContextId | number | 1288 | Default left context ID |
defaultRightContextId | number | 1288 | Default right context ID |
defaultFieldValue | string | "*" | Default value for missing fields |
flexibleCsv | boolean | false | Allow flexible CSV parsing |
skipInvalidCostOrId | boolean | false | Skip entries with invalid cost or ID |
normalizeDetails | boolean | false | Normalize morphological details |
[!NOTE] Schema information (dictionary/user-dictionary field layout) is not exposed on this binding's
Metadataobject -- there are nodictionarySchema/userDictionarySchemaproperties. Use the standaloneSchemaclass instead.
All properties support both getting and setting:
const metadata = new Metadata();
metadata.name = "custom_dict";
metadata.encoding = "EUC-JP";
console.log(metadata.name); // "custom_dict"
toObject()
Returns a plain object representation of the metadata:
const metadata = new Metadata({ name: "test" });
console.log(metadata.toObject());
Schema
The Schema class defines the field structure of dictionary entries.
Creating a Schema
const { Schema } = require("lindera-nodejs");
// Default IPADIC-compatible schema
const schema = Schema.createDefault();
// Custom schema
const custom = new Schema(["surface", "left_id", "right_id", "cost", "pos", "reading"]);
Schema Methods
| Method | Returns | Description |
|---|---|---|
getFieldIndex(name) | number | null | Get field index by name |
fieldCount() | number | Total number of fields |
getFieldName(index) | string | null | Get field name by index |
getCustomFields() | string[] | Fields from index 4 onward (morphological features) |
getAllFields() | string[] | All field names |
getFieldByName(name) | FieldDefinition | null | Get full field definition |
validateRecord(record) | void | Validate a CSV record against the schema |
const schema = Schema.createDefault();
console.log(schema.fieldCount()); // 13 (IPADIC format)
console.log(schema.getFieldIndex("pos1")); // e.g., 4
console.log(schema.getAllFields()); // ["surface", "left_id", ...]
console.log(schema.getCustomFields()); // Fields from index 4 onward
FieldDefinition
| Property | Type | Description |
|---|---|---|
index | number | Field position index |
name | string | Field name |
fieldType | FieldType | Field type enum |
description | string | undefined | Optional description |
FieldType
| Value | Description |
|---|---|
FieldType.Surface | Word surface text |
FieldType.LeftContextId | Left context ID |
FieldType.RightContextId | Right context ID |
FieldType.Cost | Word cost |
FieldType.Custom | Morphological feature field |
Text Processing Pipeline
Lindera Node.js supports a composable text processing pipeline that applies character filters before tokenization and token filters after tokenization. Filters are added to the TokenizerBuilder and executed in the order they are appended.
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
[!NOTE] This page shows a few commonly used filters as examples -- it is not the complete list.
lindera-analysisships 4 character filters and 18 token filters in total. See Filters for the full, authoritative catalogue of every character and token filter, including parameters and examples.
Character Filters
Character filters transform the input text before tokenization.
unicode_normalize
Applies Unicode normalization to the input text.
const { TokenizerBuilder } = require("lindera-nodejs");
const tokenizer = new TokenizerBuilder()
.setDictionary("embedded://ipadic")
.appendCharacterFilter("unicode_normalize", { kind: "nfkc" })
.build();
Supported normalization forms: "nfc", "nfkc", "nfd", "nfkd".
mapping
Replaces characters or strings according to a mapping table.
const tokenizer = new TokenizerBuilder()
.setDictionary("embedded://ipadic")
.appendCharacterFilter("mapping", {
mapping: {
"\u30fc": "-",
"\uff5e": "~",
},
})
.build();
japanese_iteration_mark
Resolves Japanese iteration marks (odoriji) into their full forms.
const tokenizer = new TokenizerBuilder()
.setDictionary("embedded://ipadic")
.appendCharacterFilter("japanese_iteration_mark", {
normalize_kanji: true,
normalize_kana: true,
})
.build();
Token Filters
Token filters transform or remove tokens after tokenization.
lowercase
Converts token surface forms to lowercase.
const tokenizer = new TokenizerBuilder()
.setDictionary("embedded://ipadic")
.appendTokenFilter("lowercase", {})
.build();
japanese_base_form
Replaces inflected forms with their base (dictionary) form using the morphological details from the dictionary.
const tokenizer = new TokenizerBuilder()
.setDictionary("embedded://ipadic")
.appendTokenFilter("japanese_base_form", {})
.build();
japanese_stop_tags
Removes tokens whose part-of-speech matches any of the specified tags.
const tokenizer = new TokenizerBuilder()
.setDictionary("embedded://ipadic")
.appendTokenFilter("japanese_stop_tags", {
tags: ["助詞", "助動詞"],
})
.build();
japanese_keep_tags
Keeps only tokens whose part-of-speech matches one of the specified tags. All other tokens are removed.
const tokenizer = new TokenizerBuilder()
.setDictionary("embedded://ipadic")
.appendTokenFilter("japanese_keep_tags", {
tags: ["名詞"],
})
.build();
Complete Pipeline Example
The following example combines multiple character filters and token filters into a single pipeline:
const { TokenizerBuilder } = require("lindera-nodejs");
const tokenizer = new TokenizerBuilder()
.setMode("normal")
.setDictionary("embedded://ipadic")
// Preprocessing
.appendCharacterFilter("unicode_normalize", { kind: "nfkc" })
.appendCharacterFilter("japanese_iteration_mark", {
normalize_kanji: true,
normalize_kana: true,
})
// Postprocessing
.appendTokenFilter("japanese_base_form", {})
.appendTokenFilter("japanese_stop_tags", {
tags: ["助詞", "助動詞", "記号"],
})
.appendTokenFilter("lowercase", {})
.build();
const tokens = tokenizer.tokenize("Linderaは形態素解析を行うライブラリです。");
for (const token of tokens) {
console.log(`${token.surface}\t${token.details.join(",")}`);
}
In this pipeline:
unicode_normalizeconverts full-width characters to half-width (NFKC normalization)japanese_iteration_markresolves iteration marksjapanese_base_formconverts inflected tokens to base formjapanese_stop_tagsremoves particles, auxiliary verbs, and symbolslowercasenormalizes alphabetic characters to lowercase
Training
Lindera Node.js supports training custom CRF-based morphological analysis models from annotated corpora. This functionality requires the train feature.
Prerequisites
Build lindera-nodejs with the train feature enabled (enabled by default):
npm run build -- --features train
Training a Model
[!NOTE] The file paths below (
resources/training/*.csv,*.def) are illustrative placeholders — this repository does not ship those exact files. For a complete, runnable example that generates a seed lexicon, corpus, and definition files on the fly and then trains/exports/builds a real dictionary, seelindera-nodejs/examples/train_and_export.js.
Use train() to train a CRF model from a seed lexicon and annotated corpus:
const { train } = require("lindera-nodejs");
train({
seed: "resources/training/seed.csv",
corpus: "resources/training/corpus.txt",
charDef: "resources/training/char.def",
unkDef: "resources/training/unk.def",
featureDef: "resources/training/feature.def",
rewriteDef: "resources/training/rewrite.def",
output: "/tmp/model.dat",
lambda: 0.01,
maxIter: 100,
maxThreads: 4,
});
Training Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
seed | string | required | Path to the seed lexicon file (CSV format) |
corpus | string | required | Path to the annotated training corpus |
charDef | string | required | Path to the character definition file (char.def) |
unkDef | string | required | Path to the unknown word definition file (unk.def) |
featureDef | string | required | Path to the feature definition file (feature.def) |
rewriteDef | string | required | Path to the rewrite rule definition file (rewrite.def) |
output | string | required | Output path for the trained model file |
lambda | number | 0.01 | L1 regularization cost (0.0--1.0) |
maxIter | number | 100 | Maximum number of training iterations |
maxThreads | number | undefined | undefined | Number of threads (undefined = auto-detect CPU cores) |
Exporting a Trained Model
After training, export the model to dictionary source files using exportModel():
const { exportModel } = require("lindera-nodejs");
exportModel({
model: "/tmp/model.dat",
output: "/tmp/dictionary_source",
metadata: "resources/training/metadata.json",
});
Export Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
model | string | required | Path to the trained model file (.dat) |
output | string | required | Output directory for dictionary source files |
metadata | string | undefined | undefined | Path to a base metadata.json file |
The export creates the following files in the output directory:
lex.csv-- Lexicon entries with trained costsmatrix.def-- Connection cost matrixunk.def-- Unknown word definitionschar.def-- Character category definitionsmetadata.json-- Updated metadata (whenmetadataparameter is provided)
Complete Workflow
The full workflow for training and using a custom dictionary:
const {
train,
exportModel,
buildDictionary,
Metadata,
TokenizerBuilder,
} = require("lindera-nodejs");
// Step 1: Train the CRF model
train({
seed: "resources/training/seed.csv",
corpus: "resources/training/corpus.txt",
charDef: "resources/training/char.def",
unkDef: "resources/training/unk.def",
featureDef: "resources/training/feature.def",
rewriteDef: "resources/training/rewrite.def",
output: "/tmp/model.dat",
lambda: 0.01,
maxIter: 100,
});
// Step 2: Export to dictionary source files
exportModel({
model: "/tmp/model.dat",
output: "/tmp/dictionary_source",
metadata: "resources/training/metadata.json",
});
// Step 3: Build the dictionary from exported source files
const metadata = Metadata.fromJsonFile("/tmp/dictionary_source/metadata.json");
buildDictionary("/tmp/dictionary_source", "/tmp/dictionary", metadata);
// Step 4: Use the trained dictionary
const tokenizer = new TokenizerBuilder()
.setDictionary("/tmp/dictionary")
.setMode("normal")
.build();
const tokens = tokenizer.tokenize("形態素解析のテスト");
for (const token of tokens) {
console.log(`${token.surface}\t${token.details.join(",")}`);
}
Lindera Ruby
Lindera Ruby provides Ruby bindings for the Lindera morphological analysis engine, built with Magnus and rb-sys. It brings Lindera's high-performance tokenization capabilities to the Ruby ecosystem with support for Ruby 3.1 and later.
Features
- Multi-language support: Tokenize Japanese (IPADIC, IPADIC NEologd, UniDic), Korean (ko-dic), and Chinese (CC-CEDICT, Jieba) text
- Text processing pipeline: Compose character filters and token filters for flexible preprocessing and postprocessing
- CRF-based dictionary training: Train custom morphological analysis models from annotated corpora (requires
trainfeature) - Multiple tokenization modes: Normal and decompose modes for different analysis granularity
- N-best tokenization: Retrieve multiple tokenization candidates ranked by cost
- User dictionaries: Extend system dictionaries with custom vocabulary
Documentation
- Installation -- Prerequisites, build instructions, and feature flags
- Quick Start -- A minimal example to get started
- Tokenizer API --
TokenizerBuilder,Tokenizer, andTokenclass reference - Dictionary Management -- Loading, building, and managing dictionaries
- Text Processing Pipeline -- Character filters and token filters
- Training -- Training custom CRF models and exporting dictionaries
Installation
[!NOTE] lindera-ruby is not yet published to RubyGems. You need to build from source.
Prerequisites
- Ruby 3.1 or later
- Rust toolchain -- Install via rustup
- Bundler -- Ruby dependency manager (
gem install bundler)
Obtaining Dictionaries
Lindera does not bundle dictionaries with the package. You need to obtain a pre-built dictionary separately.
Download from GitHub Releases
Pre-built dictionaries are available on the GitHub Releases page. Download and extract the dictionary archive to a local directory:
# Example: download and extract the IPADIC dictionary
curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
Development Build
Build and install lindera-ruby in development mode:
cd lindera-ruby
bundle install
bundle exec rake compile
Or use the project Makefile:
make build-lindera-ruby
Run make test-lindera-ruby to run both the Rust unit tests and the Ruby
minitest suite.
Build with Training Support
The train feature enables CRF-based dictionary training functionality. It is enabled by default:
LINDERA_FEATURES="train" bundle exec rake compile
Feature Flags
Features are specified through the LINDERA_FEATURES environment variable as a comma-separated list.
| Feature | Description | Default |
|---|---|---|
train | CRF training functionality | Enabled |
embed-ipadic | Embed Japanese dictionary (IPADIC) into the binary | Disabled |
embed-unidic | Embed Japanese dictionary (UniDic) into the binary | Disabled |
embed-ipadic-neologd | Embed Japanese dictionary (IPADIC NEologd) into the binary | Disabled |
embed-ko-dic | Embed Korean dictionary (ko-dic) into the binary | Disabled |
embed-cc-cedict | Embed Chinese dictionary (CC-CEDICT) into the binary | Disabled |
embed-jieba | Embed Chinese dictionary (Jieba) into the binary | Disabled |
embed-cjk | Embed all CJK dictionaries (IPADIC, ko-dic, Jieba) into the binary | Disabled |
Multiple features can be combined:
LINDERA_FEATURES="train,embed-ipadic,embed-ko-dic" bundle exec rake compile
[!TIP] If you want to embed a dictionary directly into the binary (advanced usage), enable the corresponding
embed-*feature flag and load it using theembedded://scheme:dictionary = Lindera.load_dictionary("embedded://ipadic")See Feature Flags for details.
Verifying the Installation
After installation, verify that lindera is available in Ruby:
require 'lindera'
puts Lindera.version
Quick Start
This guide shows how to tokenize text using lindera-ruby.
Basic Tokenization
The recommended way to create a tokenizer is through Lindera::TokenizerBuilder:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_mode('normal')
builder.set_dictionary('/path/to/ipadic')
tokenizer = builder.build
tokens = tokenizer.tokenize('関西国際空港限定トートバッグ')
tokens.each do |token|
puts "#{token.surface}\t#{token.details.join(',')}"
end
Note: Download a pre-built dictionary from GitHub Releases and specify the path to the extracted directory.
Expected output:
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
Sequential Configuration
TokenizerBuilder is configured through sequential method calls:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_mode('normal')
builder.set_dictionary('/path/to/ipadic')
tokenizer = builder.build
tokens = tokenizer.tokenize('すもももももももものうち')
tokens.each do |token|
puts "#{token.surface}\t#{token.get_detail(0)}"
end
Accessing Token Properties
Each token exposes the following properties:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('/path/to/ipadic')
tokenizer = builder.build
tokens = tokenizer.tokenize('東京タワー')
tokens.each do |token|
puts "Surface: #{token.surface}"
puts "Byte range: #{token.byte_start}..#{token.byte_end}"
puts "Position: #{token.position}"
puts "Word ID: #{token.word_id}"
puts "Unknown: #{token.unknown?}"
puts "Details: #{token.details}"
puts
end
N-best Tokenization
Retrieve multiple tokenization candidates ranked by cost:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('/path/to/ipadic')
tokenizer = builder.build
results = tokenizer.tokenize_nbest('すもももももももものうち', 3, false, nil)
results.each do |tokens, cost|
surfaces = tokens.map(&:surface)
puts "Cost #{cost}: #{surfaces.join(' / ')}"
end
Tokenizer API
TokenizerBuilder
Lindera::TokenizerBuilder configures and constructs a Tokenizer instance using the builder pattern.
Constructors
Lindera::TokenizerBuilder.new
Creates a new builder with default configuration.
require 'lindera'
builder = Lindera::TokenizerBuilder.new
Lindera::TokenizerBuilder.from_file(file_path)
Loads configuration from a JSON file and returns a new builder. This is a class method, not chained off an existing instance.
builder = Lindera::TokenizerBuilder.from_file('config.json')
Configuration Methods
set_mode(mode)
Sets the tokenization mode.
"normal"-- Standard tokenization (default)"decompose"-- Decomposes compound words into smaller units
builder.set_mode('normal')
set_dictionary(path)
Sets the system dictionary path or URI.
# Use an embedded dictionary
builder.set_dictionary('embedded://ipadic')
# Use an external dictionary
builder.set_dictionary('/path/to/dictionary')
set_user_dictionary(uri)
Sets the user dictionary URI.
builder.set_user_dictionary('/path/to/user_dictionary')
set_keep_whitespace(keep)
Controls whether whitespace tokens appear in the output.
builder.set_keep_whitespace(true)
append_character_filter(kind, args)
Appends a character filter to the preprocessing pipeline. The args parameter is a hash with string keys.
builder.append_character_filter('unicode_normalize', { 'kind' => 'nfkc' })
append_token_filter(kind, args)
Appends a token filter to the postprocessing pipeline. The args parameter is a hash with string keys, or nil if the filter requires no arguments.
builder.append_token_filter('lowercase', nil)
Build
build
Builds and returns a Tokenizer with the configured settings.
tokenizer = builder.build
Tokenizer
Lindera::Tokenizer performs morphological analysis on text.
Creating a Tokenizer
Lindera::Tokenizer.new(dictionary, mode, user_dictionary)
Creates a tokenizer directly from a loaded dictionary.
require 'lindera'
dictionary = Lindera.load_dictionary('embedded://ipadic')
tokenizer = Lindera::Tokenizer.new(dictionary, 'normal', nil)
With a user dictionary:
dictionary = Lindera.load_dictionary('embedded://ipadic')
metadata = dictionary.metadata
user_dict = Lindera.load_user_dictionary('/path/to/user_dictionary', metadata)
tokenizer = Lindera::Tokenizer.new(dictionary, 'normal', user_dict)
Tokenizer Methods
tokenize(text)
Tokenizes the input text and returns an array of Token objects.
tokens = tokenizer.tokenize('形態素解析')
Parameters:
| Name | Type | Description |
|---|---|---|
text | String | Text to tokenize |
Returns: Array<Token>
tokenize_nbest(text, n, unique, cost_threshold)
Returns the N-best tokenization results, each paired with its total path cost.
results = tokenizer.tokenize_nbest('すもももももももものうち', 3, false, nil)
results.each do |tokens, cost|
puts "#{cost}: #{tokens.map(&:surface).inspect}"
end
Parameters:
| Name | Type | Description |
|---|---|---|
text | String | Text to tokenize |
n | Integer | Number of results to return |
unique | Boolean or nil | Deduplicate results (default: false) |
cost_threshold | Integer or nil | Maximum cost difference from the best path (default: nil) |
Returns: Array<Array(Array<Token>, Integer)>
Mode
Lindera::Mode represents a tokenization mode. It is provided as a standalone helper for inspecting or comparing modes; TokenizerBuilder#set_mode and Tokenizer.new currently accept only a plain mode string ("normal" or "decompose"), not a Mode instance (see the limitation noted under Penalty below).
Creating a Mode
Lindera::Mode.new(mode_str)
Creates a Mode. The argument is required, but may be nil. Accepts "normal" / "Normal" (used when mode_str is nil) or "decompose" / "Decompose"; any other value raises ArgumentError.
require 'lindera'
mode = Lindera::Mode.new('normal')
mode = Lindera::Mode.new('decompose')
mode = Lindera::Mode.new(nil) # defaults to "normal"
Mode Methods
| Method | Returns | Description |
|---|---|---|
to_s | String | "normal" or "decompose" |
name | String | Same as to_s |
inspect | String | e.g. "#<Lindera::Mode: decompose>" |
normal? | Boolean | true if the mode is "normal" |
decompose? | Boolean | true if the mode is "decompose" |
mode = Lindera::Mode.new('decompose')
mode.to_s # "decompose"
mode.normal? # false
mode.decompose? # true
Penalty
Lindera::Penalty configures the length-based penalty thresholds used by "decompose" mode segmentation.
Creating a Penalty
Lindera::Penalty.new(kanji_penalty_length_threshold, kanji_penalty_length_penalty, other_penalty_length_threshold, other_penalty_length_penalty)
All four positional arguments are required, but each may be nil to fall back to its default (shown below).
require 'lindera'
penalty = Lindera::Penalty.new(2, 3000, 7, 1700)
penalty = Lindera::Penalty.new(nil, nil, nil, nil) # uses all defaults
Penalty Properties
All properties are read-only (there are no setter methods):
| Property | Type | Default | Description |
|---|---|---|---|
kanji_penalty_length_threshold | Integer | 2 | Kanji-only surface length above which the penalty applies |
kanji_penalty_length_penalty | Integer | 3000 | Cost penalty added for kanji-only surfaces longer than the threshold |
other_penalty_length_threshold | Integer | 7 | Surface length above which the penalty applies for non-kanji-only surfaces |
other_penalty_length_penalty | Integer | 1700 | Cost penalty added for non-kanji-only surfaces longer than the threshold |
penalty = Lindera::Penalty.new(nil, nil, nil, nil)
penalty.kanji_penalty_length_threshold # 2
Current limitation: there is currently no way to pass a Penalty into a Tokenizer or TokenizerBuilder. set_mode and Tokenizer.new only accept a plain mode string, and internally "decompose" mode always uses Penalty's default values -- constructing a custom Penalty instance has no effect on tokenization yet.
Token
Token represents a single morphological token.
Properties
| Property | Type | Description |
|---|---|---|
surface | String | Surface form of the token |
byte_start | Integer | Start byte position in the original text |
byte_end | Integer | End byte position in the original text |
position | Integer | Token position index |
word_id | Integer | Dictionary word ID |
details | Array<String> | Morphological details (part of speech, reading, etc.) |
Additionally, the predicate method unknown? returns true if the word is not in the dictionary:
token.unknown? # => false
Token Methods
get_detail(index)
Returns the detail string at the specified index, or nil if the index is out of range.
token = tokenizer.tokenize('東京')[0]
pos = token.get_detail(0) # e.g., "名詞"
subpos = token.get_detail(1) # e.g., "固有名詞"
reading = token.get_detail(7) # e.g., "トウキョウ"
Parameters:
| Name | Type | Description |
|---|---|---|
index | Integer | Zero-based index into the details array |
Returns: String or nil
The structure of details depends on the dictionary:
- IPADIC:
[品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3, 活用型, 活用形, 原形, 読み, 発音] - UniDic: Detailed morphological features following the UniDic specification
- ko-dic / CC-CEDICT / Jieba: Dictionary-specific detail formats
Schema
Lindera::Schema holds an ordered list of field names and provides lookups between field name and index. It is used by Metadata#dictionary_schema and Metadata#user_dictionary_schema (see Dictionary Management).
Creating a Schema
Lindera::Schema.new(fields)
Creates a schema from an array of field names.
require 'lindera'
schema = Lindera::Schema.new(%w[
surface
left_context_id
right_context_id
cost
major_pos
reading
])
Lindera::Schema.create_default
Returns the built-in default schema: 13 fields matching the IPADIC-style layout (surface, left_context_id, right_context_id, cost, major_pos, pos_detail_1, pos_detail_2, pos_detail_3, conjugation_type, conjugation_form, base_form, reading, pronunciation).
schema = Lindera::Schema.create_default
Schema Methods
| Method | Returns | Description |
|---|---|---|
fields | Array<String> | All field names, in order |
get_all_fields | Array<String> | Same as fields |
field_count | Integer | Total number of fields |
get_field_index(name) | Integer or nil | Index of the field named name |
get_field_name(index) | String or nil | Field name at index |
get_custom_fields | Array<String> | Field names after the four fixed fields (surface, left_context_id, right_context_id, cost) |
get_field_by_name(name) | FieldDefinition or nil | Full field definition for name |
validate_record(record) | nil | Raises ArgumentError if record does not match the schema |
to_s | String | e.g. "Schema(fields=13)" |
inspect | String | Full field list |
schema = Lindera::Schema.create_default
schema.field_count # 13
schema.get_field_index('cost') # 3
schema.get_field_name(0) # "surface"
schema.get_custom_fields # ["major_pos", "pos_detail_1", ..., "pronunciation"]
field = schema.get_field_by_name('surface')
puts "#{field.index} #{field.name} #{field.field_type}" # 0 surface surface
schema.validate_record([
'東京', '1288', '1288', '100',
'名詞', '固有名詞', '地域', '一般', '*', '*',
'東京', 'トウキョウ', 'トーキョー'
])
FieldDefinition
Lindera::FieldDefinition describes a single field within a Schema. Instances are only obtained from Schema#get_field_by_name -- there is no public constructor (Lindera::FieldDefinition.new raises TypeError).
FieldDefinition Properties
| Property | Type | Description |
|---|---|---|
index | Integer | Zero-based position of the field within the schema |
name | String | Field name |
field_type | FieldType | Field type |
description | String or nil | Optional human-readable description |
schema = Lindera::Schema.create_default
field = schema.get_field_by_name('surface')
field.index # 0
field.name # "surface"
field.field_type # #<Lindera::FieldType: surface>
field.description # nil (the default schema does not set descriptions)
FieldType
Lindera::FieldType enumerates the category of a single field. Like FieldDefinition, instances are only obtained from a Schema (via FieldDefinition#field_type) -- there is no public constructor.
to_s (and inspect) return one of:
"surface"-- surface form (word text)"left_context_id"-- left context ID"right_context_id"-- right context ID"cost"-- word cost"custom"-- any other, dictionary-specific field
field = Lindera::Schema.create_default.get_field_by_name('surface')
field.field_type.to_s # "surface"
Dictionary Management
Lindera Ruby provides functions for loading, building, and managing dictionaries used in morphological analysis.
Loading Dictionaries
System Dictionaries
Use Lindera.load_dictionary(uri) to load a system dictionary. Download a pre-built dictionary from GitHub Releases and specify the path to the extracted directory:
require 'lindera'
dictionary = Lindera.load_dictionary('/path/to/ipadic')
Embedded dictionaries (advanced) -- if you built with an embed-* feature flag, you can load an embedded dictionary:
dictionary = Lindera.load_dictionary('embedded://ipadic')
User Dictionaries
User dictionaries add custom vocabulary on top of a system dictionary.
require 'lindera'
dictionary = Lindera.load_dictionary('/path/to/ipadic')
metadata = dictionary.metadata
user_dict = Lindera.load_user_dictionary('/path/to/user_dictionary', metadata)
Pass the user dictionary when building a tokenizer:
require 'lindera'
dictionary = Lindera.load_dictionary('/path/to/ipadic')
metadata = dictionary.metadata
user_dict = Lindera.load_user_dictionary('/path/to/user_dictionary', metadata)
tokenizer = Lindera::Tokenizer.new(dictionary, 'normal', user_dict)
Or via the builder:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('/path/to/ipadic')
builder.set_user_dictionary('/path/to/user_dictionary')
tokenizer = builder.build
Building Dictionaries
System Dictionary
Build a system dictionary from source files:
require 'lindera'
metadata = Lindera::Metadata.from_json_file('metadata.json')
Lindera.build_dictionary('/path/to/input_dir', '/path/to/output_dir', metadata)
The input directory should contain the dictionary source files (CSV lexicon, matrix.def, etc.).
User Dictionary
Build a user dictionary from a CSV file:
require 'lindera'
metadata = Lindera::Metadata.from_json_file('metadata.json')
Lindera.build_user_dictionary('ipadic', 'user_words.csv', '/path/to/output_dir', metadata)
The metadata parameter is optional. When omitted, default metadata values are used:
Lindera.build_user_dictionary('ipadic', 'user_words.csv', '/path/to/output_dir', nil)
[!NOTE] The first argument (
kind,'ipadic'above) is currently unused -- it is reserved for future use and has no effect on the build. Any string value can be passed for it today.
Metadata
The Lindera::Metadata class configures dictionary parameters.
Creating Metadata
require 'lindera'
# Create default metadata with standard settings
metadata = Lindera::Metadata.create_default
Lindera::Metadata.new takes all nine properties as required positional
arguments (each may be nil to fall back to its default) -- use it only when
you need to override specific values:
metadata = Lindera::Metadata.new(
'my_dict', # name
'UTF-8', # encoding
-10_000, # default_word_cost
1288, # default_left_context_id
1288, # default_right_context_id
'*', # default_field_value
false, # flexible_csv
false, # skip_invalid_cost_or_id
false # normalize_details
)
Loading from JSON
metadata = Lindera::Metadata.from_json_file('metadata.json')
Getting Metadata from a Dictionary
A loaded dictionary's metadata can be retrieved directly:
dictionary = Lindera.load_dictionary('/path/to/ipadic')
metadata = dictionary.metadata
Properties
| Property | Type | Default | Description |
|---|---|---|---|
name | String | "default" | Dictionary name |
encoding | String | "UTF-8" | Character encoding |
default_word_cost | Integer | -10000 | Default cost for unknown words |
default_left_context_id | Integer | 1288 | Default left context ID |
default_right_context_id | Integer | 1288 | Default right context ID |
default_field_value | String | "*" | Default value for missing fields |
flexible_csv | Boolean | false | Allow flexible CSV parsing |
skip_invalid_cost_or_id | Boolean | false | Skip entries with invalid cost or ID |
normalize_details | Boolean | false | Normalize morphological details |
Text Processing Pipeline
Lindera Ruby supports a composable text processing pipeline that applies character filters before tokenization and token filters after tokenization. Filters are added to the TokenizerBuilder and executed in the order they are appended.
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
[!NOTE] This page shows a few commonly used filters as examples -- it is not the complete list.
lindera-analysisships 4 character filters and 18 token filters in total. See Filters for the full, authoritative catalogue of every character and token filter, including parameters and examples.
Character Filters
Character filters transform the input text before tokenization.
unicode_normalize
Applies Unicode normalization to the input text.
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_character_filter('unicode_normalize', { 'kind' => 'nfkc' })
tokenizer = builder.build
Supported normalization forms: "nfc", "nfkc", "nfd", "nfkd".
mapping
Replaces characters or strings according to a mapping table.
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_character_filter('mapping', {
'mapping' => {
"\u30fc" => '-',
"\uff5e" => '~'
}
})
tokenizer = builder.build
japanese_iteration_mark
Resolves Japanese iteration marks (odoriji) into their full forms.
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_character_filter('japanese_iteration_mark', {
'normalize_kanji' => true,
'normalize_kana' => true
})
tokenizer = builder.build
Token Filters
Token filters transform or remove tokens after tokenization.
lowercase
Converts token surface forms to lowercase.
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('lowercase', nil)
tokenizer = builder.build
japanese_base_form
Replaces inflected forms with their base (dictionary) form using the morphological details from the dictionary.
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('japanese_base_form', nil)
tokenizer = builder.build
japanese_stop_tags
Removes tokens whose part-of-speech matches any of the specified tags.
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('japanese_stop_tags', {
'tags' => ['助詞', '助動詞']
})
tokenizer = builder.build
japanese_keep_tags
Keeps only tokens whose part-of-speech matches one of the specified tags. All other tokens are removed.
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('japanese_keep_tags', {
'tags' => ['名詞']
})
tokenizer = builder.build
japanese_katakana_stem
Removes trailing prolonged sound marks from katakana tokens that exceed a minimum length.
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('embedded://ipadic')
builder.append_token_filter('japanese_katakana_stem', { 'min' => 3 })
tokenizer = builder.build
Complete Pipeline Example
The following example combines multiple character filters and token filters into a single pipeline:
require 'lindera'
builder = Lindera::TokenizerBuilder.new
builder.set_mode('normal')
builder.set_dictionary('embedded://ipadic')
# Preprocessing
builder.append_character_filter('unicode_normalize', { 'kind' => 'nfkc' })
builder.append_character_filter('japanese_iteration_mark', {
'normalize_kanji' => true,
'normalize_kana' => true
})
# Postprocessing
builder.append_token_filter('japanese_base_form', nil)
builder.append_token_filter('japanese_stop_tags', {
'tags' => ['助詞', '助動詞', '記号']
})
builder.append_token_filter('lowercase', nil)
tokenizer = builder.build
tokens = tokenizer.tokenize('Linderaは形態素解析を行うライブラリです。')
tokens.each do |token|
puts "#{token.surface}\t#{token.details.join(',')}"
end
In this pipeline:
unicode_normalizeconverts full-width characters to half-width (NFKC normalization)japanese_iteration_markresolves iteration marksjapanese_base_formconverts inflected tokens to base formjapanese_stop_tagsremoves particles, auxiliary verbs, and symbolslowercasenormalizes alphabetic characters to lowercase
Training
Lindera Ruby supports training custom CRF-based morphological analysis models from annotated corpora. This functionality requires the train feature.
Prerequisites
Build lindera-ruby with the train feature enabled:
LINDERA_FEATURES="embed-ipadic,train" bundle exec rake compile
Training a Model
Use Lindera.train to train a CRF model from a seed lexicon and annotated corpus:
require 'lindera'
Lindera.train(
'resources/training/seed.csv',
'resources/training/corpus.txt',
'resources/training/char.def',
'resources/training/unk.def',
'resources/training/feature.def',
'resources/training/rewrite.def',
'/tmp/model.dat',
0.01, # lambda (L1 regularization)
100, # max_iter
nil # max_threads (nil = auto-detect CPU cores)
)
Training Parameters
Parameters are passed as positional arguments in the following order:
| Position | Name | Type | Description |
|---|---|---|---|
| 1 | seed | String | Path to the seed lexicon file (CSV format) |
| 2 | corpus | String | Path to the annotated training corpus |
| 3 | char_def | String | Path to the character definition file (char.def) |
| 4 | unk_def | String | Path to the unknown word definition file (unk.def) |
| 5 | feature_def | String | Path to the feature definition file (feature.def) |
| 6 | rewrite_def | String | Path to the rewrite rule definition file (rewrite.def) |
| 7 | output | String | Output path for the trained model file |
| 8 | lambda | Float | L1 regularization cost (0.0--1.0) |
| 9 | max_iter | Integer | Maximum number of training iterations |
| 10 | max_threads | Integer or nil | Number of threads (nil = auto-detect CPU cores) |
Exporting a Trained Model
After training, export the model to dictionary source files using Lindera.export:
require 'lindera'
Lindera.export(
'/tmp/model.dat',
'/tmp/dictionary_source',
'resources/training/metadata.json'
)
Export Parameters
| Position | Name | Type | Description |
|---|---|---|---|
| 1 | model | String | Path to the trained model file (.dat) |
| 2 | output | String | Output directory for dictionary source files |
| 3 | metadata | String or nil | Path to a base metadata.json file |
The export creates the following files in the output directory:
lex.csv-- Lexicon entries with trained costsmatrix.def-- Connection cost matrixunk.def-- Unknown word definitionschar.def-- Character category definitionsmetadata.json-- Updated metadata (whenmetadataparameter is provided)
Complete Workflow
The full workflow for training and using a custom dictionary:
require 'lindera'
# Step 1: Train the CRF model
Lindera.train(
'resources/training/seed.csv',
'resources/training/corpus.txt',
'resources/training/char.def',
'resources/training/unk.def',
'resources/training/feature.def',
'resources/training/rewrite.def',
'/tmp/model.dat',
0.01, # lambda
100, # max_iter
nil # max_threads
)
# Step 2: Export to dictionary source files
Lindera.export(
'/tmp/model.dat',
'/tmp/dictionary_source',
'resources/training/metadata.json'
)
# Step 3: Build the dictionary from exported source files
metadata = Lindera::Metadata.from_json_file('/tmp/dictionary_source/metadata.json')
Lindera.build_dictionary('/tmp/dictionary_source', '/tmp/dictionary', metadata)
# Step 4: Use the trained dictionary
builder = Lindera::TokenizerBuilder.new
builder.set_dictionary('/tmp/dictionary')
builder.set_mode('normal')
tokenizer = builder.build
tokens = tokenizer.tokenize('形態素解析のテスト')
tokens.each do |token|
puts "#{token.surface}\t#{token.details.join(',')}"
end
Lindera PHP
Lindera PHP provides PHP bindings for the Lindera morphological analysis engine, built with ext-php-rs. It brings Lindera's high-performance tokenization capabilities to the PHP ecosystem with support for PHP 8.1 and later.
Features
- Multi-language support: Tokenize Japanese (IPADIC, IPADIC NEologd, UniDic), Korean (ko-dic), and Chinese (CC-CEDICT, Jieba) text
- Text processing pipeline: Compose character filters and token filters for flexible preprocessing and postprocessing
- CRF-based dictionary training: Train custom morphological analysis models from annotated corpora (requires
trainfeature) - Multiple tokenization modes: Normal and decompose modes for different analysis granularity
- N-best tokenization: Retrieve multiple tokenization candidates ranked by cost
- User dictionaries: Extend system dictionaries with custom vocabulary
Documentation
- Installation -- Prerequisites, build instructions, and feature flags
- Quick Start -- A minimal example to get started
- Tokenizer API --
TokenizerBuilder,Tokenizer, andTokenclass reference - Dictionary Management -- Loading, building, and managing dictionaries
- Text Processing Pipeline -- Character filters and token filters
- Training -- Training custom CRF models and exporting dictionaries
Installation
[!NOTE] lindera-php is not yet published to Packagist. You need to build from source.
Prerequisites
- PHP 8.1 or later
- Rust toolchain -- Install via rustup
- Composer -- PHP dependency manager (optional, for running tests)
Obtaining Dictionaries
Lindera does not bundle dictionaries with the package. You need to obtain a pre-built dictionary separately.
Download from GitHub Releases
Pre-built dictionaries are available on the GitHub Releases page. Download and extract the dictionary archive to a local directory:
# Example: download and extract the IPADIC dictionary
curl -LO https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip
unzip lindera-ipadic-<version>.zip -d /path/to/ipadic
Development Build
Build the lindera-php extension from the project root:
cargo build -p lindera-php
Or use the project Makefile:
make build-lindera-php
Build with Training Support
The train feature enables CRF-based dictionary training functionality:
cargo build -p lindera-php --features train
Feature Flags
| Feature | Description | Default |
|---|---|---|
train | CRF training functionality | Enabled (default) |
embed-ipadic | Embed Japanese dictionary (IPADIC) into the binary | Disabled |
embed-unidic | Embed Japanese dictionary (UniDic) into the binary | Disabled |
embed-ipadic-neologd | Embed Japanese dictionary (IPADIC NEologd) into the binary | Disabled |
embed-ko-dic | Embed Korean dictionary (ko-dic) into the binary | Disabled |
embed-cc-cedict | Embed Chinese dictionary (CC-CEDICT) into the binary | Disabled |
embed-jieba | Embed Chinese dictionary (Jieba) into the binary | Disabled |
embed-cjk | Embed all CJK dictionaries (IPADIC, ko-dic, Jieba) into the binary | Disabled |
Multiple features can be combined:
cargo build -p lindera-php --features "train,embed-ipadic,embed-ko-dic"
[!TIP] If you want to embed a dictionary directly into the binary (advanced usage), enable the corresponding
embed-*feature flag and load it using theembedded://scheme:$dictionary = Lindera\Dictionary::load('embedded://ipadic');See Feature Flags for details.
Loading the Extension
Load the compiled shared library when running PHP:
php -d extension=target/debug/liblindera_php.so script.php
For release builds:
cargo build -p lindera-php --release
php -d extension=target/release/liblindera_php.so script.php
Alternatively, add the extension to your php.ini:
extension=/absolute/path/to/liblindera_php.so
Verifying the Installation
After building, verify that lindera is available in PHP:
php -d extension=target/debug/liblindera_php.so -r "echo Lindera\Dictionary::version() . PHP_EOL;"
Quick Start
This guide shows how to tokenize text using lindera-php.
Basic Tokenization
Load a dictionary, create a tokenizer, and tokenize text:
<?php
// Load the dictionary
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
// Create a tokenizer
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal');
// Tokenize the text
$tokens = $tokenizer->tokenize('関西国際空港限定トートバッグ');
foreach ($tokens as $token) {
echo $token->surface . "\t" . implode(',', $token->details) . "\n";
}
Note: Download a pre-built dictionary from GitHub Releases and specify the path to the extracted directory.
Expected output:
関西国際空港 名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
Using TokenizerBuilder
TokenizerBuilder gives you more flexible configuration options:
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setMode('normal');
$builder->setDictionary('/path/to/ipadic');
$tokenizer = $builder->build();
$tokens = $tokenizer->tokenize('すもももももももものうち');
foreach ($tokens as $token) {
echo $token->surface . "\t" . $token->getDetail(0) . "\n";
}
Accessing Token Properties
Each token exposes the following properties:
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('/path/to/ipadic');
$tokenizer = $builder->build();
$tokens = $tokenizer->tokenize('東京タワー');
foreach ($tokens as $token) {
echo "Surface: {$token->surface}\n";
echo "Byte range: {$token->byte_start}..{$token->byte_end}\n";
echo "Position: {$token->position}\n";
echo "Word ID: {$token->word_id}\n";
echo "Unknown: " . ($token->is_unknown ? 'true' : 'false') . "\n";
echo "Details: " . implode(',', $token->details) . "\n";
echo "\n";
}
N-best Tokenization
Retrieve multiple tokenization candidates ranked by cost:
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('/path/to/ipadic');
$tokenizer = $builder->build();
$results = $tokenizer->tokenizeNbest('すもももももももものうち', 3);
foreach ($results as $result) {
$surfaces = array_map(fn($t) => $t->surface, $result->tokens);
echo "Cost {$result->cost}: " . implode(' / ', $surfaces) . "\n";
}
Tokenizer API
TokenizerBuilder
Lindera\TokenizerBuilder configures and constructs a Tokenizer instance using the builder pattern.
Constructors
new Lindera\TokenizerBuilder()
Creates a new builder with default configuration.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->fromFile($filePath)
Loads configuration from a JSON file.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->fromFile('config.json');
Configuration Methods
setMode($mode)
Sets the tokenization mode.
"normal"-- Standard tokenization (default)"decompose"-- Decomposes compound words into smaller units
<?php
$builder->setMode('normal');
setDictionary($path)
Sets the system dictionary path or URI.
<?php
// Use an embedded dictionary
$builder->setDictionary('embedded://ipadic');
// Use an external dictionary
$builder->setDictionary('/path/to/dictionary');
setUserDictionary($uri)
Sets the user dictionary URI.
<?php
$builder->setUserDictionary('/path/to/user_dictionary.csv');
setKeepWhitespace($keep)
Controls whether whitespace tokens appear in the output.
<?php
$builder->setKeepWhitespace(true);
appendCharacterFilter($kind, $args)
Appends a character filter to the preprocessing pipeline.
<?php
$builder->appendCharacterFilter('unicode_normalize', ['kind' => 'nfkc']);
appendTokenFilter($kind, $args)
Appends a token filter to the postprocessing pipeline.
<?php
$builder->appendTokenFilter('lowercase');
Build
build()
Builds and returns a Tokenizer with the configured settings.
<?php
$tokenizer = $builder->build();
Tokenizer
Lindera\Tokenizer performs morphological analysis on text.
Creating a Tokenizer
new Lindera\Tokenizer($dictionary, $mode, $userDictionary)
Creates a tokenizer directly from a loaded dictionary.
<?php
$dictionary = Lindera\Dictionary::load('embedded://ipadic');
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal');
With a user dictionary:
<?php
$dictionary = Lindera\Dictionary::load('embedded://ipadic');
$metadata = $dictionary->metadata();
$userDict = Lindera\Dictionary::loadUser('/path/to/user_dictionary.csv', $metadata);
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal', $userDict);
Tokenizer Methods
tokenize($text)
Tokenizes the input text and returns an array of Token objects.
<?php
$tokens = $tokenizer->tokenize('形態素解析');
Parameters:
| Name | Type | Description |
|---|---|---|
$text | string | Text to tokenize |
Returns: array<Token>
tokenizeNbest($text, $n, $unique, $costThreshold)
Returns the N-best tokenization results as an array of NbestResult objects.
<?php
$results = $tokenizer->tokenizeNbest('すもももももももものうち', 3);
foreach ($results as $result) {
echo "Cost: {$result->cost}\n";
foreach ($result->tokens as $token) {
echo " {$token->surface}\n";
}
}
Parameters:
| Name | Type | Description |
|---|---|---|
$text | string | Text to tokenize |
$n | int | Number of results to return |
$unique | bool|null | Deduplicate results (default: false) |
$costThreshold | int|null | Maximum cost difference from the best path (default: null) |
Returns: array<NbestResult>
NbestResult
Lindera\NbestResult represents a single N-best tokenization result.
NbestResult Properties
| Property | Type | Description |
|---|---|---|
$tokens | array<Token> | The tokens in this result |
$cost | int | The total cost of this segmentation |
Token
Lindera\Token represents a single morphological token.
Token Properties
| Property | Type | Description |
|---|---|---|
$surface | string | Surface form of the token |
$byte_start | int | Start byte position in the original text |
$byte_end | int | End byte position in the original text |
$position | int | Token position index |
$word_id | int | Dictionary word ID |
$is_unknown | bool | true if the word is not in the dictionary |
$details | array<string> | Morphological details (part of speech, reading, etc.) |
Token Methods
getDetail($index)
Returns the detail string at the specified index, or null if the index is out of range.
<?php
$token = $tokenizer->tokenize('東京')[0];
$pos = $token->getDetail(0); // e.g., "名詞"
$subpos = $token->getDetail(1); // e.g., "固有名詞"
$reading = $token->getDetail(7); // e.g., "トウキョウ"
Parameters:
| Name | Type | Description |
|---|---|---|
$index | int | Zero-based index into the details array |
Returns: string|null
The structure of details depends on the dictionary:
- IPADIC:
[品詞, 品詞細分類1, 品詞細分類2, 品詞細分類3, 活用型, 活用形, 原形, 読み, 発音] - UniDic: Detailed morphological features following the UniDic specification
- ko-dic / CC-CEDICT / Jieba: Dictionary-specific detail formats
Mode
Lindera\Mode represents the tokenization mode.
Creating a Mode
<?php
$mode = new Lindera\Mode('normal');
$mode = new Lindera\Mode('decompose');
$mode = new Lindera\Mode(); // default: 'normal'
Mode Properties
| Property | Type | Description |
|---|---|---|
$name | string | The mode name ("normal" or "decompose") |
Mode Methods
| Method | Return Type | Description |
|---|---|---|
isNormal() | bool | true if the mode is normal |
isDecompose() | bool | true if the mode is decompose |
Penalty
Lindera\Penalty configures how aggressively decompose mode splits compound words, based on character type and length thresholds.
Note:
Penaltyis not currently wired intoTokenizerBuilderor theTokenizerconstructor -- there is no setter that accepts it, so constructing one has no effect on tokenization yet.decomposemode always uses the default penalty values shown below.
<?php
// All parameters are optional and default to the values used by decompose mode
$penalty = new Lindera\Penalty(
kanji_penalty_length_threshold: 2,
kanji_penalty_length_penalty: 3000,
other_penalty_length_threshold: 7,
other_penalty_length_penalty: 1700,
);
Penalty Properties
| Property | Type | Default | Description |
|---|---|---|---|
$kanji_penalty_length_threshold | int | 2 | Length threshold for kanji sequences |
$kanji_penalty_length_penalty | int | 3000 | Penalty applied to kanji sequences exceeding the threshold |
$other_penalty_length_threshold | int | 7 | Length threshold for other character sequences |
$other_penalty_length_penalty | int | 1700 | Penalty applied to other character sequences exceeding the threshold |
Dictionary Management
Lindera PHP provides static methods on the Lindera\Dictionary class for loading, building, and managing dictionaries used in morphological analysis.
Loading Dictionaries
System Dictionaries
Use Lindera\Dictionary::load($uri) to load a system dictionary. Download a pre-built dictionary from GitHub Releases and specify the path to the extracted directory:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
Embedded dictionaries (advanced) -- if you built with an embed-* feature flag, you can load an embedded dictionary:
<?php
$dictionary = Lindera\Dictionary::load('embedded://ipadic');
User Dictionaries
User dictionaries add custom vocabulary on top of a system dictionary.
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
$metadata = $dictionary->metadata();
$userDict = Lindera\Dictionary::loadUser('/path/to/user_dictionary.csv', $metadata);
Pass the user dictionary when creating a tokenizer directly:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
$metadata = $dictionary->metadata();
$userDict = Lindera\Dictionary::loadUser('/path/to/user_dictionary.csv', $metadata);
$tokenizer = new Lindera\Tokenizer($dictionary, 'normal', $userDict);
Or via the builder:
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('/path/to/ipadic');
$builder->setUserDictionary('/path/to/user_dictionary.csv');
$tokenizer = $builder->build();
Building Dictionaries
System Dictionary
Build a system dictionary from source files:
<?php
$metadata = Lindera\Metadata::fromJsonFile('/path/to/metadata.json');
Lindera\Dictionary::build('/path/to/input_dir', '/path/to/output_dir', $metadata);
The input directory should contain the dictionary source files (CSV lexicon, matrix.def, etc.).
Here is an example that downloads and builds the IPADIC dictionary:
<?php
$url = 'https://lindera.dev/mecab-ipadic-2.7.0-20070801.tar.gz';
$filename = '/tmp/mecab-ipadic-2.7.0-20070801.tar.gz';
// Download and extract dictionary source
file_put_contents($filename, file_get_contents($url));
$phar = new PharData($filename);
$phar->extractTo('/tmp/', null, true);
// Load metadata and build
$metadata = Lindera\Metadata::fromJsonFile('resources/ipadic_metadata.json');
Lindera\Dictionary::build(
'/tmp/mecab-ipadic-2.7.0-20070801',
'/tmp/lindera-ipadic',
$metadata
);
User Dictionary
Build a user dictionary from a CSV file:
<?php
$metadata = new Lindera\Metadata();
Lindera\Dictionary::buildUser('ipadic', 'user_words.csv', '/path/to/output_dir', $metadata);
Metadata
The Lindera\Metadata class configures dictionary parameters.
Creating Metadata
<?php
// Default metadata
$metadata = new Lindera\Metadata();
// Custom metadata
$metadata = new Lindera\Metadata(
name: 'my_dictionary',
encoding: 'UTF-8',
default_word_cost: -10000,
);
// Create with all defaults explicitly
$metadata = Lindera\Metadata::createDefault();
Loading from JSON
<?php
$metadata = Lindera\Metadata::fromJsonFile('metadata.json');
Properties
| Property | Type | Default | Description |
|---|---|---|---|
name | string | "default" | Dictionary name |
encoding | string | "UTF-8" | Character encoding |
default_word_cost | int | -10000 | Default cost for unknown words |
default_left_context_id | int | 1288 | Default left context ID |
default_right_context_id | int | 1288 | Default right context ID |
default_field_value | string | "*" | Default value for missing fields |
flexible_csv | bool | false | Allow flexible CSV parsing |
skip_invalid_cost_or_id | bool | false | Skip entries with invalid cost or ID |
normalize_details | bool | false | Normalize morphological details |
dictionary_schema_fields | array<string> | IPADIC schema | Schema fields for the main dictionary |
user_dictionary_schema_fields | array<string> | Minimal schema | Schema fields for user dictionaries |
All properties are read-only via getter methods:
<?php
$metadata = new Lindera\Metadata(name: 'custom_dict', encoding: 'EUC-JP');
echo $metadata->name; // "custom_dict"
echo $metadata->encoding; // "EUC-JP"
toArray()
Returns an associative array representation of the metadata:
<?php
$metadata = new Lindera\Metadata(name: 'test');
print_r($metadata->toArray());
Dictionary Info
The Lindera\Dictionary object provides metadata accessors:
<?php
$dictionary = Lindera\Dictionary::load('/path/to/ipadic');
echo $dictionary->metadataName(); // Dictionary name
echo $dictionary->metadataEncoding(); // Dictionary encoding
$metadata = $dictionary->metadata(); // Full Metadata object
Version
Retrieve the Lindera library version:
<?php
echo Lindera\Dictionary::version();
Schema
The Lindera\Schema class defines the field layout of a dictionary.
Creating a Schema
<?php
// Default schema (IPADIC-compatible)
$schema = Lindera\Schema::createDefault();
// Custom schema
$schema = new Lindera\Schema(['surface', 'pos']);
Schema Methods
| Method | Return Type | Description |
|---|---|---|
fieldCount() | int | Returns the number of fields |
getFieldIndex($name) | int | Returns the field's index (-1 if not found) |
getFieldByName($name) | FieldDefinition|null | Returns the field's definition |
getCustomFields() | array<string> | Returns the custom field names |
validateRecord($record) | void | Validates that a record conforms to the schema |
Schema Properties
| Property | Type | Description |
|---|---|---|
$fields | array<string> | The field names |
Text Processing Pipeline
Lindera PHP supports a composable text processing pipeline that applies character filters before tokenization and token filters after tokenization. Filters are added to the TokenizerBuilder and executed in the order they are appended.
Input Text
--> Character Filters (preprocessing)
--> Tokenization
--> Token Filters (postprocessing)
--> Output Tokens
[!NOTE] This page shows a few commonly used filters as examples -- it is not the complete list.
lindera-analysisships 4 character filters and 18 token filters in total. See Filters for the full, authoritative catalogue of every character and token filter, including parameters and examples.
Character Filters
Character filters transform the input text before tokenization.
unicode_normalize
Applies Unicode normalization to the input text.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendCharacterFilter('unicode_normalize', ['kind' => 'nfkc']);
$tokenizer = $builder->build();
Supported normalization forms: "nfc", "nfkc", "nfd", "nfkd".
mapping
Replaces characters or strings according to a mapping table.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendCharacterFilter('mapping', [
'mapping' => [
'リンデラ' => 'lindera',
],
]);
$tokenizer = $builder->build();
japanese_iteration_mark
Resolves Japanese iteration marks (odoriji) into their full forms.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendCharacterFilter('japanese_iteration_mark', [
'normalize_kanji' => 'true',
'normalize_kana' => 'true',
]);
$tokenizer = $builder->build();
Token Filters
Token filters transform or remove tokens after tokenization.
lowercase
Converts token surface forms to lowercase.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('lowercase');
$tokenizer = $builder->build();
japanese_base_form
Replaces inflected forms with their base (dictionary) form using the morphological details from the dictionary.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('japanese_base_form', []);
$tokenizer = $builder->build();
japanese_katakana_stem
Removes the trailing long sound mark from katakana words to normalize spelling variants, stemming only words at least min characters long.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('japanese_katakana_stem', ['min' => 3]);
$tokenizer = $builder->build();
japanese_stop_tags
Removes tokens whose part-of-speech matches any of the specified tags.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('japanese_stop_tags', [
'tags' => ['助詞', '助動詞'],
]);
$tokenizer = $builder->build();
japanese_keep_tags
Keeps only tokens whose part-of-speech matches one of the specified tags. All other tokens are removed.
<?php
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('embedded://ipadic');
$builder->appendTokenFilter('japanese_keep_tags', [
'tags' => ['名詞'],
]);
$tokenizer = $builder->build();
Complete Pipeline Example
The following example combines multiple character filters and token filters into a single pipeline:
<?php
$builder = new Lindera\TokenizerBuilder();
// Set mode and dictionary
$builder->setMode('normal');
$builder->setDictionary('embedded://ipadic');
// Preprocessing
$builder->appendCharacterFilter('unicode_normalize', ['kind' => 'nfkc']);
$builder->appendCharacterFilter(
'japanese_iteration_mark',
['normalize_kanji' => 'true', 'normalize_kana' => 'true']
);
$builder->appendCharacterFilter('mapping', ['mapping' => ['リンデラ' => 'lindera']]);
// Postprocessing
$builder->appendTokenFilter('japanese_katakana_stem', ['min' => 3]);
$builder->appendTokenFilter('japanese_stop_tags', [
'tags' => [
'接続詞',
'助詞',
'助詞,格助詞',
'助詞,格助詞,一般',
'助詞,係助詞',
'助詞,副助詞',
'助詞,終助詞',
'助詞,連体化',
'助動詞',
'記号',
'記号,一般',
'記号,読点',
'記号,句点',
'記号,空白',
],
]);
$builder->appendTokenFilter('lowercase');
// Build the tokenizer
$tokenizer = $builder->build();
// Tokenize
$text = 'Linderaは形態素解析エンジンです。';
$tokens = $tokenizer->tokenize($text);
foreach ($tokens as $token) {
echo $token->surface . "\t" . implode(',', $token->details) . "\n";
}
In this pipeline:
unicode_normalizeconverts full-width characters to half-width (NFKC normalization)japanese_iteration_markresolves iteration marksmappingreplaces the specified stringsjapanese_katakana_stemstems katakana wordsjapanese_stop_tagsremoves particles, auxiliary verbs, and symbolslowercasenormalizes alphabetic characters to lowercase
Training
Lindera PHP supports training custom CRF-based morphological analysis models from annotated corpora. This functionality requires the train feature.
Prerequisites
Build lindera-php with the train feature enabled (train is already enabled by default in lindera-php):
cargo build -p lindera-php --features train,embed-ipadic
Training a Model
Use Lindera\Trainer::train() to train a CRF model from a seed lexicon and annotated corpus:
<?php
Lindera\Trainer::train(
seed: 'resources/training/seed.csv',
corpus: 'resources/training/corpus.txt',
char_def: 'resources/training/char.def',
unk_def: 'resources/training/unk.def',
feature_def: 'resources/training/feature.def',
rewrite_def: 'resources/training/rewrite.def',
output: '/tmp/model.dat',
lambda: 0.01,
max_iter: 100,
max_threads: null,
);
Training Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
$seed | string | required | Path to the seed lexicon file (CSV format) |
$corpus | string | required | Path to the annotated training corpus |
$char_def | string | required | Path to the character definition file (char.def) |
$unk_def | string | required | Path to the unknown word definition file (unk.def) |
$feature_def | string | required | Path to the feature definition file (feature.def) |
$rewrite_def | string | required | Path to the rewrite rule definition file (rewrite.def) |
$output | string | required | Output path for the trained model file |
$lambda | float | 0.01 | L1 regularization cost (0.0--1.0) |
$max_iter | int | 100 | Maximum number of training iterations |
$max_threads | int|null | null | Number of threads (null = auto-detect CPU cores) |
Exporting a Trained Model
After training, export the model to dictionary source files using Lindera\Trainer::export():
<?php
Lindera\Trainer::export(
model: '/tmp/model.dat',
output: '/tmp/dictionary_source',
metadata: 'resources/training/metadata.json',
);
Export Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
$model | string | required | Path to the trained model file (.dat) |
$output | string | required | Output directory for dictionary source files |
$metadata | string|null | null | Path to a base metadata.json file |
The export creates the following files in the output directory:
lex.csv-- Lexicon entries with trained costsmatrix.def-- Connection cost matrixunk.def-- Unknown word definitionschar.def-- Character category definitionsmetadata.json-- Updated metadata (when$metadataparameter is provided)
Complete Workflow
The full workflow for training and using a custom dictionary:
<?php
// Step 1: Train the CRF model
Lindera\Trainer::train(
seed: 'resources/training/seed.csv',
corpus: 'resources/training/corpus.txt',
char_def: 'resources/training/char.def',
unk_def: 'resources/training/unk.def',
feature_def: 'resources/training/feature.def',
rewrite_def: 'resources/training/rewrite.def',
output: '/tmp/model.dat',
lambda: 0.01,
max_iter: 100,
);
// Step 2: Export to dictionary source files
Lindera\Trainer::export(
model: '/tmp/model.dat',
output: '/tmp/dictionary_source',
metadata: 'resources/training/metadata.json',
);
// Step 3: Build the dictionary from exported source files
$metadata = Lindera\Metadata::fromJsonFile('/tmp/dictionary_source/metadata.json');
Lindera\Dictionary::build('/tmp/dictionary_source', '/tmp/dictionary', $metadata);
// Step 4: Use the trained dictionary
$builder = new Lindera\TokenizerBuilder();
$builder->setDictionary('/tmp/dictionary');
$builder->setMode('normal');
$tokenizer = $builder->build();
$tokens = $tokenizer->tokenize('形態素解析のテスト');
foreach ($tokens as $token) {
echo $token->surface . "\t" . implode(',', $token->details) . "\n";
}
Lindera WASM
Lindera WASM provides WebAssembly bindings for Lindera's morphological analysis engine, built with wasm-bindgen. It enables Japanese, Korean, and Chinese text tokenization directly in web browsers, Node.js, and bundler environments.
Distribution Formats
Lindera WASM supports multiple distribution formats via wasm-pack:
| Target | Use Case | Module System |
|---|---|---|
web | Browser ESM | ES Modules |
bundler | Webpack, Vite, Rollup | ES Modules (bundler-resolved) |
Dictionary Packages
Each package embeds a specific dictionary for offline use:
| Feature Flag | Dictionary | Language |
|---|---|---|
| (none) | No embedded dictionary | -- |
embed-ipadic | IPADIC | Japanese |
embed-unidic | UniDic | Japanese |
embed-ko-dic | ko-dic | Korean |
embed-cc-cedict | CC-CEDICT | Chinese |
embed-jieba | Jieba | Chinese |
embed-cjk | IPADIC + ko-dic + Jieba | CJK |
Sections
- Installation -- Building and installing lindera-wasm packages
- Quick Start -- Minimal working example
- Tokenizer API -- Full API reference for JavaScript/TypeScript
- Dictionary Management -- Loading and building dictionaries
- Browser Usage -- Integration with web applications
- OPFS Dictionary Storage -- Persistent dictionary caching with OPFS
Installation
Prerequisites
Obtaining Dictionaries
Lindera WASM does not bundle dictionaries by default. The recommended approach for browser environments is to download dictionaries at runtime using the OPFS (Origin Private File System) API.
Download from GitHub Releases
Pre-built dictionaries are available on the GitHub Releases page. In browser environments, use the OPFS helpers to download and cache dictionaries:
import { downloadDictionary, hasDictionary } from 'lindera-wasm-web/opfs';
if (!await hasDictionary("ipadic")) {
await downloadDictionary(
"https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip",
"ipadic",
);
}
See OPFS Dictionary Storage for the full workflow.
Building with wasm-pack
Build the WASM package for your target environment:
Web (ES Modules for browsers)
wasm-pack build --target web
Bundler (Webpack, Vite, Rollup)
wasm-pack build --target bundler
The output is written to the pkg/ directory inside the lindera-wasm crate.
Available Feature Flags (Advanced)
For advanced users who want to embed dictionaries directly into the WASM binary, the following feature flags are available. This increases the binary size significantly but eliminates the need to download dictionaries at runtime.
| Feature | Dictionary | Language |
|---|---|---|
embed-ipadic | IPADIC | Japanese |
embed-unidic | UniDic | Japanese |
embed-ko-dic | ko-dic | Korean |
embed-cc-cedict | CC-CEDICT | Chinese |
embed-jieba | Jieba | Chinese |
embed-cjk | IPADIC + ko-dic + Jieba | CJK (all) |
You can combine multiple dictionaries by enabling multiple feature flags:
wasm-pack build --target web --features embed-ipadic,embed-ko-dic
NPM Package Naming Convention
When publishing to npm, the recommended naming convention is:
lindera-wasm-{target}
lindera-wasm-{target}-{dict}
Examples:
lindera-wasm-weblindera-wasm-web-ipadiclindera-wasm-bundler-unidiclindera-wasm-web-cjk
To set the package name before publishing, edit the name field in the generated pkg/package.json.
[!NOTE] This project's own release workflow (
.github/workflows/release.yml) only builds and publishes two packages to npm --lindera-wasm-webandlindera-wasm-bundler-- built without anyembed-*feature. Dictionary-suffixed names such aslindera-wasm-web-ipadicare not published anywhere; they only illustrate what a local build with anembed-*feature (see Available Feature Flags above) would produce after you rename the package yourself.
Installing from npm
Pre-built packages are available on npm:
npm install lindera-wasm-web
Or with yarn:
yarn add lindera-wasm-web
[!NOTE] The npm package does not include dictionaries. Use the OPFS helpers to download dictionaries at runtime. See OPFS Dictionary Storage.
Quick Start
Web (Browser) -- OPFS Dictionary Loading
The recommended approach is to download dictionaries at runtime using the OPFS helpers:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm-web';
import { downloadDictionary, loadDictionaryFiles, hasDictionary } from 'lindera-wasm-web/opfs';
async function main() {
await __wbg_init();
// Download dictionary if not cached
if (!await hasDictionary("ipadic")) {
await downloadDictionary(
"https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip",
"ipadic",
);
}
// Load dictionary from OPFS
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictDa, files.dictVals, files.dictWordsIdx,
files.dictWords, files.matrixMtx, files.charDef, files.unk,
);
// Build tokenizer
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("関西国際空港限定トートバッグ");
tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
}
main();
Note: Download a pre-built dictionary from GitHub Releases. See OPFS Dictionary Storage for the full workflow.
Expected output:
関西国際空港 名詞,固有名詞,一般,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定 名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ 名詞,一般,*,*,*,*,*,*,*
Using Embedded Dictionaries (Advanced)
If you built with an embed-* feature flag, you can use embedded dictionaries:
[!NOTE]
lindera-wasm-web-ipadicis an illustrative package name here, not something published to npm. Onlylindera-wasm-webandlindera-wasm-bundlerare actually published; see NPM Package Naming Convention for how to build and name a package like this yourself.
import __wbg_init, { TokenizerBuilder } from 'lindera-wasm-web-ipadic';
async function main() {
await __wbg_init();
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("関西国際空港限定トートバッグ");
tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
}
main();
Using Filters
You can add character filters and token filters to the tokenization pipeline:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm-web';
import { loadDictionaryFiles } from 'lindera-wasm-web/opfs';
async function main() {
await __wbg_init();
// Assume dictionary is already cached in OPFS
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictDa, files.dictVals, files.dictWordsIdx,
files.dictWords, files.matrixMtx, files.charDef, files.unk,
);
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
// Add Unicode NFKC normalization
builder.appendCharacterFilter("unicode_normalize", { kind: "nfkc" });
// Add a stop-tags filter to remove particles and auxiliary verbs
builder.appendTokenFilter("japanese_stop_tags", {
tags: ["助詞", "助動詞"]
});
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("Linderaは形態素解析エンジンです");
tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
}
main();
N-Best Tokenization
Retrieve multiple tokenization candidates ranked by cost:
const results = tokenizer.tokenizeNbest("すもももももももものうち", 3);
results.forEach((result, rank) => {
console.log(`--- NBEST ${rank + 1} (cost=${result.cost}) ---`);
result.tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
});
Tokenizer API
This page documents the JavaScript/TypeScript API exposed by lindera-wasm.
TokenizerBuilder
Builder class for creating a configured Tokenizer instance.
Constructor
const builder = new TokenizerBuilder();
Creates a new builder with default settings.
Methods
setMode(mode)
Sets the tokenization mode.
- Parameters:
mode(string) --"normal"or"decompose" - Returns: void
builder.setMode("normal");
setDictionary(uri)
Sets the dictionary to use for tokenization.
- Parameters:
uri(string) -- Dictionary URI (e.g.,"embedded://ipadic") - Returns: void
builder.setDictionary("embedded://ipadic");
setDictionaryInstance(dictionary)
Sets a pre-loaded dictionary instance for tokenization.
Use this when the dictionary has been loaded from bytes (e.g., via loadDictionaryFromBytes()) instead of from a URI.
- Parameters:
dictionary(Dictionary) -- A loaded dictionary object - Returns: void
import { loadDictionaryFromBytes } from 'lindera-wasm-web';
import { loadDictionaryFiles } from 'lindera-wasm-web/opfs';
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictDa, files.dictVals, files.dictWordsIdx,
files.dictWords, files.matrixMtx, files.charDef, files.unk,
);
builder.setDictionaryInstance(dictionary);
setUserDictionary(uri)
Sets a user-defined dictionary by URI.
- Parameters:
uri(string) -- Path or URI to the user dictionary - Returns: void
builder.setUserDictionary("file:///path/to/user_dict.csv");
setUserDictionaryInstance(userDictionary)
Sets a pre-loaded user dictionary instance. Use this when the user dictionary has been loaded from bytes instead of from a URI.
- Parameters:
userDictionary(UserDictionary) -- A loaded user dictionary object - Returns: void
setKeepWhitespace(keep)
Sets whether whitespace tokens are preserved in the output.
- Parameters:
keep(boolean) --trueto keep whitespace tokens - Returns: void
builder.setKeepWhitespace(true);
appendCharacterFilter(name, args)
Appends a character filter to the preprocessing pipeline.
- Parameters:
name(string) -- Filter name (e.g.,"unicode_normalize","japanese_iteration_mark")args(object, optional) -- Filter configuration
- Returns: void
builder.appendCharacterFilter("unicode_normalize", { kind: "nfkc" });
appendTokenFilter(name, args)
Appends a token filter to the postprocessing pipeline.
- Parameters:
name(string) -- Filter name (e.g.,"japanese_stop_tags","lowercase")args(object, optional) -- Filter configuration
- Returns: void
builder.appendTokenFilter("japanese_stop_tags", {
tags: ["助詞", "助動詞", "記号"]
});
build()
Builds and returns a configured Tokenizer instance. Consumes the builder.
- Returns:
Tokenizer
const tokenizer = builder.build();
Tokenizer
The main tokenizer class. Can be created via TokenizerBuilder.build() or directly via the constructor.
Tokenizer Constructor
const tokenizer = new Tokenizer(dictionary, mode, userDictionary);
- Parameters:
dictionary(Dictionary) -- A loaded dictionary objectmode(string, optional) -- Tokenization mode ("normal"or"decompose", defaults to"normal")userDictionary(UserDictionary, optional) -- A loaded user dictionary
Tokenizer Methods
tokenize(text)
Tokenizes the input text.
- Parameters:
text(string) -- Text to tokenize - Returns:
Token[]-- Array of token objects
const tokens = tokenizer.tokenize("関西国際空港");
tokenizeNbest(text, n, unique?, costThreshold?)
Returns N-best tokenization results ordered by total path cost.
- Parameters:
text(string) -- Text to tokenizen(number) -- Number of results to returnunique(boolean, optional) -- Deduplicate results with identical segmentation (default:false)costThreshold(bigint, optional) -- Only return paths withinbestCost + threshold
- Returns: Array of
{ tokens: object[], cost: number }
const results = tokenizer.tokenizeNbest("すもももももももものうち", 3);
// With a cost threshold -- note that it must be passed as a bigint literal
const resultsWithThreshold = tokenizer.tokenizeNbest("すもももももももものうち", 3, false, 100n);
Token
Represents a single token produced by the tokenizer.
Properties
| Property | Type | Description |
|---|---|---|
surface | string | Surface form of the token |
byte_start | number | Start byte offset in the original text |
byte_end | number | End byte offset in the original text |
position | number | Position index of the token |
word_id | number | Word ID in the dictionary |
is_unknown | boolean | Whether the token is an unknown word |
details | string[] | Morphological detail fields |
[!NOTE] These are the real field names exposed on the
Tokenobject --lindera-wasm/src/token.rsdoes not apply anyjs_namerename, so the fields stay snake_case in JavaScript. OnlytoJSON()(below) renames them to camelCase for JSON-friendly output.
Token Methods
getDetail(index)
Returns the detail string at the specified index.
- Parameters:
index(number) -- Zero-based index into the details array - Returns:
string | undefined
const pos = token.getDetail(0); // e.g., "名詞"
const reading = token.getDetail(7); // e.g., "トウキョウ"
toJSON()
Returns a plain JavaScript object representation of the token.
- Returns:
objectwith keys:surface,byteStart,byteEnd,position,wordId,isUnknown,details
console.log(JSON.stringify(token.toJSON(), null, 2));
Helper Functions
[!NOTE] The examples below import from
lindera-wasm-web-ipadic, an illustrative package name for a local build with theembed-ipadicfeature -- it is not published to npm. Onlylindera-wasm-webandlindera-wasm-bundlerare actually published; see NPM Package Naming Convention.
loadDictionary(uri)
Loads a dictionary from the specified URI.
- Parameters:
uri(string) -- Dictionary URI (e.g.,"embedded://ipadic") - Returns:
Dictionary
import { loadDictionary } from 'lindera-wasm-web-ipadic';
const dict = loadDictionary("embedded://ipadic");
loadUserDictionary(uri, metadata)
Loads a user dictionary from the specified URI.
- Parameters:
uri(string) -- Path or URI to the user dictionary filemetadata(Metadata) -- Dictionary metadata object
- Returns:
UserDictionary
buildDictionary(inputDir, outputDir, metadata)
Builds a compiled dictionary from source files.
- Parameters:
inputDir(string) -- Path to the directory containing source dictionary filesoutputDir(string) -- Path to the output directorymetadata(Metadata) -- Dictionary metadata object
- Returns: void
buildUserDictionary(inputFile, outputDir, metadata?)
Builds a compiled user dictionary from a CSV file.
- Parameters:
inputFile(string) -- Path to the user dictionary CSV fileoutputDir(string) -- Path to the output directorymetadata(Metadata, optional) -- Dictionary metadata object
- Returns: void
version() / getVersion()
Returns the version string of the lindera-wasm package.
- Returns:
string
import { version } from 'lindera-wasm-web-ipadic';
console.log(version()); // e.g., "4.0.1"
Enums and Utility Classes
Mode
Tokenization mode enum.
| Value | Description |
|---|---|
Mode.Normal | Standard tokenization based on dictionary cost |
Mode.Decompose | Decompose compound words using penalty-based segmentation |
Penalty
Configuration for decompose mode. Controls how aggressively compound words are decomposed.
const penalty = new Penalty(
kanjiThreshold?, // Kanji length threshold (default: 2)
kanjiPenalty?, // Kanji length penalty (default: 3000)
otherThreshold?, // Other character length threshold (default: 7)
otherPenalty?, // Other character length penalty (default: 1700)
);
| Property | Type | Default | Description |
|---|---|---|---|
kanji_penalty_length_threshold | number | 2 | Length threshold for kanji compound splitting |
kanji_penalty_length_penalty | number | 3000 | Penalty cost for kanji compounds exceeding threshold |
other_penalty_length_threshold | number | 7 | Length threshold for non-kanji compound splitting |
other_penalty_length_penalty | number | 1700 | Penalty cost for non-kanji compounds exceeding threshold |
LinderaError
Error type for Lindera operations.
const error = new LinderaError("message");
console.log(error.message); // "message"
console.log(error.toString()); // "message"
| Property / Method | Type | Description |
|---|---|---|
message | string | Error message |
toString() | string | Returns the error message |
[!NOTE]
LinderaErroris exported as a utility class, but the current error paths inTokenizerBuilder,Tokenizer, and the dictionary-loading functions (lindera-wasm/src/tokenizer.rs,lindera-wasm/src/dictionary.rs) all reject withJsValue::from_str(...), not aJsLinderaError/LinderaErrorinstance. In practice, failures thrown by these APIs surface in JavaScript as plain strings, so catch them withcatch (e) { ... }and treateas astring, not as aLinderaErrorinstance.
Snake-Case Aliases
For consistency with the Python API, all methods are also available in snake_case form:
| camelCase | snake_case |
|---|---|
setMode() | set_mode() |
setDictionary() | set_dictionary() |
setDictionaryInstance() | set_dictionary_instance() |
setUserDictionary() | set_user_dictionary() |
setUserDictionaryInstance() | set_user_dictionary_instance() |
setKeepWhitespace() | set_keep_whitespace() |
appendCharacterFilter() | append_character_filter() |
appendTokenFilter() | append_token_filter() |
tokenizeNbest() | tokenize_nbest() |
loadDictionary() | load_dictionary() |
loadDictionaryFromBytes() | load_dictionary_from_bytes() |
loadUserDictionary() | load_user_dictionary() |
buildDictionary() | build_dictionary() |
buildUserDictionary() | build_user_dictionary() |
Dictionary Management
Loading Dictionaries from OPFS
The recommended way to use dictionaries in WASM is to download them from GitHub Releases and load them via OPFS. This avoids embedding large dictionaries in the WASM binary.
Loading from Bytes
Use loadDictionaryFromBytes() to construct a Dictionary from raw byte arrays stored in OPFS or other browser storage.
loadDictionaryFromBytes(metadata, dictDa, dictVals, dictWordsIdx, dictWords, matrixMtx, charDef, unk)
- Parameters:
metadata(Uint8Array) -- Contents ofmetadata.jsondictDa(Uint8Array) -- Contents ofdict.da(Double-Array Trie)dictVals(Uint8Array) -- Contents ofdict.vals(word value data)dictWordsIdx(Uint8Array) -- Contents ofdict.wordsidx(word details index)dictWords(Uint8Array) -- Contents ofdict.words(word details)matrixMtx(Uint8Array) -- Contents ofmatrix.mtx(connection cost matrix)charDef(Uint8Array) -- Contents ofchar_def.bin(character definitions)unk(Uint8Array) -- Contents ofunk.bin(unknown word dictionary)
- Returns:
Dictionary
import { loadDictionaryFromBytes, TokenizerBuilder } from 'lindera-wasm-web';
import { loadDictionaryFiles } from 'lindera-wasm-web/opfs';
// Load dictionary files from OPFS
const files = await loadDictionaryFiles("ipadic");
// Create a Dictionary from bytes
const dictionary = loadDictionaryFromBytes(
files.metadata,
files.dictDa,
files.dictVals,
files.dictWordsIdx,
files.dictWords,
files.matrixMtx,
files.charDef,
files.unk,
);
// Use with TokenizerBuilder
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
See OPFS Dictionary Storage for the full OPFS workflow including downloading and caching.
Embedded Dictionaries (Advanced)
If you built with an embed-* feature flag, you can load embedded dictionaries via the embedded:// URI scheme. This increases the WASM binary size significantly.
[!NOTE]
lindera-wasm-web-ipadicin the examples below is an illustrative package name for a local build with theembed-ipadicfeature, not something published to npm. Onlylindera-wasm-webandlindera-wasm-bundlerare actually published; see NPM Package Naming Convention.
Loading an Embedded Dictionary
import { loadDictionary } from 'lindera-wasm-web-ipadic';
const dictionary = loadDictionary("embedded://ipadic");
Available embedded dictionary URIs (depending on which features were enabled at build time):
| URI | Feature Flag |
|---|---|
embedded://ipadic | embed-ipadic |
embedded://unidic | embed-unidic |
embedded://ko-dic | embed-ko-dic |
embedded://cc-cedict | embed-cc-cedict |
embedded://jieba | embed-jieba |
Using with TokenizerBuilder
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.setMode("normal");
const tokenizer = builder.build();
Using with Tokenizer Constructor
import { loadDictionary, Tokenizer } from 'lindera-wasm-web-ipadic';
const dictionary = loadDictionary("embedded://ipadic");
const tokenizer = new Tokenizer(dictionary, "normal");
Dictionary Class
The Dictionary class represents a loaded morphological analysis dictionary.
Properties
| Property | Type | Description |
|---|---|---|
name | string | Dictionary name (e.g., "ipadic") |
encoding | string | Character encoding of the dictionary |
metadata | Metadata | Full metadata object |
console.log(dictionary.name); // "ipadic"
console.log(dictionary.encoding); // "utf-8"
User Dictionaries
User dictionaries allow you to add custom words that are not in the system dictionary.
Loading a User Dictionary
import { loadUserDictionary } from 'lindera-wasm-web';
const metadata = dictionary.metadata;
const userDict = loadUserDictionary("/path/to/user_dict.csv", metadata);
Using a User Dictionary with Tokenizer
import { loadDictionaryFromBytes, loadUserDictionary, Tokenizer } from 'lindera-wasm-web';
import { loadDictionaryFiles } from 'lindera-wasm-web/opfs';
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictDa, files.dictVals, files.dictWordsIdx,
files.dictWords, files.matrixMtx, files.charDef, files.unk,
);
const userDict = loadUserDictionary("/path/to/user_dict.csv", dictionary.metadata);
const tokenizer = new Tokenizer(dictionary, "normal", userDict);
User Dictionary CSV Format
The user dictionary CSV follows the same format as the Lindera user dictionary:
東京スカイツリー,カスタム名詞,トウキョウスカイツリー
東武スカイツリーライン,カスタム名詞,トウブスカイツリーライン
Each line contains: surface,part_of_speech,reading
Building Dictionaries
You can build compiled dictionaries from source files using the JavaScript API.
Building a System Dictionary
metadata must be an actual Metadata instance, not a plain object literal -- the generated binding asserts the argument is a Metadata and throws otherwise. Create one with Metadata.createDefault() and set the fields you need:
import { buildDictionary, Metadata } from 'lindera-wasm-web';
const metadata = Metadata.createDefault();
metadata.name = "custom-dict";
metadata.encoding = "utf-8";
buildDictionary("/path/to/source/dir", "/path/to/output/dir", metadata);
Building a User Dictionary
import { buildUserDictionary } from 'lindera-wasm-web';
buildUserDictionary("/path/to/user_dict.csv", "/path/to/output/dir");
The metadata parameter is optional for buildUserDictionary. If omitted, default metadata is used.
Metadata
The Metadata class configures dictionary parameters.
Constructor
const metadata = new Metadata(name?, encoding?);
- Parameters:
name(string, optional) -- Dictionary name (default:"default")encoding(string, optional) -- Character encoding (default:"UTF-8")
Static Methods
Metadata.createDefault()
Creates a Metadata instance with default values.
const metadata = Metadata.createDefault();
Metadata Properties
| Property | Type | Default | Description |
|---|---|---|---|
name | string | "default" | Dictionary name |
encoding | string | "UTF-8" | Character encoding |
dictionary_schema | Schema | IPADIC schema | Schema for the main dictionary |
user_dictionary_schema | Schema | Minimal schema | Schema for user dictionaries |
All properties support both getting and setting:
const metadata = Metadata.createDefault();
metadata.name = "custom_dict";
metadata.encoding = "EUC-JP";
console.log(metadata.name); // "custom_dict"
[!NOTE] Unlike the Python, Node.js, Ruby, and PHP bindings, the WASM
Metadataclass does not exposedefault_word_cost,default_left_context_id,default_right_context_id,default_field_value,flexible_csv,skip_invalid_cost_or_id, ornormalize_detailsas gettable/settable properties (seelindera-wasm/src/metadata.rs). These always fall back to the shared binding defaults (word cost-10000, context IDs1288, field value"*", flagsfalse) and cannot be customized from JavaScript.
You can also access the metadata from a loaded dictionary via dictionary.metadata.
Schema
The Schema class defines the field structure of dictionary entries.
Schema Constructor
const schema = new Schema(["surface", "left_id", "right_id", "cost", "pos", "reading"]);
Schema Static Methods
Schema.create_default()-- Creates a built-in 13-field schema loosely modeled on IPADIC's layout: the four system fields (surface,left_context_id,right_context_id,cost) followed by nine generic feature fields (major_pos,pos_detail_1-pos_detail_3,conjugation_type,conjugation_form,base_form,reading,pronunciation). These names -- and theconjugation_type/conjugation_formorder -- differ from the reallindera-ipadicdictionary schema (part_of_speech,part_of_speech_subcategory_1-_3,conjugation_form,conjugation_type, ...). To match an actual IPADIC dictionary's schema, usedictionary.metadata.dictionary_schemafrom a loaded dictionary instead
Schema Methods
| Method | Returns | Description |
|---|---|---|
get_field_index(name) | number | undefined | Get field index by name |
field_count() | number | Total number of fields |
get_field_name(index) | string | undefined | Get field name by index |
get_custom_fields() | string[] | Fields beyond index 3 (morphological features) |
get_all_fields() | string[] | All field names |
get_field_by_name(name) | FieldDefinition | undefined | Get full field definition |
FieldDefinition
| Property | Type | Description |
|---|---|---|
index | number | Field position index |
name | string | Field name |
field_type | FieldType | Field type enum |
description | string | undefined | Optional description |
FieldType
| Value | Description |
|---|---|
FieldType.Surface | Word surface text |
FieldType.LeftContextId | Left context ID |
FieldType.RightContextId | Right context ID |
FieldType.Cost | Word cost |
FieldType.Custom | Morphological feature field |
Browser Usage
ES Module Import
In browser environments, you must initialize the WASM module before using any Lindera functions. The default export __wbg_init handles this initialization.
The recommended approach is to load dictionaries from OPFS rather than embedding them in the WASM binary:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm-web';
import { downloadDictionary, loadDictionaryFiles, hasDictionary } from 'lindera-wasm-web/opfs';
async function main() {
// Initialize the WASM module (must be called once before using any API)
await __wbg_init();
// Download dictionary if not cached
if (!await hasDictionary("ipadic")) {
await downloadDictionary(
"https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip",
"ipadic",
);
}
// Load dictionary from OPFS
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictDa, files.dictVals, files.dictWordsIdx,
files.dictWords, files.matrixMtx, files.charDef, files.unk,
);
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("形態素解析を行います");
tokens.forEach(token => {
console.log(`${token.surface}: ${token.details.join(',')}`);
});
}
main();
Using Embedded Dictionaries (Advanced)
If you built with an embed-* feature flag, you can use embedded dictionaries instead of OPFS:
[!NOTE]
lindera-wasm-web-ipadicis an illustrative package name here, not something published to npm. Onlylindera-wasm-webandlindera-wasm-bundlerare actually published; see NPM Package Naming Convention for how to build and name a package like this yourself.
import __wbg_init, { TokenizerBuilder } from 'lindera-wasm-web-ipadic';
async function main() {
await __wbg_init();
const builder = new TokenizerBuilder();
builder.setDictionary("embedded://ipadic");
builder.setMode("normal");
const tokenizer = builder.build();
const tokens = tokenizer.tokenize("形態素解析を行います");
tokens.forEach(token => {
console.log(`${token.surface}: ${token.details.join(',')}`);
});
}
main();
HTML Example
A minimal HTML page using lindera-wasm with OPFS dictionary loading:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lindera WASM Demo</title>
</head>
<body>
<textarea id="input" rows="4" cols="50">関西国際空港限定トートバッグ</textarea>
<br>
<button id="tokenize" disabled>Tokenize</button>
<pre id="output">Loading dictionary...</pre>
<script type="module">
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from './pkg/lindera_wasm.js';
import { downloadDictionary, loadDictionaryFiles, hasDictionary } from './pkg/opfs.js';
let tokenizer;
async function init() {
await __wbg_init();
// Download dictionary if not cached
if (!await hasDictionary("ipadic")) {
document.getElementById('output').textContent = 'Downloading dictionary...';
await downloadDictionary(
"https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip",
"ipadic",
);
}
// Load dictionary from OPFS
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictDa, files.dictVals, files.dictWordsIdx,
files.dictWords, files.matrixMtx, files.charDef, files.unk,
);
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
tokenizer = builder.build();
document.getElementById('tokenize').disabled = false;
document.getElementById('output').textContent = 'Ready!';
}
document.getElementById('tokenize').addEventListener('click', () => {
const text = document.getElementById('input').value;
const tokens = tokenizer.tokenize(text);
const output = tokens.map(t =>
`${t.surface}\t${t.details.join(',')}`
).join('\n');
document.getElementById('output').textContent = output;
});
init();
</script>
</body>
</html>
Webpack Configuration
When using Webpack 5, enable the asyncWebAssembly experiment:
// webpack.config.js
module.exports = {
experiments: {
asyncWebAssembly: true,
},
module: {
rules: [
{
test: /\.wasm$/,
type: "webassembly/async",
},
],
},
};
Then import using the bundler target build:
import { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm-bundler';
import { loadDictionaryFiles } from 'lindera-wasm-bundler/opfs';
// Load dictionary from OPFS (see OPFS Dictionary Storage for setup)
const files = await loadDictionaryFiles("ipadic");
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictDa, files.dictVals, files.dictWordsIdx,
files.dictWords, files.matrixMtx, files.charDef, files.unk,
);
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
With the bundler target, __wbg_init() is called automatically by the bundler.
Vite / Rollup Setup
Vite supports WASM out of the box with the web target. Place the built pkg/ directory in your project and import directly:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from './pkg/lindera_wasm.js';
import { loadDictionaryFiles } from './pkg/opfs.js';
await __wbg_init();
// Load dictionary from OPFS and use TokenizerBuilder as shown above
For the bundler target with Vite, you may need the vite-plugin-wasm plugin:
// vite.config.js
import wasm from 'vite-plugin-wasm';
export default {
plugins: [wasm()],
};
Chrome Extension Considerations
Chrome extensions using Manifest V3 restrict WebAssembly.compile and WebAssembly.instantiate by default. To use lindera-wasm in an extension, you need to add wasm-unsafe-eval to your Content Security Policy:
{
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}
Note that wasm-unsafe-eval only allows WebAssembly execution and does not permit arbitrary JavaScript eval().
Performance Tips
- Initialize once: Call
__wbg_init()once at application startup, not on every tokenization request. - Reuse the tokenizer: Create the
Tokenizerinstance once and reuse it for multiple calls totokenize(). - Web Workers: For heavy tokenization workloads, consider running Lindera in a Web Worker to avoid blocking the main thread.
OPFS Dictionary Storage
Lindera WASM provides OPFS (Origin Private File System) helper utilities for persistent dictionary caching in web browsers. This allows you to download dictionaries once and reuse them across sessions without embedding them in the WASM binary.
Overview
The OPFS helpers are distributed as a separate JavaScript module (opfs.js) alongside the WASM package. They provide functions to download, store, load, and manage dictionaries using the browser's Origin Private File System.
Dictionaries are stored under the OPFS path lindera/dictionaries/<name>/.
Import
import { downloadDictionary, loadDictionaryFiles, removeDictionary,
listDictionaries, hasDictionary } from 'lindera-wasm-web/opfs';
Functions
downloadDictionary(url, name, options?)
Downloads a dictionary zip archive, extracts it, and stores the files in OPFS.
The archive should be a zip file containing the 8 required dictionary files, optionally nested in a subdirectory.
- Parameters:
url(string) -- URL of the dictionary zip archivename(string) -- Name to store the dictionary under (e.g.,"ipadic")options(object, optional):onProgress(function) -- Progress callbackfetchInit(RequestInit, optional) -- Additional options passed through tofetch()(e.g., custom headers, credentials, anAbortSignal)
- Returns:
Promise<void>
await downloadDictionary(
"https://example.com/ipadic.zip",
"ipadic",
{
onProgress: (progress) => {
switch (progress.phase) {
case "downloading":
console.log(`Downloading: ${progress.loaded}/${progress.total} bytes`);
break;
case "extracting":
console.log("Extracting archive...");
break;
case "storing":
console.log("Storing in OPFS...");
break;
case "complete":
console.log("Done!");
break;
}
},
},
);
Progress Callback
The onProgress callback receives an object with the following shape:
| Property | Type | Description |
|---|---|---|
phase | string | "downloading", "extracting", "storing", or "complete" |
loaded | number | undefined | Bytes downloaded (only during "downloading" phase) |
total | number | undefined | Total bytes if known (only during "downloading" phase) |
loadDictionaryFiles(name)
Loads dictionary files from OPFS as an object of Uint8Array values.
The returned object can be passed directly to loadDictionaryFromBytes().
- Parameters:
name(string) -- The dictionary name (e.g.,"ipadic") - Returns:
Promise<DictionaryFiles>
const files = await loadDictionaryFiles("ipadic");
DictionaryFiles
| Property | Type | Source File |
|---|---|---|
metadata | Uint8Array | metadata.json |
dictDa | Uint8Array | dict.da (Double-Array Trie) |
dictVals | Uint8Array | dict.vals (word value data) |
dictWordsIdx | Uint8Array | dict.wordsidx (word details index) |
dictWords | Uint8Array | dict.words (word details) |
matrixMtx | Uint8Array | matrix.mtx (connection cost matrix) |
charDef | Uint8Array | char_def.bin (character definitions) |
unk | Uint8Array | unk.bin (unknown word dictionary) |
removeDictionary(name)
Removes a dictionary from OPFS.
- Parameters:
name(string) -- The dictionary name to remove - Returns:
Promise<void>
await removeDictionary("ipadic");
listDictionaries()
Lists all dictionaries stored in OPFS.
- Returns:
Promise<string[]>-- Array of dictionary names
const names = await listDictionaries();
console.log(names); // e.g., ["ipadic", "unidic"]
hasDictionary(name)
Checks if a dictionary exists in OPFS.
- Parameters:
name(string) -- The dictionary name to check - Returns:
Promise<boolean>
if (await hasDictionary("ipadic")) {
console.log("Dictionary is cached");
}
Complete Workflow
A typical workflow for using OPFS-based dictionaries:
import __wbg_init, { TokenizerBuilder, loadDictionaryFromBytes } from 'lindera-wasm-web';
import { downloadDictionary, loadDictionaryFiles, hasDictionary } from 'lindera-wasm-web/opfs';
async function main() {
await __wbg_init();
const DICT_NAME = "ipadic";
const DICT_URL = "https://github.com/lindera/lindera/releases/download/<version>/lindera-ipadic-<version>.zip";
// Download dictionary if not already cached
if (!await hasDictionary(DICT_NAME)) {
await downloadDictionary(DICT_URL, DICT_NAME, {
onProgress: ({ phase, loaded, total }) => {
if (phase === "downloading" && total) {
console.log(`${(loaded / total * 100).toFixed(1)}%`);
}
},
});
}
// Load dictionary from OPFS
const files = await loadDictionaryFiles(DICT_NAME);
const dictionary = loadDictionaryFromBytes(
files.metadata, files.dictDa, files.dictVals, files.dictWordsIdx,
files.dictWords, files.matrixMtx, files.charDef, files.unk,
);
// Build tokenizer
const builder = new TokenizerBuilder();
builder.setDictionaryInstance(dictionary);
builder.setMode("normal");
const tokenizer = builder.build();
// Tokenize
const tokens = tokenizer.tokenize("形態素解析を行います");
tokens.forEach(token => {
console.log(`${token.surface}\t${token.details.join(',')}`);
});
}
main();
Required Dictionary Files
A valid dictionary archive must contain these 8 files:
| File | Description |
|---|---|
metadata.json | Dictionary metadata (name, encoding, schema, etc.) |
dict.da | Double-Array Trie structure |
dict.vals | Word value data |
dict.wordsidx | Word details index |
dict.words | Word details (morphological features) |
matrix.mtx | Connection cost matrix |
char_def.bin | Character category definitions |
unk.bin | Unknown word dictionary |
Browser Compatibility
OPFS requires a secure context (HTTPS or localhost) and is supported in:
- Chrome 86+
- Edge 86+
- Firefox 111+
- Safari 15.2+
The zip extraction uses the DecompressionStream API, which requires:
- Chrome 80+
- Edge 80+
- Firefox 113+
- Safari 16.4+
Lindera IPADIC
Lindera IPADIC is a Japanese dictionary crate based on IPADIC. IPADIC is the most common dictionary for Japanese morphological analysis.
Contents
- Dictionary Format -- Field definitions for system and user dictionaries
- Build -- How to build the dictionary from source
- Examples -- Tokenization examples
API Reference
Lindera IPADIC
Dictionary version
This repository contains mecab-ipadic.
Dictionary format
Refer to the manual for details on the IPADIC dictionary format and part-of-speech tags.
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞 | Part-of-speech | |
| 5 | 品詞細分類1 | Part-of-speech subcategory 1 | |
| 6 | 品詞細分類2 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類3 | Part-of-speech subcategory 3 | |
| 8 | 活用形 | Conjugation form | |
| 9 | 活用型 | Conjugation type | |
| 10 | 原形 | Base form | |
| 11 | 読み | Reading | |
| 12 | 発音 | Pronunciation |
User dictionary format (CSV)
Simple version
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 品詞 | Part-of-speech | |
| 2 | 読み | Reading |
Fields not covered by this simple schema (such as base_form and pronunciation) are filled with the dictionary's default_field_value (* for IPADIC).
Detailed version
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞 | Part-of-speech | |
| 5 | 品詞細分類1 | Part-of-speech subcategory 1 | |
| 6 | 品詞細分類2 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類3 | Part-of-speech subcategory 3 | |
| 8 | 活用形 | Conjugation form | |
| 9 | 活用型 | Conjugation type | |
| 10 | 原形 | Base form | |
| 11 | 読み | Reading | |
| 12 | 発音 | Pronunciation | |
| 13 | - | - | After 13, it can be freely expanded. |
API reference
The API reference is available. Please see following URL:
Build
This page describes how to build the IPADIC dictionary from source files.
Build system dictionary
Download the IPADIC source files and build the dictionary:
# Download and extract IPADIC source files
% curl -L -o /tmp/mecab-ipadic-2.7.0-20250920.tar.gz "https://Lindera.dev/mecab-ipadic-2.7.0-20250920.tar.gz"
% tar zxvf /tmp/mecab-ipadic-2.7.0-20250920.tar.gz -C /tmp
# Build the dictionary
% lindera build \
--src /tmp/mecab-ipadic-2.7.0-20250920 \
--dest /tmp/lindera-ipadic-2.7.0-20250920 \
--metadata ./lindera-ipadic/metadata.json
Build user dictionary
Build a user dictionary from a CSV file:
% lindera build \
--src ./resources/user_dict/ipadic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ipadic/metadata.json \
--user
For more details about user dictionary format, see Dictionary Format.
Embedding in binary
To embed the IPADIC dictionary directly into the binary:
cargo build --features=embed-ipadic
This allows using embedded://ipadic as the dictionary path without external dictionary files.
Examples
This page shows tokenization examples using the IPADIC dictionary.
Tokenize with external IPADIC
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-ipadic-2.7.0-20250920
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
Tokenize with embedded IPADIC
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析 名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
NOTE: To include IPADIC dictionary in the binary, you must build with the --features=embed-ipadic option.
Tokenize with user dictionary (CSV format)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict embedded://ipadic \
--user-dict ./resources/user_dict/ipadic_simple_userdic.csv
東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
Tokenize with user dictionary (binary format)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict /tmp/lindera-ipadic-2.7.0-20250920 \
--user-dict ./resources/user_dict/ipadic_simple_userdic.bin
東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
Rust API example
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "日本語の形態素解析を行うことができます。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera IPADIC NEologd
Lindera IPADIC NEologd is a Japanese dictionary crate based on IPADIC NEologd, which includes neologisms (new words). It extends the standard IPADIC dictionary with additional vocabulary covering recent terms and proper nouns.
Contents
- Dictionary Format -- Field definitions for system and user dictionaries
- Build -- How to build the dictionary from source
- Examples -- Tokenization examples
API Reference
Lindera IPADIC NEologd
Dictionary version
This repository contains mecab-ipadic-neologd.
Dictionary format
Refer to the manual for details on the IPADIC dictionary format and part-of-speech tags.
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞 | Part-of-speech | |
| 5 | 品詞細分類1 | Part-of-speech subcategory 1 | |
| 6 | 品詞細分類2 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類3 | Part-of-speech subcategory 3 | |
| 8 | 活用形 | Conjugation form | |
| 9 | 活用型 | Conjugation type | |
| 10 | 原形 | Base form | |
| 11 | 読み | Reading | |
| 12 | 発音 | Pronunciation |
User dictionary format (CSV)
Simple version
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 品詞 | Part-of-speech | |
| 2 | 読み | Reading |
Detailed version
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞 | Part-of-speech | |
| 5 | 品詞細分類1 | Part-of-speech subcategory 1 | |
| 6 | 品詞細分類2 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類3 | Part-of-speech subcategory 3 | |
| 8 | 活用形 | Conjugation form | |
| 9 | 活用型 | Conjugation type | |
| 10 | 原形 | Base form | |
| 11 | 読み | Reading | |
| 12 | 発音 | Pronunciation | |
| 13 | - | - | After 13, it can be freely expanded. |
API reference
The API reference is available. Please see following URL:
Build
This page describes how to build the IPADIC NEologd dictionary from source files.
Build system dictionary
Download the IPADIC NEologd source files and build the dictionary:
% curl -L -o /tmp/mecab-ipadic-neologd-0.0.7-20200820.tar.gz "https://lindera.dev/mecab-ipadic-neologd-0.0.7-20200820.tar.gz"
% tar zxvf /tmp/mecab-ipadic-neologd-0.0.7-20200820.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-ipadic-neologd-0.0.7-20200820 \
--dest /tmp/lindera-ipadic-neologd-0.0.7-20200820 \
--metadata ./lindera-ipadic-neologd/metadata.json
Build user dictionary
Build a user dictionary from a CSV file:
% lindera build \
--src ./resources/user_dict/ipadic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ipadic-neologd/metadata.json \
--user
For more details about user dictionary format, see Dictionary Format.
Embedding in binary
To embed the IPADIC NEologd dictionary directly into the binary:
cargo build --features=embed-ipadic-neologd
This allows using embedded://ipadic-neologd as the dictionary path without external dictionary files.
Examples
This page shows tokenization examples using the IPADIC NEologd dictionary.
Tokenize with external IPADIC NEologd
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-ipadic-neologd-0.0.7-20200820
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素解析 名詞,固有名詞,一般,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
Notice that NEologd treats "形態素解析" (morphological analysis) as a single compound noun, whereas standard IPADIC splits it into "形態素" and "解析".
Tokenize with embedded IPADIC NEologd
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://ipadic-neologd
日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
形態素解析 名詞,固有名詞,一般,*,*,*,形態素解析,ケイタイソカイセキ,ケイタイソカイセキ
を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
行う 動詞,自立,*,*,五段・ワ行促音便,基本形,行う,オコナウ,オコナウ
こと 名詞,非自立,一般,*,*,*,こと,コト,コト
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
でき 動詞,自立,*,*,一段,連用形,できる,デキ,デキ
ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。 記号,句点,*,*,*,*,。,。,。
EOS
NOTE: To include IPADIC NEologd dictionary in the binary, you must build with the --features=embed-ipadic-neologd option.
Tokenize with user dictionary (CSV format)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict embedded://ipadic-neologd \
--user-dict ./resources/user_dict/ipadic_simple_userdic.csv
東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
Tokenize with user dictionary (binary format)
% echo "東京スカイツリーの最寄り駅はとうきょうスカイツリー駅です" | lindera tokenize \
--dict /tmp/lindera-ipadic-neologd-0.0.7-20200820 \
--user-dict ./resources/user_dict/ipadic_simple_userdic.bin
東京スカイツリー カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリー,*
の 助詞,連体化,*,*,*,*,の,ノ,ノ
最寄り駅 名詞,一般,*,*,*,*,最寄り駅,モヨリエキ,モヨリエキ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
とうきょうスカイツリー駅 カスタム名詞,*,*,*,*,*,*,トウキョウスカイツリーエキ,*
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
EOS
Rust API example
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ipadic-neologd")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "日本語の形態素解析を行うことができます。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera UniDic
Lindera UniDic is a Japanese dictionary crate based on UniDic, which uses uniform word unit definitions. UniDic provides more detailed morphological information than IPADIC, with 21 fields per entry.
Contents
- Dictionary Format -- Field definitions for system and user dictionaries
- Build -- How to build the dictionary from source
- Examples -- Tokenization examples
API Reference
Lindera UniDic
Dictionary version
This repository contains unidic-mecab.
Dictionary format
Refer to the manual for details on the unidic-mecab dictionary format and part-of-speech tags.
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞大分類 | Part-of-speech | |
| 5 | 品詞中分類 | Part-of-speech subcategory 1 | |
| 6 | 品詞小分類 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類 | Part-of-speech subcategory 3 | |
| 8 | 活用型 | Conjugation type | |
| 9 | 活用形 | Conjugation form | |
| 10 | 語彙素読み | Reading | |
| 11 | 語彙素(語彙素表記 + 語彙素細分類) | Lexeme | |
| 12 | 書字形出現形 | Orthographic surface form | |
| 13 | 発音形出現形 | Phonological surface form | |
| 14 | 書字形基本形 | Orthographic base form | |
| 15 | 発音形基本形 | Phonological base form | |
| 16 | 語種 | Word type | |
| 17 | 語頭変化型 | Initial mutation type | |
| 18 | 語頭変化形 | Initial mutation form | |
| 19 | 語末変化型 | Final mutation type | |
| 20 | 語末変化形 | Final mutation form |
User dictionary format (CSV)
Simple version
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 品詞大分類 | Part-of-speech | |
| 2 | 語彙素読み | Reading |
Detailed version
| Index | Name (Japanese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表層形 | Surface | |
| 1 | 左文脈ID | Left context ID | |
| 2 | 右文脈ID | Right context ID | |
| 3 | コスト | Cost | |
| 4 | 品詞大分類 | Part-of-speech | |
| 5 | 品詞中分類 | Part-of-speech subcategory 1 | |
| 6 | 品詞小分類 | Part-of-speech subcategory 2 | |
| 7 | 品詞細分類 | Part-of-speech subcategory 3 | |
| 8 | 活用型 | Conjugation type | |
| 9 | 活用形 | Conjugation form | |
| 10 | 語彙素読み | Reading | |
| 11 | 語彙素(語彙素表記 + 語彙素細分類) | Lexeme | |
| 12 | 書字形出現形 | Orthographic surface form | |
| 13 | 発音形出現形 | Phonological surface form | |
| 14 | 書字形基本形 | Orthographic base form | |
| 15 | 発音形基本形 | Phonological base form | |
| 16 | 語種 | Word type | |
| 17 | 語頭変化型 | Initial mutation type | |
| 18 | 語頭変化形 | Initial mutation form | |
| 19 | 語末変化型 | Final mutation type | |
| 20 | 語末変化形 | Final mutation form | |
| 21 | - | - | After 21, it can be freely expanded. |
API reference
The API reference is available. Please see following URL:
Build
This page describes how to build the UniDic dictionary from source files.
Build system dictionary
Download the UniDic source files and build the dictionary:
% curl -L -o /tmp/unidic-mecab-2.1.2.tar.gz "https://Lindera.dev/unidic-mecab-2.1.2.tar.gz"
% tar zxvf /tmp/unidic-mecab-2.1.2.tar.gz -C /tmp
% lindera build \
--src /tmp/unidic-mecab-2.1.2 \
--dest /tmp/lindera-unidic-2.1.2 \
--metadata ./lindera-unidic/metadata.json \
--context-id-freq ./lindera-unidic/context_id_freq.txt
[!TIP]
lindera-unidic/metadata.jsonsetsconnection_id_mapping: true, so the builder relabels the connection-cost matrix's context IDs by access frequency to improve cache locality when looking up connection costs. Passing--context-id-freq/-fwith the bundledcontext_id_freq.txthistogram gives this remapping real corpus frequency data to rank IDs by. Omitting the flag silently falls back to a much weaker entry-count-based proxy instead of failing, so the build still succeeds but without the full benefit. Either way, tokenization output is unaffected -- the remap is a bijective relabeling that only changes a build-time optimization, never correctness.
Build user dictionary
Build a user dictionary from a CSV file:
% lindera build \
--src ./resources/user_dict/unidic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-unidic/metadata.json \
--user
For more details about user dictionary format, see Dictionary Format.
Embedding in binary
To embed the UniDic dictionary directly into the binary:
cargo build --features=embed-unidic
This allows using embedded://unidic as the dictionary path without external dictionary files.
Examples
This page shows tokenization examples using the UniDic dictionary.
Tokenize with external UniDic
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict /tmp/lindera-unidic-2.1.2
日本 名詞,固有名詞,地名,国,*,*,ニッポン,日本,日本,ニッポン,日本,ニッポン,固,*,*,*,*
語 名詞,普通名詞,一般,*,*,*,ゴ,語,語,ゴ,語,ゴ,漢,*,*,*,*
の 助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*
形態 名詞,普通名詞,一般,*,*,*,ケイタイ,形態,形態,ケータイ,形態,ケータイ,漢,*,*,*,*
素 接尾辞,名詞的,一般,*,*,*,ソ,素,素,ソ,素,ソ,漢,*,*,*,*
解析 名詞,普通名詞,サ変可能,*,*,*,カイセキ,解析,解析,カイセキ,解析,カイセキ,漢,*,*,*,*
を 助詞,格助詞,*,*,*,*,ヲ,を,を,オ,を,オ,和,*,*,*,*
行う 動詞,一般,*,*,五段-ワア行,連体形-一般,オコナウ,行う,行う,オコナウ,行う,オコナウ,和,*,*,*,*
こと 名詞,普通名詞,一般,*,*,*,コト,事,こと,コト,こと,コト,和,コ濁,基本形,*,*
が 助詞,格助詞,*,*,*,*,ガ,が,が,ガ,が,ガ,和,*,*,*,*
でき 動詞,非自立可能,*,*,上一段-カ行,連用形-一般,デキル,出来る,でき,デキ,できる,デキル,和,*,*,*,*
ます 助動詞,*,*,*,助動詞-マス,終止形-一般,マス,ます,ます,マス,ます,マス,和,*,*,*,*
。 補助記号,句点,*,*,*,*,,。,。,,。,,記号,*,*,*,*
EOS
Notice that UniDic splits "日本語" into "日本" and "語", and "形態素" into "形態" and "素", reflecting its uniform word unit definitions.
Tokenize with embedded UniDic
% echo "日本語の形態素解析を行うことができます。" | lindera tokenize \
--dict embedded://unidic
日本 名詞,固有名詞,地名,国,*,*,ニッポン,日本,日本,ニッポン,日本,ニッポン,固,*,*,*,*
語 名詞,普通名詞,一般,*,*,*,ゴ,語,語,ゴ,語,ゴ,漢,*,*,*,*
の 助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*
形態 名詞,普通名詞,一般,*,*,*,ケイタイ,形態,形態,ケータイ,形態,ケータイ,漢,*,*,*,*
素 接尾辞,名詞的,一般,*,*,*,ソ,素,素,ソ,素,ソ,漢,*,*,*,*
解析 名詞,普通名詞,サ変可能,*,*,*,カイセキ,解析,解析,カイセキ,解析,カイセキ,漢,*,*,*,*
を 助詞,格助詞,*,*,*,*,ヲ,を,を,オ,を,オ,和,*,*,*,*
行う 動詞,一般,*,*,五段-ワア行,連体形-一般,オコナウ,行う,行う,オコナウ,行う,オコナウ,和,*,*,*,*
こと 名詞,普通名詞,一般,*,*,*,コト,事,こと,コト,こと,コト,和,コ濁,基本形,*,*
が 助詞,格助詞,*,*,*,*,ガ,が,が,ガ,が,ガ,和,*,*,*,*
でき 動詞,非自立可能,*,*,上一段-カ行,連用形-一般,デキル,出来る,でき,デキ,できる,デキル,和,*,*,*,*
ます 助動詞,*,*,*,助動詞-マス,終止形-一般,マス,ます,ます,マス,ます,マス,和,*,*,*,*
。 補助記号,句点,*,*,*,*,,。,。,,。,,記号,*,*,*,*
EOS
NOTE: To include UniDic dictionary in the binary, you must build with the --features=embed-unidic option.
Rust API example
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://unidic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "日本語の形態素解析を行うことができます。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera ko-dic
Lindera ko-dic is a Korean dictionary crate based on mecab-ko-dic.
Contents
- Dictionary Format -- Field definitions for system and user dictionaries
- Build -- How to build the dictionary from source
- Examples -- Tokenization examples
API Reference
Lindera ko-dic
Dictionary version
This repository contains mecab-ko-dic.
Dictionary format
Information about the dictionary format and part-of-speech tags used by mecab-ko-dic id documented in this Google Spreadsheet, linked to from mecab-ko-dic's repository readme.
Note how ko-dic has one less feature column than NAIST JDIC, and has an altogether different set of information (e.g. doesn't provide the "original form" of the word).
The tags are a slight modification of those specified by 세종 (Sejong), whatever that is. The mappings from Sejong to mecab-ko-dic's tag names are given in tab 태그 v2.0 on the above-linked spreadsheet.
The dictionary format is specified fully (in Korean) in tab 사전 형식 v2.0 of the spreadsheet. Any blank values default to *.
| Index | Name (Korean) | Name (English) | Notes |
|---|---|---|---|
| 0 | 표면 | Surface | |
| 1 | 왼쪽 문맥 ID | Left context ID | |
| 2 | 오른쪽 문맥 ID | Right context ID | |
| 3 | 비용 | Cost | |
| 4 | 품사 태그 | Part-of-speech tag | See 태그 v2.0 tab on spreadsheet |
| 5 | 의미 부류 | Meaning | (too few examples for me to be sure) |
| 6 | 종성 유무 | Presence or absence | T for true; F for false; else * |
| 7 | 읽기 | Reading | usually matches surface, but may differ for foreign words e.g. Chinese character words |
| 8 | 타입 | Type | One of: Inflect (활용); Compound (복합명사); or Preanalysis (기분석) |
| 9 | 첫번째 품사 | First part-of-speech | e.g. given a part-of-speech tag of "VV+EM+VX+EP", would return VV |
| 10 | 마지막 품사 | Last part-of-speech | e.g. given a part-of-speech tag of "VV+EM+VX+EP", would return EP |
| 11 | 표현 | Expression | 활용, 복합명사, 기분석이 어떻게 구성되는지 알려주는 필드 -- Fields that tell how usage, compound nouns, and key analysis are organized |
User dictionary format (CSV)
Simple version
| Index | Name (Korean) | Name (English) | Notes |
|---|---|---|---|
| 0 | 표면 | Surface | |
| 1 | 품사 태그 | part-of-speech tag | See 태그 v2.0 tab on spreadsheet |
| 2 | 읽기 | reading | usually matches surface, but may differ for foreign words e.g. Chinese character words |
Detailed version
| Index | Name (Korean) | Name (English) | Notes |
|---|---|---|---|
| 0 | 표면 | Surface | |
| 1 | 왼쪽 문맥 ID | Left context ID | |
| 2 | 오른쪽 문맥 ID | Right context ID | |
| 3 | 비용 | Cost | |
| 4 | 품사 태그 | part-of-speech tag | See 태그 v2.0 tab on spreadsheet |
| 5 | 의미 부류 | meaning | (too few examples for me to be sure) |
| 6 | 종성 유무 | presence or absence | T for true; F for false; else * |
| 7 | 읽기 | reading | usually matches surface, but may differ for foreign words e.g. Chinese character words |
| 8 | 타입 | type | One of: Inflect (활용); Compound (복합명사); or Preanalysis (기분석) |
| 9 | 첫번째 품사 | first part-of-speech | e.g. given a part-of-speech tag of "VV+EM+VX+EP", would return VV |
| 10 | 마지막 품사 | last part-of-speech | e.g. given a part-of-speech tag of "VV+EM+VX+EP", would return EP |
| 11 | 표현 | expression | 활용, 복합명사, 기분석이 어떻게 구성되는지 알려주는 필드 -- Fields that tell how usage, compound nouns, and key analysis are organized |
| 12 | - | - | After 12, it can be freely expanded. |
API reference
The API reference is available. Please see following URL:
Build
Build system dictionary
Download and extract the mecab-ko-dic source files, then build the dictionary:
% curl -L -o /tmp/mecab-ko-dic-2.1.1-20180720.tar.gz "https://Lindera.dev/mecab-ko-dic-2.1.1-20180720.tar.gz"
% tar zxvf /tmp/mecab-ko-dic-2.1.1-20180720.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-ko-dic-2.1.1-20180720 \
--dest /tmp/lindera-ko-dic-2.1.1-20180720 \
--metadata ./lindera-ko-dic/metadata.json
Build user dictionary
% lindera build \
--src ./resources/user_dict/ko-dic_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-ko-dic/metadata.json \
--user
Embedding the dictionary
To embed the ko-dic dictionary directly into the binary, build with the following feature flag:
% cargo build --features=embed-ko-dic
Examples
Tokenize with external ko-dic
% echo "한국어의형태해석을실시할수있습니다." | lindera tokenize \
--dict /tmp/lindera-ko-dic-2.1.1-20180720
한국어 NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
의 JKG,*,F,의,*,*,*,*
형태 NNG,*,F,형태,*,*,*,*
해석 NNG,행위,T,해석,*,*,*,*
을 JKO,*,T,을,*,*,*,*
실시 NNG,행위,F,실시,*,*,*,*
할 XSV+ETM,*,T,할,Inflect,XSV,ETM,하/XSV/*+ᆯ/ETM/*
수 NNB,*,F,수,*,*,*,*
있 VV,*,T,있,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*
. SF,*,*,*,*,*,*,*
EOS
Tokenize with embedded ko-dic
% echo "한국어의형태해석을실시할수있습니다." | lindera tokenize \
--dict embedded://ko-dic
한국어 NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
의 JKG,*,F,의,*,*,*,*
형태 NNG,*,F,형태,*,*,*,*
해석 NNG,행위,T,해석,*,*,*,*
을 JKO,*,T,을,*,*,*,*
실시 NNG,행위,F,실시,*,*,*,*
할 XSV+ETM,*,T,할,Inflect,XSV,ETM,하/XSV/*+ᆯ/ETM/*
수 NNB,*,F,수,*,*,*,*
있 VV,*,T,있,*,*,*,*
습니다 EF,*,F,습니다,*,*,*,*
. SF,*,*,*,*,*,*,*
EOS
NOTE: To include ko-dic dictionary in the binary, you must build with the --features=embed-ko-dic option.
Rust API example
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://ko-dic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "한국어의형태해석을실시할수있습니다."; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera CC-CEDICT
Lindera CC-CEDICT is a Chinese dictionary crate based on CC-CEDICT-MeCab.
Contents
- Dictionary Format -- Field definitions for system and user dictionaries
- Build -- How to build the dictionary from source
- Examples -- Tokenization examples
API Reference
Lindera CC-CE-DICT
Dictionary version
This repository contains CC-CEDICT-MeCab.
Dictionary format
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 左语境ID | Left context ID | |
| 2 | 右语境ID | Right context ID | |
| 3 | 成本 | Cost | |
| 4 | 词类 | Part-of-speech | |
| 5 | 词类1 | Part-of-speech subcategory 1 | |
| 6 | 词类2 | Part-of-speech subcategory 2 | |
| 7 | 词类3 | Part-of-speech subcategory 3 | |
| 8 | 併音 | Pinyin | |
| 9 | 繁体字 | Traditional | |
| 10 | 簡体字 | Simplified | |
| 11 | 定义 | Definition |
User dictionary format (CSV)
Simple version
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 词类 | Part-of-speech | |
| 2 | 併音 | Pinyin |
Detailed version
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 左语境ID | Left context ID | |
| 2 | 右语境ID | Right context ID | |
| 3 | 成本 | Cost | |
| 4 | 词类 | Part-of-speech | |
| 5 | 词类1 | Part-of-speech subcategory 1 | |
| 6 | 词类2 | Part-of-speech subcategory 2 | |
| 7 | 词类3 | Part-of-speech subcategory 3 | |
| 8 | 併音 | Pinyin | |
| 9 | 繁体字 | Traditional | |
| 10 | 簡体字 | Simplified | |
| 11 | 定义 | Definition | |
| 12 | - | - | After 12, it can be freely expanded. |
API reference
The API reference is available. Please see following URL:
Build
Build system dictionary
Download and extract the CC-CEDICT-MeCab source files, then build the dictionary:
% curl -L -o /tmp/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz "https://lindera.dev/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz"
% tar zxvf /tmp/CC-CEDICT-MeCab-0.1.0-20200409.tar.gz -C /tmp
% lindera build \
--src /tmp/CC-CEDICT-MeCab-0.1.0-20200409 \
--dest /tmp/lindera-cc-cedict-0.1.0-20200409 \
--metadata ./lindera-cc-cedict/metadata.json
Build user dictionary
% lindera build \
--src ./resources/user_dict/cc-cedict_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-cc-cedict/metadata.json \
--user
Embedding the dictionary
To embed the CC-CEDICT dictionary directly into the binary, build with the following feature flag:
% cargo build --features=embed-cc-cedict
Examples
Tokenize with external CC-CEDICT
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict /tmp/lindera-cc-cedict-0.1.0-20200409
可以 *,*,*,*,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good/
进行 *,*,*,*,jin4 xing2,進行,进行,to advance/to conduct/underway/in progress/to do/to carry out/to carry on/to execute/
中文 *,*,*,*,Zhong1 wen2,中文,中文,Chinese language/
形态学 *,*,*,*,xing2 tai4 xue2,形態學,形态学,morphology (in biology or linguistics)/
分析 *,*,*,*,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4]/
。 *,*,*,*,*,*,*,*
EOS
Tokenize with embedded CC-CEDICT
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict embedded://cc-cedict
可以 *,*,*,*,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good/
进行 *,*,*,*,jin4 xing2,進行,进行,to advance/to conduct/underway/in progress/to do/to carry out/to carry on/to execute/
中文 *,*,*,*,Zhong1 wen2,中文,中文,Chinese language/
形态学 *,*,*,*,xing2 tai4 xue2,形態學,形态学,morphology (in biology or linguistics)/
分析 *,*,*,*,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4]/
。 *,*,*,*,*,*,*,*
EOS
NOTE: To include CC-CEDICT dictionary in the binary, you must build with the --features=embed-cc-cedict option.
Rust API example
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://cc-cedict")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "可以进行中文形态学分析。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Lindera Jieba
Lindera Jieba is a Chinese dictionary crate based on mecab-jieba.
Contents
- Dictionary Format -- Field definitions for system and user dictionaries
- Build -- How to build the dictionary from source
- Examples -- Tokenization examples
API Reference
Lindera Jieba
Dictionary version
This repository contains mecab-jieba.
Dictionary format
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 左语境ID | Left context ID | |
| 2 | 右语境ID | Right context ID | |
| 3 | 成本 | Cost | |
| 4 | 词类 | Part-of-speech | |
| 5 | 字符类型 | Character type | |
| 6 | 併音 | Pinyin | |
| 7 | 繁体字 | Traditional | |
| 8 | 簡体字 | Simplified | |
| 9 | 定义 | Definition | |
| 10 | 字符数 | Character count | |
| 11 | 首字符 | First character | |
| 12 | 末字符 | Last character | |
| 13 | 频率等级 | Frequency band |
User dictionary format (CSV)
Simple version
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 词类 | Part-of-speech | |
| 2 | 併音 | Pinyin |
Detailed version
| Index | Name (Chinese) | Name (English) | Notes |
|---|---|---|---|
| 0 | 表面形式 | Surface | |
| 1 | 左语境ID | Left context ID | |
| 2 | 右语境ID | Right context ID | |
| 3 | 成本 | Cost | |
| 4 | 词类 | Part-of-speech | |
| 5 | 字符类型 | Character type | |
| 6 | 併音 | Pinyin | |
| 7 | 繁体字 | Traditional | |
| 8 | 簡体字 | Simplified | |
| 9 | 定义 | Definition | |
| 10 | 字符数 | Character count | |
| 11 | 首字符 | First character | |
| 12 | 末字符 | Last character | |
| 13 | 频率等级 | Frequency band | |
| 14 | - | - | After 14, it can be freely expanded. |
API reference
The API reference is available. Please see following URL:
Build
Build system dictionary
Download and extract the mecab-jieba source files, then build the dictionary:
% curl -L -o /tmp/mecab-jieba-0.1.1.tar.gz "https://lindera.dev/mecab-jieba-0.1.1.tar.gz"
% tar zxvf /tmp/mecab-jieba-0.1.1.tar.gz -C /tmp
% lindera build \
--src /tmp/mecab-jieba-0.1.1/dict-src \
--dest /tmp/lindera-jieba-0.1.1 \
--metadata ./lindera-jieba/metadata.json
Build user dictionary
% lindera build \
--src ./resources/user_dict/jieba_simple_userdic.csv \
--dest ./resources/user_dict \
--metadata ./lindera-jieba/metadata.json \
--user
Embedding the dictionary
To embed the Jieba dictionary directly into the binary, build with the following feature flag:
% cargo build --features=embed-jieba
Examples
Tokenize with external Jieba
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict /tmp/lindera-jieba-0.1.1
可以 c,CHINESE,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good,2,可,以,high
进行 v,CHINESE,jin4 xing2,進行,进行,(of a process etc) to proceed; to be in progress; to be underway/(of people) to carry out; to conduct (an investigation or discussion etc)/(of an army etc) to be on the march; to advance,2,进,行,high
中文 nz,CHINESE,Zhong1 wen2,中文,中文,Chinese language,2,中,文,high
形态 n,CHINESE,xing2 tai4,形態,形态,shape/form/pattern/morphology,2,形,态,high
学 n,CHINESE,xue2,學,学,to learn/to study/to imitate/science/-ology,1,学,学,high
分析 vn,CHINESE,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4],2,分,析,high
。 w,*,*,*,*,*,*,*,*,*
EOS
Tokenize with embedded Jieba
% echo "可以进行中文形态学分析。" | lindera tokenize \
--dict embedded://jieba
可以 c,CHINESE,ke3 yi3,可以,可以,can/may/possible/able to/not bad/pretty good,2,可,以,high
进行 v,CHINESE,jin4 xing2,進行,进行,(of a process etc) to proceed; to be in progress; to be underway/(of people) to carry out; to conduct (an investigation or discussion etc)/(of an army etc) to be on the march; to advance,2,进,行,high
中文 nz,CHINESE,Zhong1 wen2,中文,中文,Chinese language,2,中,文,high
形态 n,CHINESE,xing2 tai4,形態,形态,shape/form/pattern/morphology,2,形,态,high
学 n,CHINESE,xue2,學,学,to learn/to study/to imitate/science/-ology,1,学,学,high
分析 vn,CHINESE,fen1 xi1,分析,分析,to analyze/analysis/CL:個|个[ge4],2,分,析,high
。 w,*,*,*,*,*,*,*,*,*
EOS
NOTE: To include Jieba dictionary in the binary, you must build with the --features=embed-jieba option.
Rust API example
use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; use lindera_analysis::tokenizer::Tokenizer; use lindera::LinderaResult; fn main() -> LinderaResult<()> { let dictionary = load_dictionary("embedded://jieba")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let text = "可以进行中文形态学分析。"; let mut tokens = tokenizer.tokenize(text)?; for token in tokens.iter_mut() { let details = token.details().join(","); println!("{}\t{}", token.surface.as_ref(), details); } Ok(()) }
Migrating from v3 to v4
Lindera v4.0.0 is a major release that bundles the breaking changes that were deliberately deferred during the v3 series. Each change is small on its own; this guide lists every one so you can upgrade with confidence.
The breaking changes were verified mechanically against a cargo public-api diff of
the v3.0.7 and v4 public surfaces.
Overview
| Change | Affects | What you do |
|---|---|---|
Default schema field names use pos_detail_* | Python, Node.js, Ruby, PHP | Update field names middle_pos / small_pos / fine_pos to pos_detail_1 / pos_detail_2 / pos_detail_3 |
Token.details is always a list | Python, Node.js, Ruby | Remove null / None / nil handling |
Binding Segmenter removed | Python, WASM | Use the tokenizer instead |
LINDERA_CACHE env var removed | Rust build, CLI | Use LINDERA_DICTIONARIES_PATH |
| User-dictionary binary format changed | All (prebuilt .bin) | Rebuild user dictionaries from their CSV source |
lindera-dictionary viterbi internals encapsulated | Rust crate users | Use the new accessors |
The top-level lindera crate's public Rust API is unchanged between v3.0.7 and v4.
Default dictionary schema field names
Schema.create_default() (and the default dictionary schema) now names the three
part-of-speech detail fields pos_detail_1, pos_detail_2, and pos_detail_3
(indices 5, 6, 7), instead of middle_pos, small_pos, and fine_pos. This makes
every binding match the core lindera::dictionary::Schema::default(), which already
used pos_detail_*.
This affects the Python, Node.js, Ruby, and PHP bindings. The WASM binding already
used pos_detail_*, so it is unchanged.
In Python:
schema = Schema.create_default()
# v3: schema.fields[5] == "middle_pos"
# v4: schema.fields[5] == "pos_detail_1"
# v3
index = schema.get_field_index("middle_pos")
# v4
index = schema.get_field_index("pos_detail_1")
If you reference these field names by string anywhere (lookups, custom schemas,
serialized configuration), update them to the pos_detail_* form.
Token.details is always a list
Token.details is now always a list of strings and is never null / None / nil.
A token with no details is represented by an empty list. Previously the Python,
Node.js, and Ruby bindings wrapped it in a nullable type even though it was always
populated in practice; the PHP and WASM bindings were already non-nullable.
In Python the type changes from list[str] | None to list[str]:
# v3 — defensive null check was required by the type
if token.details is not None:
pos = token.details[0]
# v4 — details is always a list
pos = token.details[0]
In Node.js the type changes from Array<string> | null to Array<string>, and in
Ruby from Array | nil to Array. Remove any null / nil checks accordingly.
Binding Segmenter removed
The vestigial Segmenter wrappers were removed from the bindings. They had no
constructor and could not be used; segmentation has always been reachable through the
tokenizer.
- Python: the
lindera.segmentersubmodule andlindera.segmenter.Segmenterare gone. - WASM: the
Segmenterclass export is gone.
Tokenize through the tokenizer instead:
from lindera import Tokenizer, TokenizerBuilder
tokenizer = TokenizerBuilder().build()
tokens = tokenizer.tokenize("関西国際空港")
LINDERA_CACHE environment variable removed
The deprecated LINDERA_CACHE build-time environment variable was removed. Use
LINDERA_DICTIONARIES_PATH, which has been the supported variable for several
releases:
# v3 (deprecated)
export LINDERA_CACHE=/path/to/dicts
# v4
export LINDERA_DICTIONARIES_PATH=/path/to/dicts
User-dictionary binary format changed
User dictionaries now use the same 8-bit variant-count encoding as system
dictionaries (supporting up to 255 variants per surface, previously 31). As a result,
a user-dictionary .bin file built with v3 is decoded incorrectly by v4. There is no
format-version guard, so the failure is silent — tokens are produced, but with the
wrong details.
Rebuild user dictionaries from their CSV source with v4:
lindera build --user \
--src user_dict.csv \
--dest ./build \
--metadata lindera-ipadic/metadata.json
If you load a user dictionary from a .csv file (rather than a prebuilt .bin), it is
rebuilt at load time and no action is needed.
Rust library: lindera-dictionary viterbi internals
This affects only direct users of the lindera-dictionary crate; the lindera crate
API is unchanged.
The internal viterbi structs no longer expose public fields. Use the accessors instead:
#![allow(unused)] fn main() { // v3 — direct field access let id = word_id.id; let cost = word_entry.word_cost; // v4 — accessors let id = word_id.id(); let cost = word_entry.word_cost(); }
Other changes in lindera_dictionary::viterbi:
EdgeTypewas removed.WordEntrygainednew(),word_cost(), andword_id();WordIdgainedid().WordEntry::serialize,WordEntry::deserialize, andWordEntry::SERIALIZED_LENare no longer public.util::read_aligned_fileand theembedded_dictionary!macro were added.
This list was derived from the complete machine-generated cargo public-api diff
for the lindera-dictionary crate between the v3.0.7 and v4 releases.
Upgrade checklist
- Replace
middle_pos/small_pos/fine_poswithpos_detail_1/pos_detail_2/pos_detail_3(Python, Node.js, Ruby, PHP). - Remove
null/None/nilchecks onToken.details(Python, Node.js, Ruby). - Replace any use of the binding
Segmenterwith the tokenizer (Python, WASM). - Replace
LINDERA_CACHEwithLINDERA_DICTIONARIES_PATH. - Rebuild prebuilt user-dictionary
.binfiles from their CSV source. - Switch direct
lindera-dictionaryviterbi field access to the new accessors (Rust).
Migrating from v4 to v5
Lindera v5.0.0 restructures the workspace around a lean core: the default
build of the lindera crate is now a pure morphological segmenter, and the
dictionary-training pipeline lives in its own crate. This guide lists every
breaking change and the one-line fixes for each.
[!NOTE] v5.0.0 is the next planned release and has not been published to crates.io yet; the current published version is
4.0.1. The changes described below already exist on themainbranch, ahead of the version bump.
Overview
| Change | Affects | What you do |
|---|---|---|
Analysis chain moved to the new lindera-analysis crate | Rust users of Tokenizer, character filters, or token filters | Depend on lindera-analysis and update import paths |
lindera-dictionary no longer has a train feature | Direct lindera-dictionary --features train users | Depend on lindera-trainer (or the lindera facade's train feature) |
| Build cache variable renamed | Users setting LINDERA_DICTIONARIES_PATH | Rename it to LINDERA_BUILD_DICTIONARY_CACHE_DIR (the old name still works until v6.0.0) |
The language bindings (Python, Node.js, Ruby, PHP, WASM) and the CLI are unaffected: they enable the required features themselves, and their APIs and output are unchanged. Tokenization output is also unchanged — v5 produces byte-for-byte identical tokens to v4 for the same input and dictionary.
The analysis chain moved to lindera-analysis
In v5.0 the lindera crate is a pure morphological segmenter: the
character_filter, token_filter, and tokenizer modules moved to the new
lindera-analysis crate
(mirroring Lucene's split between a tokenizer core and analyzer modules).
If you use Tokenizer or any filter, depend on the new crate and update the
import paths — the APIs themselves are unchanged:
# v4
[dependencies]
lindera = "4.0"
# v5
[dependencies]
lindera = "5.0"
lindera-analysis = "5.0"
#![allow(unused)] fn main() { // v4 use lindera::tokenizer::Tokenizer; use lindera::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter; // v5 use lindera_analysis::tokenizer::Tokenizer; use lindera_analysis::token_filter::japanese_stop_tags::JapaneseStopTagsTokenFilter; }
If you only segment text, nothing changes in your code — and your dependency tree shrinks (kanaria, unicode-normalization, unicode-segmentation, unicode-blocks, and serde_yaml_ng are no longer built):
#![allow(unused)] fn main() { use std::borrow::Cow; use lindera::dictionary::load_dictionary; use lindera::mode::Mode; use lindera::segmenter::Segmenter; let dictionary = load_dictionary("/path/to/ipadic")?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokens = segmenter.segment(Cow::Borrowed("関西国際空港限定トートバッグ"))?; }
Training moved to the lindera-trainer crate
The CRF training pipeline (TrainerConfig, Trainer, Corpus, Model,
SerializableModel) moved from lindera-dictionary's train-gated trainer
module into the new lindera-trainer crate. As a result,
lindera-dictionary no longer depends on lindera-crf or regex.
Through the lindera facade nothing changes — the train feature now
pulls lindera-trainer and re-exports it under the same path:
#![allow(unused)] fn main() { // Works in both v4 and v5 (with the `train` feature): use lindera::dictionary::trainer::{Corpus, Trainer, TrainerConfig}; }
Only direct users of lindera-dictionary --features train need to switch:
# v4
[dependencies]
lindera-dictionary = { version = "4.0", features = ["train"] }
# v5
[dependencies]
lindera-dictionary = "5.0"
lindera-trainer = "5.0"
#![allow(unused)] fn main() { // v4 use lindera_dictionary::trainer::{Corpus, Trainer, TrainerConfig}; // v5 use lindera_trainer::{Corpus, Trainer, TrainerConfig}; }
The lindera train → lindera export → lindera build CLI workflow is
unchanged.
Build cache environment variable renamed
The build-time dictionary cache variable LINDERA_DICTIONARIES_PATH is
renamed to LINDERA_BUILD_DICTIONARY_CACHE_DIR to make its contract explicit:
it is read only at build time by the dictionary crates' build scripts, and it
designates an auto-managed cache holding the downloaded dictionary archives
and the built binary dictionaries.
The old name keeps working through v5.x as a deprecated fallback (the new name wins when both are set) and will be removed in v6.0.0.
# v4
export LINDERA_DICTIONARIES_PATH=/path/to/cache
# v5
export LINDERA_BUILD_DICTIONARY_CACHE_DIR=/path/to/cache
Development Guide
This section provides information for developers who want to build, test, or contribute to Lindera.
- Build & Test -- Build commands, test execution, and quality checks
- Feature Flags -- Available feature flags and their effects
- Project Structure -- Crate layout and module organization
- Training Pipeline -- CRF-based dictionary training workflow
- Contributing -- Guidelines for contributors
Build & Test
Build
Default Build
Build the workspace with default features (mmap):
cargo build
Build with Training Support
Include CRF-based dictionary training functionality:
cargo build --features train
Build CLI Only
cargo build -p lindera-cli
The CLI has the train feature enabled by default.
Test
Single Test
Run a specific test within a crate (recommended for development):
cargo test -p <crate> <test_name>
Training Feature Tests
cargo test -p lindera-trainer
All Features for a Crate
Run the full test suite for a single crate (matches CI):
cargo test -p <crate> --all-features
Workspace-Wide Tests
cargo test
Quality Checks
Format Check
Verify code formatting matches the project style:
cargo fmt --all -- --check
To auto-fix formatting:
cargo fmt --all
Lint
Run Clippy with warnings treated as errors:
cargo clippy -- -D warnings
Documentation
API Documentation
Generate and open Rust API documentation:
cargo doc --no-deps --open
mdBook Documentation
Build the user-facing documentation:
mdbook build docs
Preview locally at http://localhost:3000:
mdbook serve docs
Markdown Lint
Check documentation for Markdown style issues:
markdownlint-cli2 "docs/src/**/*.md"
Rules are configured in .markdownlint.json at the repository root.
Feature Flags
Lindera uses Cargo feature flags to control optional functionality and dictionary embedding.
Core Features
| Feature | Description | Default |
|---|---|---|
mmap | Memory-mapped file support | Yes |
train | CRF-based dictionary training (depends on lindera-trainer) | CLI only |
mmapis enabled by default in the mainlinderacrate.- The analysis chain (character filters, token filters, and the
Tokenizer) is not a feature of this crate: as of v5.0 it lives in the companionlindera-analysiscrate. Thelinderacrate itself is a pure segmenter around theSegmenterAPI. trainis enabled by default only inlindera-cli. For library usage, enable it explicitly with--features train.
Using External Dictionaries (Recommended)
The recommended approach is to use pre-built dictionaries as external files. Download a dictionary from GitHub Releases and specify its path at runtime:
#![allow(unused)] fn main() { let dictionary = load_dictionary("/path/to/ipadic")?; }
No additional feature flags are required for this usage.
Dictionary Embedding Features (Advanced)
These features embed pre-built dictionaries directly into the binary, eliminating the need for external dictionary files at runtime. This is intended for advanced users who need self-contained binaries.
| Feature | Dictionary | Language |
|---|---|---|
embed-ipadic | IPADIC | Japanese |
embed-ipadic-neologd | IPADIC NEologd | Japanese |
embed-unidic | UniDic | Japanese |
embed-ko-dic | ko-dic | Korean |
embed-cc-cedict | CC-CEDICT | Chinese |
embed-jieba | Jieba | Chinese |
None of these are enabled by default. Enable them as needed:
[dependencies]
lindera = { version = "5.0", features = ["embed-ipadic"] }
When embedding is enabled, you can load the dictionary with:
#![allow(unused)] fn main() { let dictionary = load_dictionary("embedded://ipadic")?; }
Combination Features
These meta-features enable multiple dictionaries at once for multilingual applications.
| Feature | Included Dictionaries |
|---|---|
embed-cjk | IPADIC + ko-dic + Jieba |
embed-cjk2 | UniDic + ko-dic + Jieba |
embed-cjk3 | IPADIC NEologd + ko-dic + Jieba |
Combining Feature Flags
Multiple feature flags can be combined. For example, to embed both Japanese and Korean dictionaries:
[dependencies]
lindera = { version = "5.0", features = ["embed-ipadic", "embed-ko-dic"] }
Or from the command line:
cargo build --features embed-ipadic,embed-ko-dic
Notes
- Embedding dictionaries increases binary size significantly. Only embed dictionaries you actually need.
- The
trainfeature adds a dependency onlindera-crfand increases compile time. It is not needed for tokenization-only use cases. - The
mmapfeature enables memory-mapped dictionary loading for filesystem-based dictionaries, requested via--mmap(CLI) or theuse_mmapsegmenter config key. It only avoids eagerly reading the largest word-list files (dict.vals/dict.wordsidx/dict.words) into memory; the connection-cost matrix and the double-array trie are always fully materialized regardless. It has no effect on embedded dictionaries.
Project Structure
Lindera is organized as a Cargo workspace with multiple crates.
Directory Layout
lindera/
├── lindera-crf/ # CRF engine (pure Rust, no_std)
├── lindera-dictionary/ # Dictionary base library
├── lindera-trainer/ # CRF-based dictionary training
├── lindera/ # Core morphological segmentation library
├── lindera-analysis/ # Analysis chain (character/token filters, tokenizer)
├── lindera-cli/ # CLI tool
├── lindera-binding-core/ # FFI-independent helpers shared by the language bindings
├── lindera-ipadic/ # IPADIC dictionary (Japanese)
├── lindera-ipadic-neologd/ # IPADIC NEologd dictionary (Japanese)
├── lindera-unidic/ # UniDic dictionary (Japanese)
├── lindera-ko-dic/ # ko-dic dictionary (Korean)
├── lindera-cc-cedict/ # CC-CEDICT dictionary (Chinese)
├── lindera-jieba/ # Jieba dictionary (Chinese)
├── lindera-python/ # Python bindings (PyO3)
├── lindera-nodejs/ # Node.js bindings (NAPI-RS)
├── lindera-ruby/ # Ruby bindings (Magnus + rb-sys)
├── lindera-php/ # PHP bindings (ext-php-rs)
├── lindera-wasm/ # WebAssembly bindings (wasm-bindgen)
├── resources/ # Test resources and sample data
├── docs/ # Documentation (mdBook)
└── examples/ # Example code
Crate Descriptions
Core Crates
lindera-crf
Pure Rust implementation of Conditional Random Fields (CRF). Supports no_std environments. Uses rkyv for fast zero-copy serialization. This crate provides the statistical learning engine used in dictionary training.
lindera-dictionary
Base library for dictionary handling: loading, building, and querying dictionaries.
lindera-trainer
CRF training pipeline for creating custom dictionaries. Builds on lindera-dictionary runtime types and the lindera-crf engine. Consumed through the lindera facade's train feature (re-exported as lindera::dictionary::trainer).
| Module | Role |
|---|---|
config.rs | Configuration management (seed dict, char.def, feature.def, rewrite.def) |
corpus.rs | Training corpus processing |
feature_extractor.rs | Feature template parsing and feature ID management |
feature_rewriter.rs | MeCab-compatible feature rewriting (3-section format) |
model.rs | Trained model storage, serialization, and dictionary output |
lindera
The main morphological segmentation library. Integrates dictionary crates and provides the Segmenter API.
lindera-analysis
Lucene-style analysis chain on top of lindera: character filters, token filters, and the Tokenizer that composes them around a Segmenter.
lindera-cli
Command-line interface for tokenization, dictionary training, export, and building. The train feature is enabled by default.
lindera-binding-core
FFI-independent helpers shared by all five language bindings (lindera-python, lindera-nodejs, lindera-ruby, lindera-php, lindera-wasm): a core tokenizer/schema/metadata layer that each binding wraps in its own language-native API.
Dictionary Crates
Each dictionary crate contains pre-built dictionary data for a specific language and dictionary source.
| Crate | Language | Dictionary Source |
|---|---|---|
lindera-ipadic | Japanese | IPADIC |
lindera-ipadic-neologd | Japanese | IPADIC NEologd (extended vocabulary) |
lindera-unidic | Japanese | UniDic |
lindera-ko-dic | Korean | ko-dic |
lindera-cc-cedict | Chinese | CC-CEDICT |
lindera-jieba | Chinese | Jieba |
Bindings
lindera-python
Python bindings built with PyO3. Exposes the Lindera tokenizer API to Python applications.
lindera-nodejs
Node.js bindings built with NAPI-RS. Exposes the Lindera tokenizer API to Node.js applications.
lindera-ruby
Ruby bindings built with Magnus and rb-sys. Exposes the Lindera tokenizer API as a Ruby gem.
lindera-php
PHP bindings built with ext-php-rs. Exposes the Lindera tokenizer API as a PHP extension.
lindera-wasm
WebAssembly bindings built with wasm-bindgen. Enables tokenization in browsers and Node.js.
Other Directories
resources/
Test resources including sample dictionaries, user dictionaries, and test corpora used by the test suite.
docs/
User-facing documentation built with mdBook. The table of contents is defined in docs/src/SUMMARY.md. A Japanese translation is available under docs/ja/.
examples/
Runnable example programs demonstrating common usage patterns. Run with:
cargo run --features=embed-ipadic --example=<example_name>
Training Pipeline
Lindera provides CRF-based dictionary training functionality for creating custom morphological analysis models. This feature requires the train feature flag.
Overview
The training pipeline follows three stages:
lindera train --> model.dat --> lindera export --> dictionary files --> lindera build --> compiled dictionary
- Train: Learn CRF weights from an annotated corpus and seed dictionary, producing a binary model file.
- Export: Convert the trained model into Lindera dictionary source files.
- Build: Compile the source files into a binary dictionary that Lindera can load at runtime.
Required Input Files
1. Seed Lexicon (seed.csv)
Base vocabulary dictionary in MeCab CSV format.
外国,0,0,0,名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人,0,0,0,名詞,接尾,一般,*,*,*,人,ジン,ジン
参政,0,0,0,名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
Each line contains: surface,left_id,right_id,cost,pos,pos_detail1,pos_detail2,pos_detail3,inflection_type,inflection_form,base_form,reading,pronunciation
The left_id, right_id, and cost fields are set to 0 in the seed dictionary -- the trainer will compute appropriate values from the CRF model.
2. Training Corpus (corpus.txt)
Annotated text data in tab-separated format. Each line is surface<TAB>pos_info, and sentences are separated by EOS.
外国 名詞,一般,*,*,*,*,外国,ガイコク,ガイコク
人 名詞,接尾,一般,*,*,*,人,ジン,ジン
参政 名詞,サ変接続,*,*,*,*,参政,サンセイ,サンセイ
権 名詞,接尾,一般,*,*,*,権,ケン,ケン
EOS
これ 連体詞,*,*,*,*,*,これ,コレ,コレ
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
テスト 名詞,サ変接続,*,*,*,*,テスト,テスト,テスト
EOS
Training quality depends heavily on the quantity and quality of this corpus.
3. Character Definition (char.def)
Defines character type categories and Unicode code point ranges.
# Category definition: category_name compatibility_flag continuity_flag length
DEFAULT 0 1 0
HIRAGANA 1 1 0
KATAKANA 1 1 0
KANJI 0 0 2
ALPHA 1 1 0
NUMERIC 1 1 0
# Character range mapping
0x3041..0x3096 HIRAGANA # Hiragana
0x30A1..0x30F6 KATAKANA # Katakana
0x4E00..0x9FAF KANJI # Kanji
0x0030..0x0039 NUMERIC # Numbers
0x0041..0x005A ALPHA # Uppercase letters
0x0061..0x007A ALPHA # Lowercase letters
Parameters control how unknown words of each character type are segmented: compatibility with adjacent characters, whether runs of the same type continue as a single token, and default token length.
4. Unknown Word Definition (unk.def)
Defines how out-of-vocabulary words are handled by character type.
DEFAULT,0,0,0,名詞,一般,*,*,*,*,*,*,*
HIRAGANA,0,0,0,名詞,一般,*,*,*,*,*,*,*
KATAKANA,0,0,0,名詞,一般,*,*,*,*,*,*,*
KANJI,0,0,0,名詞,一般,*,*,*,*,*,*,*
ALPHA,0,0,0,名詞,固有名詞,一般,*,*,*,*,*,*
NUMERIC,0,0,0,名詞,数,*,*,*,*,*,*,*
5. Feature Template (feature.def)
MeCab-compatible feature extraction patterns that define what information the CRF model uses for learning.
# Unigram features (word-level)
UNIGRAM U00:%F[0] # POS
UNIGRAM U01:%F[0],%F?[1] # POS + POS detail (%F?[n] = optional, skipped if *)
UNIGRAM U02:%F[6] # Base form
UNIGRAM U03:%w # Surface form
# Bigram features (context combination)
BIGRAM B00:%L[0]/%R[0] # Left POS / Right POS
BIGRAM B01:%L[0],%L[1]/%R[0],%R[1] # Left POS detail / Right POS detail
Template variables:
| Variable | Description |
|---|---|
%F[n] / %F?[n] | Feature field at index n (? = optional, skipped if value is *) |
%L[n] | Left context feature field (from rewrite.def left section) |
%R[n] | Right context feature field (from rewrite.def right section) |
%w | Surface form of the word |
%u | Unigram rewritten feature string |
%l | Left rewritten feature string |
%r | Right rewritten feature string |
6. Feature Rewrite Rules (rewrite.def)
Feature normalization rules in MeCab-compatible 3-section format. Sections are separated by blank lines.
# Section 1: Unigram rewrite rules
名詞,固有名詞,* 名詞,固有名詞
助動詞,*,*,*,特殊・デス 助動詞
* *
# Section 2: Left context rewrite rules
名詞,固有名詞,* 名詞,固有名詞
助詞,* 助詞
* *
# Section 3: Right context rewrite rules
名詞,固有名詞,* 名詞,固有名詞
助詞,* 助詞
* *
Each line is pattern<TAB>replacement. Patterns use * as a wildcard and are matched by prefix. The first matching rule in each section is applied. Different rules can be applied to unigram, left context, and right context independently, enabling fine-grained feature normalization to reduce sparsity.
Training Parameters
| Parameter | Description | Default |
|---|---|---|
lambda | Regularization coefficient (controls overfitting) | 0.01 |
regularization | Regularization type: l1, l2, or elasticnet | l1 |
elastic-net-l1-ratio | L1 ratio for Elastic Net regularization (0.0-1.0, only used with --regularization elasticnet) | 0.5 |
max-iterations | Maximum number of training iterations | 100 |
max-threads | Number of parallel processing threads | CPU core count |
CLI Usage
Train
lindera train \
--seed seed.csv \
--corpus corpus.txt \
--char-def char.def \
--unk-def unk.def \
--feature-def feature.def \
--rewrite-def rewrite.def \
--lambda 0.01 \
--max-iterations 100 \
--max-threads 4 \
--output model.dat
Export
Convert the trained model into dictionary source files:
lindera export \
--model model.dat \
--metadata metadata.json \
--output ./dict-source
This produces the following files:
| File | Description |
|---|---|
lex.csv | Lexicon with trained costs |
matrix.def | Connection cost matrix |
unk.def | Unknown word definition |
char.def | Character definition |
feature.def | Feature template |
rewrite.def | Feature rewrite rules |
left-id.def | Left context ID mapping |
right-id.def | Right context ID mapping |
metadata.json | Dictionary metadata |
Build
Compile the exported source files into a binary dictionary:
lindera build \
--src ./dict-source \
--dest ./dict-compiled \
--metadata ./dict-source/metadata.json
Output Model Format
The trained model is serialized in rkyv binary format for fast loading. It contains:
- Feature weights learned by the CRF
- Label set (vocabulary entries)
- Part-of-speech information
- Feature templates
- Training metadata (regularization, iterations, feature/label counts)
API Usage
For the full lindera-trainer API surface, see Lindera Trainer Architecture and API Reference.
#![allow(unused)] fn main() { use std::fs::File; use lindera_trainer::{Corpus, Trainer, TrainerConfig}; // Load configuration from files let seed_file = File::open("resources/training/seed.csv")?; let char_file = File::open("resources/training/char.def")?; let unk_file = File::open("resources/training/unk.def")?; let feature_file = File::open("resources/training/feature.def")?; let rewrite_file = File::open("resources/training/rewrite.def")?; let config = TrainerConfig::from_readers( seed_file, char_file, unk_file, feature_file, rewrite_file )?; // Initialize and configure trainer let trainer = Trainer::new(config)? .regularization_cost(0.01) .max_iter(100) .num_threads(4); // Load corpus let corpus_file = File::open("resources/training/corpus.txt")?; let corpus = Corpus::from_reader(corpus_file)?; // Execute training let model = trainer.train(corpus)?; // Save model (binary format) let mut output = File::create("trained_model.dat")?; model.write_model(&mut output)?; // Output in Lindera dictionary format let mut lex_out = File::create("output_lex.csv")?; let mut conn_out = File::create("output_conn.dat")?; let mut unk_out = File::create("output_unk.def")?; let mut user_out = File::create("output_user.csv")?; model.write_dictionary(&mut lex_out, &mut conn_out, &mut unk_out, &mut user_out)?; Ok::<(), Box<dyn std::error::Error>>(()) }
Recommended Corpus Specifications
For generating effective dictionaries for real applications:
Corpus Size
| Level | Sentences | Use Case |
|---|---|---|
| Minimum | 100+ | Basic operation verification |
| Recommended | 1,000+ | Practical applications |
| Ideal | 10,000+ | Commercial quality |
Quality Guidelines
- Vocabulary diversity: Balanced distribution of different parts of speech, coverage of inflections and suffixes, appropriate inclusion of technical terms and proper nouns.
- Consistency: Apply analysis criteria consistently across the corpus.
- Verification: Manually verify morphological analysis results. Maintain an error rate below 5%.
Contributing
Thank you for your interest in contributing to Lindera! This page provides guidelines to help you get started.
Getting Started
-
Fork the repository on GitHub.
-
Clone your fork locally:
git clone https://github.com/<your-username>/lindera.git cd lindera -
Create a feature branch:
git checkout -b feature/my-feature -
Make your changes, then verify they pass all checks:
cargo fmt --all -- --check cargo clippy -- -D warnings cargo test -
Commit and push your changes, then open a pull request.
Code Style
- Follow the existing code style in the repository.
- Run
cargo fmtbefore committing. - All public and private items (types, functions, modules, fields, constants, type aliases) must have documentation comments (
///). - Trait implementation methods should also have documentation comments describing implementation-specific behavior.
- Function and method documentation should include
# Argumentsand# Returnssections where applicable. - Code comments, documentation comments, commit messages, log messages, and error messages should be written in English.
- Avoid
unwrap()andexpect()in production code (test code is fine). - Use
unsafeblocks only when necessary, and always include a// SAFETY: ...comment. - Use file-based module style (
src/tokenizer.rs) instead ofmod.rsstyle.
Testing
-
Write unit tests for all new functionality.
-
Run the relevant test(s) during development for fast feedback:
cargo test -p <crate> <test_name> -
When working on training pipeline functionality, run the
lindera-trainercrate's tests:cargo test -p lindera-trainer
Commit Messages
Follow the Conventional Commits specification. Write commit messages in English.
Examples:
feat: add Korean dictionary supportfix: correct character category ID in trainerdocs: update installation instructionsrefactor: split large training method into smaller functions
Documentation
-
If your change affects user-facing documentation, update the relevant files in
docs/src/. -
After editing Markdown files, verify there are no lint errors:
markdownlint-cli2 "docs/src/**/*.md" -
Rules are configured in
.markdownlint.jsonat the repository root.
Dependencies
When adding new dependencies, verify license compatibility. Lindera uses the MIT / Apache-2.0 dual license.
Feature Flags
Use #[cfg(feature = "train")] for conditional compilation of training-related code. See Feature Flags for a full list.
Reporting Issues
When reporting a bug, please include:
- Lindera version (
lindera --versionor checkCargo.toml) - Rust version (
rustc --version) - Operating system
- Steps to reproduce the issue
- Expected and actual behavior