CLI tool
polm/cutlet avatar
polm/cutlet

cutlet transliterates on top of the tokenizer you already use

Japanese to romaji converter in Python

387 stars27 forksPythonMIT

At a glance

What is it?
A Japanese to romaji converter that delegates segmentation to fugashi, so it runs against whichever MeCab dictionary your tokeniser already has, and ships Modified Hepburn, Kunrei and Nihon rather than traditional Hepburn. Foreign spelling comes from the dictionary, which is why カツカレー comes out as cutlet curry unless you switch it off.
Who is it for?
Adopt cutlet if your pipeline already tokenises Japanese with fugashi, because sharing the dictionary removes the main cost of a transliteration step and makes the romaji token-aligned with the segmentation you were going to use anyway. Do not adopt it if you need traditional Hepburn, n-to-m for words like Shimbashi, macrons, or passport romanisation, because the documentation lists all three as unsupported and only the last can be patched with an exception.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 33 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Segmentation is delegated to fugashi, so the dictionary is yours to choose

The design decision that separates this from other romaji converters is stated in one line: internally cutlet uses fugashi, so you can use the same dictionary you use for normal tokenization.

That matters because transliteration is mostly a segmentation problem. Without knowing where one word ends and the next begins, you cannot decide whether to apply a reading rule, whether a particle follows a noun or an adjective, or whether a katakana loanword should be spelled as a foreign word. A converter that carries its own segmentation has to ship a dictionary and keep it current.

By delegating, cutlet makes that your decision. If you already tokenise Japanese with fugashi and a MeCab dictionary you have tuned, the romaji comes out aligned to those same word boundaries. The release history shows this being taken seriously: v0.3.0 was titled Token-aligned romaji.

The dependencies are small and reveal the split of work. setup.py installs jaconv, which handles Japanese character normalisation, fugashi for tokenisation, and mojimoji, which handles the halfwidth and fullwidth character variants that come up in real text.

So the converter itself is a set of reading rules, and everything that requires knowing what a word is comes from the dictionary you already maintain.

use_foreign_spelling decides whether カツカレー is cutlet curry or Katsu karee

The clearest demonstration in the documentation is a single flag.

python
import cutlet
katsu = cutlet.Cutlet()
katsu.romaji("カツカレーは美味しい")
# => 'Cutlet curry wa oishii'

katsu.slug("カツカレーは美味しい")
# => 'cutlet-curry-wa-oishii'

katsu.use_foreign_spelling = False
katsu.romaji("カツカレーは美味しい")
# => 'Katsu karee wa oishii'

katu = cutlet.Cutlet('kunrei')
katu.romaji("富士山")
# => 'Huzi yama'

With the default behaviour, a katakana loanword is written the way a dictionary would write it: カツカレー becomes cutlet curry rather than a transliteration of its sounds. The feature list credits UniDic for this, using the foreign spelling when it is available there.

Setting use_foreign_spelling to False turns that off and gives the phonetic reading instead, so the same input becomes Katsu karee. Both are defensible outputs and they are not interchangeable. If your romaji output is going to be matched against romanised text from somewhere else, a loanword spelled the Japanese way will not match, and vice versa.

The slug mode is the URL case of the same decision. katsu.slug returns the hyphenated form suitable for a URL, and because it derives from romaji, the foreign-spelling setting changes your URL scheme as well. Decide that once, at construction, not per page.

The last example shows the system argument at construction time: Cutlet('kunrei') for Kunreisiki, and 'Huzi yama' rather than the Hepburn 'Fuji yama'.

Modified Hepburn only: n-to-m, macrons and passport romanisation are all out

The unsupported list is longer than the feature list, and it is the most useful thing in the documentation.

Not supported: traditional Hepburn n-to-m, with Shimbashi given as the example; macrons or circumflexes, with Tōkyō and Tôkyô as the examples; passport Hepburn, with Satoh as the example; hyphenating words; and traditional Hepburn in general.

Read together, that is a deliberate narrowing. Modified Hepburn treats n before b, p and m as n, so Shimbashi is Shimobashi rather than Shimbashi, and it does not mark vowel length. The supported systems are Modified Hepburn, Kunreisiki and Nihonsiki, and the CLI accepts hepburn, kunrei, nippon or nihon as the first argument.

The escape hatch for the cases you cannot have is the exception list. There is a built-in exceptions list covering Tokyo, Osaka and similar, custom overrides for individual mappings, and custom overrides for specific words. For passport Hepburn the documentation notes in parentheses that Satoh can be handled with an exception.

That is the practical shape: get the bulk of the corpus right with a system you accept, then enumerate the words that need to be exceptions. The list is not hardcoded. setup.py ships exceptions.tsv as package data for the cutlet package, so the exception list is a data file you can inspect, and arguably one you can extend in a fork.

Proper nouns are capitalised by default, which is another rule that changes matching behaviour if you are comparing output against a corpus that does not capitalise.

Installing it means installing a MeCab dictionary too, and the CLI reads stdin

Installation is one pip command, with a catch that is easy to miss.

bash
pip install cutlet
pip install unidic-lite

The second command is not optional. If you do not have a MeCab dictionary installed you also have to install one, and the documentation recommends unidic-lite if you are just getting started. That is a direct consequence of the fugashi design: cutlet ships rules, you supply the lexicon.

The command line script is included for quick testing. You use cutlet and each line of stdin is treated as a sentence.

bash
$ cutlet
ローマ字変換プログラム作ってみた。
Roma ji henkan program tsukutte mita.

The example input is a sentence about building a romaji conversion program, and the output splits it into words. You can specify the system as the first argument, choosing from hepburn, kunrei, nippon or nihon.

The entry point is wired in setup.py as a console script mapping cutlet to cutlet.cli:main, so the command exists as soon as the package installs.

There is also an interactive demo, a Streamlit app, and generated documentation at polm.github.io/cutlet, which is the fastest way to see what a given system does to a phrase before you commit to it. The repository also carries a pytest.ini and tests requiring pytest and hypothesis, so the test stack includes property-based testing, which is a sensible choice for a library whose whole job is mapping a large space of inputs to strings.

pykakasi carries its own dictionary; kakasi stopped in 2014

The alternatives section names two projects and the difference between them is the one that decides your choice.

kakasi is described as historically important but not updated since 2014. That is a maintenance statement rather than a quality statement, and it is decisive for anything you intend to keep running.

pykakasi is self contained: it does its own segmentation and uses its own dictionary.

That is precisely the trade cutlet declines to make. pykakasi is easier to install because there is one thing to install, and it behaves identically on every machine regardless of what else is present. Cutlet asks you to bring a dictionary, which is more setup and produces output that is consistent with the rest of your own tokenisation.

So the choice is between a converter that knows how Japanese works without help, and one that agrees with a dictionary you have already chosen. If you have no Japanese tokenisation stack at all, pykakasi is the smaller commitment. If you have one, agreeing with it is worth the extra install step, because the alternative is two dictionaries that disagree about where words begin.

One caveat belongs here rather than in a footnote: this is the documentation's own comparison, and it is written by the cutlet author. The factual parts, kakasi's last update and pykakasi's self-containment, are the ones to verify against those projects if the decision matters.

setup.py imports distutils, and the version number comes from your git tags

Two details in the packaging are worth knowing before you file an issue about an install failure.

The first is the version. setup.py sets use_scm_version=True, with a comment above the import explaining why: this is necessary to get the version right, otherwise it will fail and give v0.0.0. The version is derived from git by setuptools_scm, and pyproject.toml requires setuptools_scm with its own empty configuration block. In other words, there is no version string in the source, and an install from a tarball or a source archive without tags produces 0.0.0.

The second is distutils. setup.py imports setup from distutils.core rather than from setuptools. That module was deprecated in the standard library and removed in Python 3.12, and cutlet declares python_requires of 3.10 or newer. The import works in practice because setuptools installs a compatibility shim, but it is the kind of line that breaks first on a future interpreter, and it is the first thing to look at if pip fails on a new Python.

The rest of the metadata is tidy. Author Paul O'Leary McCann, description Romaji converter, MIT classifier, Japanese language classifier, and package_data carrying exceptions.tsv.

Development dependencies are fully pinned in requirements.txt, including the test stack: pytest 9.0.3, hypothesis 6.78.2, fugashi 1.2.1, jaconv 0.3.4, mojimoji 0.0.12 and unidic-lite 1.0.8.

The release history is short and meaningful: v0.1.19 fixed odoriji in 2021, v0.3.0 made the romaji token-aligned in 2023, and v0.5.0 changed whitespace preservation in 2025-01-04. The licence is MIT and the last push was on 2026-09-09.

Editorial conclusion

Adopt cutlet if your pipeline already tokenises Japanese with fugashi, because sharing the dictionary removes the main cost of a transliteration step and makes the romaji token-aligned with the segmentation you were going to use anyway. Do not adopt it if you need traditional Hepburn, n-to-m for words like Shimbashi, macrons, or passport romanisation, because the documentation lists all three as unsupported and only the last can be patched with an exception. Verify three things first: which romaji system your data expects, since Kunrei and Nihon differ visibly and Hepburn is the odd one out, whether you want foreign spellings like cutlet curry or phonetic ones like Katsu karee, because that flag changes your output vocabulary wholesale, and whether your MeCab dictionary actually carries the entries you need. The licence is MIT, the last release was v0.5.0 on 2025-01-04, and the last push was on 2026-09-09.

Frequently asked questions

How do I install cutlet for Japanese to romaji conversion?

Run pip install cutlet, and install a MeCab dictionary as well if you do not have one, since cutlet uses fugashi for segmentation. The documentation recommends unidic-lite, installed with pip install unidic-lite, as a starting point.

Which romanization systems does cutlet support?

Modified Hepburn, Kunreisiki and Nihonsiki, selected as hepburn, kunrei, nippon or nihon when constructing Cutlet or passing the first CLI argument. Traditional Hepburn is not supported, including n-to-m as in Shimbashi, macrons, and passport Hepburn.

What does use_foreign_spelling change in cutlet?

With the default True, a katakana loanword is written as a dictionary would, so カツカレー becomes cutlet curry using UniDic's foreign spelling. Setting it to False gives the phonetic reading instead, so the same input becomes Katsu karee.

How does cutlet compare with pykakasi and kakasi?

The documentation describes kakasi as historically important but not updated since 2014, and pykakasi as self contained, doing its own segmentation with its own dictionary. Cutlet instead uses fugashi so it shares the MeCab dictionary you already use for tokenization.

Official sources

  1. License: MIT
  2. polm/cutlet on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/polm-cutlet.svg)](https://hysenlabs.com/projects/polm-cutlet)