opencc_purepy
opencc_purepy is a pure Python implementation of OpenCC (Open Chinese Convert), supporting conversion between Simplified, Traditional, Hong Kong, Taiwan, Japanese Kanji, and Small Seal Script. It uses dictionary-based segmentation and mapping logic inspired by the original OpenCC.
🚩 Features
- Pure Python – no native dependencies
- Multiple Chinese locale conversions (Simplified, Traditional, HK, TW, JP)
- Small Seal Script conversions (
s2seal,t2seal,seal2s,seal2t) - Punctuation style conversion (optional)
- Unicode compatibility normalization (CJK compatibility ideographs and extended allographs)
- Automatic code detection (Simplified/Traditional)
- CLI with Office document support (
.docx,.xlsx,.pptx,.odt,.ods,.odp,.epub)
🐍
opencc_purepyrequires Python 3.7 or later.
🔁 Supported Conversion Configs
| Code | Description |
|---|---|
s2t |
Simplified → Traditional |
t2s |
Traditional → Simplified |
s2tw |
Simplified → Traditional (Taiwan) |
tw2s |
Traditional (Taiwan) → Simplified |
s2twp |
Simplified → Traditional (Taiwan) with phrase and variant normalization |
tw2sp |
Traditional (Taiwan) → Simplified with idioms |
s2hk |
Simplified → Traditional (Hong Kong) |
hk2s |
Traditional (Hong Kong) → Simplified |
s2hkp |
Simplified → Hong Kong Traditional with phrase and variant normalization |
hk2sp |
Hong Kong Traditional with phrases/variants → Simplified |
t2tw |
Traditional → Traditional (Taiwan) |
tw2t |
Traditional (Taiwan) → Traditional |
t2twp |
Traditional → Traditional (Taiwan) with idioms |
tw2tp |
Traditional (Taiwan) → Traditional with idioms |
t2hk |
Traditional → Traditional (Hong Kong) |
t2hkp |
Traditional → Traditional (Hong Kong) with idioms |
hk2t |
Traditional (Hong Kong) → Traditional |
hk2tp |
Traditional (Hong Kong) → Traditional with idioms |
t2jp |
Japanese Kyujitai → Shinjitai |
jp2t |
Japanese Shinjitai → Kyujitai |
s2seal |
Simplified Chinese → Small Seal Script |
t2seal |
Traditional Chinese → Small Seal Script |
seal2s |
Small Seal Script → Simplified Chinese |
seal2t |
Small Seal Script → Traditional Chinese |
📦 Installation
Install from PyPI (Stable Release)
Install the latest published release:
pip install -U opencc-purepy
Install from GitHub (Development Version)
Install the latest development version directly from GitHub:
With Git installed:
pip install -U "git+https://github.com/laisuk/opencc_purepy.git@master"
Without Git:
pip install -U "https://github.com/laisuk/opencc_purepy/archive/refs/heads/master.zip"
Both methods install the development source, including changes not yet published on PyPI.
Note: Development versions may contain experimental changes. A Python build backend and its dependencies are required, but no native compiler is needed.
Verify Installation
opencc-purepy --help
echo "汉字转换" | opencc-purepy convert -c s2t
🚀 Usage
Python
from opencc_purepy import OpenCC
text = "“春眠不觉晓,处处闻啼鸟。”"
opencc = OpenCC("s2t")
converted = opencc.convert(text, punctuation=True)
print(converted) # 「春眠不覺曉,處處聞啼鳥。」
Small Seal Script conversion
Use a config with convert() or call a direction-specific method:
from opencc_purepy import OpenCC, OpenccConfig
cc = OpenCC(OpenccConfig.S2SEAL) # Also accepts "s2seal"
seal_text = cc.convert("汉字小篆") #
assert cc.t2seal("漢字小篆") == seal_text
assert cc.seal2s(seal_text) == "汉字小篆"
assert cc.seal2t(seal_text) == "漢字小篆"
assert cc.s2seal("汉字小篆") == seal_text
The direct methods leave the active config unchanged. All four accept punctuation=True:
s2seal, t2seal, and seal2t apply Traditional-style punctuation mappings; seal2s applies
Simplified-style punctuation mappings.
opencc-purepy convert -i simplified.txt -o seal.txt -c s2seal
opencc-purepy convert -i seal.txt -o traditional.txt -c seal2t
Conversion applies these dictionary rounds in order:
| Config | Dictionary rounds |
|---|---|
s2seal |
Simplified → Traditional → SealVariants → SealCharactersRev |
t2seal |
SealVariants → SealCharactersRev |
seal2s |
SealCharacters → SealVariantsRev → Traditional → Simplified |
seal2t |
SealCharacters → SealVariantsRev |
CLI
The default conversion configuration is s2t. Run any subcommand with --help to see the complete supported
configuration and custom-dictionary slot lists.
Text File Conversion
python -m opencc_purepy convert -i input.txt -o output.txt -c s2t -p
# or, if installed as a script:
opencc-purepy convert -i input.txt -o output.txt -c s2t -p
Standard input and output are used when -i or -o is omitted:
echo "汉字转换" | opencc-purepy convert -c s2t
Use --in-enc and --out-enc for non-UTF-8 text files. DeTofu fallback can be enabled at a selected extension level,
with an optional UTF-8 custom mapping file:
opencc-purepy convert -i input.txt -o output.txt -c s2t \
--in-enc utf-8 --out-enc utf-8 --detofu ext-c
opencc-purepy convert -i input.txt -c s2t \
--detofu all --detofu-file ./custom_detofu.txt
Use -n/--norm-compat to normalize CJK Compatibility Ideographs before conversion, or
-E/--norm-compat-extended to apply the complete built-in Unicode CJK compatibility normalization. If both options are
specified, --norm-compat-extended takes precedence:
opencc-purepy convert -i input.txt -o output.txt -c t2s --norm-compat
opencc-purepy convert -i input.txt -o output.txt -c t2s \
--norm-compat-extended
Office Document Conversion subcommand (office)
Supports: .docx, .xlsx, .pptx, .odt, .ods, .odp, .epub
# Convert Word document with font preservation
opencc-purepy office -i example.docx -c t2s --keep-font
# Convert EPUB and auto-detect output name from input file extension
opencc-purepy office -i book.epub -c s2t
# Normalize, convert, then apply display fallbacks (v1.4.4)
opencc-purepy office -i book.epub -c t2s -E --detofu ext-b
# Use custom display fallbacks after conversion
opencc-purepy office -i example.docx -c s2t \
--detofu all --detofu-file ./custom_detofu.txt
# Convert Excel and specify output path and format
opencc-purepy office -i sheet.xlsx -o result.xlsx -c s2tw --format xlsx
Both convert and office apply the same order: optional compatibility normalization → OpenCC conversion (including
-p punctuation mappings) → optional DeTofu. -E takes precedence over -n.
--detofu-file requires --detofu; it supplies display fallbacks rather than OpenCC phrase mappings.
When -o is omitted, office writes <input-stem>_converted.<format> beside the source. Use --convert-filename
to apply the same text pipeline to the generated output stem. --format selects the input package format; it does
not convert between document formats. --keep-font preserves recognized font-family attributes.
📚 Custom Dictionaries
opencc_purepy follows the OpenCC lexicon structure. Custom entries are loaded through existing OpenCC dictionary
slots, such as DictSlot.STPhrases, DictSlot.TSPhrases, DictSlot.STPunctuations, and other OpenCC slots. There is
no generic UserDict slot.
Dictionary slot mappings support both:
DictSlot(recommended)- string slot names such as
"st_phrases"(backward compatible)
Raw TXT dictionary loading: OpenCC.from_dicts()
Use OpenCC.from_dicts() when you want to load OpenCC TXT dictionary files directly through
DictionaryMaxlength.from_dicts(). With appends={...}, it loads the base TXT dictionaries first, then custom entries.
Duplicate keys use late-comer wins, so custom entries override built-in entries. This is recommended for most users.
from opencc_purepy import DictSlot, OpenCC
cc = OpenCC.from_dicts(
config="s2t",
appends={
DictSlot.STPhrases: "./UserDict.txt",
DictSlot.TWVariantsPhrases: "./custom/TWVariantsPhrases.txt",
DictSlot.HKVariantsPhrases: "./custom/HKVariantsPhrases.txt",
},
)
print(cc.convert("帕兰蒂尔是一家公司"))
String slot names remain supported for compatibility:
from opencc_purepy import OpenCC
cc = OpenCC.from_dicts(
config="s2t",
appends={
"st_phrases": "./UserDict.txt",
},
)
The same appends={...} and overrides={...} arguments are also supported by DictionaryMaxlength.from_dicts() when
you want to create and reuse a dictionary instance yourself.
Quick file API: OpenCC.from_dict_files()
Use OpenCC.from_dict_files() when you want a post-load custom-file API on top of the packaged JSON dictionaries. It
loads opencc_purepy/dicts/dictionary_maxlength.json first, then applies each OpenCC-compatible custom file to the
requested slot.
from opencc_purepy import DictSlot, OpenCC
from opencc_purepy.utils import CustomDictSpec
cc = OpenCC.from_dict_files(
config="hk2sp",
specs=[
CustomDictSpec(DictSlot.HKPhrasesRev, "append", "./my_hk_dict.txt"),
],
)
print(cc.convert("這個細路哥很靈活"))
Each CustomDictSpec is slot, mode, path:
slot: aDictSlot, such asDictSlot.HKPhrasesRevmode:"append"or"override"path: a UTF-8 OpenCC text dictionary file
Use "append" for user terms that should extend a built-in slot. Use "override" only when the file is a complete
replacement for that slot.
Dictionary slot parsing
DictSlot.parse() accepts a DictSlot directly or parses enum-style, compact, and legacy underscore-separated string
forms. DictSlot.canonical_name() returns the public enum-style slot name used by cross-language APIs and custom
dictionary specifications; .value remains the legacy underscore-separated key.
from opencc_purepy import DictSlot, OpenCC
slot = DictSlot.parse("HKPhrasesRev")
assert slot is DictSlot.HKPhrasesRev
assert DictSlot.parse("hkphrasesrev") is slot
assert DictSlot.parse("hk_phrases_rev") is slot
assert slot.canonical_name() == "HKPhrasesRev"
assert slot.value == "hk_phrases_rev"
# Discover every currently supported public slot name.
slots = OpenCC.available_slots()
assert "HKPhrasesRev" in slots
assert "JPSCharactersRev" in slots
For advanced API use, parse_custom_dict_spec() reuses the same parser for the complete CLI-style
slot:mode:path token. This avoids manually constructing CustomDictSpec records while keeping token parsing in the
utility layer:
from opencc_purepy import OpenCC
from opencc_purepy.utils import parse_custom_dict_spec
tokens = [
"hk_phrases_rev:append:./my_hk_dict.txt",
]
cc = OpenCC.from_dict_files(
config="hk2sp",
specs=[parse_custom_dict_spec(token) for token in tokens],
)
Use DictSlot.parse() when only a slot name needs normalization. Use parse_custom_dict_spec() when the input is the
combined slot:mode:path form used by the CLI.
CLI custom dictionary files
The convert, office, and dictgen subcommands support -D/--custom-dict <slot:mode:path>. The option can be used
multiple times. For convert and office, it follows the same packaged-JSON-plus-custom-files behavior as
OpenCC.from_dict_files(). Specifications are applied from left to right; when multiple files affect the same slot,
later specifications see and may replace entries produced by earlier ones.
opencc-purepy convert -c hk2sp \
-D hkphrasesrev:append:./my_hk_dict.txt
Slot names accept enum-style names, compact names, and legacy keys, including HKPhrasesRev, hkphrasesrev, and
hk_phrases_rev.
opencc-purepy office -i book.epub -c hk2sp \
--custom-dict hkphrasesrev:append:./my_hk_dict.txt
For dictgen, -D/--custom-dict applies custom dictionary files before writing the generated JSON:
opencc-purepy dictgen -d ./my_dicts -o dictionary_maxlength.json \
-D STPhrases:append:./UserDict.txt
# Compact JSON while preserving dictionary insertion/source order
opencc-purepy dictgen -o dictionary_maxlength.json --compact --no-sort
Post-load file customization
Use DictionaryMaxlength.with_custom_dict_files() when you already have a dictionary instance and want to apply
OpenCC-compatible text dictionary files after loading it. Post-load customization supports both appends={...} and
overrides={...}.
from opencc_purepy import DictSlot, OpenCC
from opencc_purepy.dictionary_lib import DictionaryMaxlength
dictionary = DictionaryMaxlength.from_json().with_custom_dict_files(
appends={
DictSlot.STPhrases: "./UserDict.txt",
DictSlot.TWVariantsPhrases: "./custom/TWVariantsPhrases.txt",
DictSlot.HKVariantsPhrases: "./custom/HKVariantsPhrases.txt",
},
)
cc = OpenCC(config="s2t", dictionary=dictionary)
print(cc.convert("帕兰蒂尔是一家公司"))
Create a private dictionary instance first with DictionaryMaxlength.from_json() or DictionaryMaxlength.from_dicts().
These methods mutate and return the same dictionary instance. Apply customization before constructing OpenCC,
which caches conversion plans. Do not mutate the shared global provider returned by DictionaryMaxlength.get_provider()
or
DictionaryMaxlength.new(); the post-load customization APIs are intended for private dictionary instances.
Tofu-risk / Extension Unicode fallback pairs
Use DictionaryMaxlength.with_custom_dicts() for exact in-memory custom pairs when you need to patch tofu-risk
characters or Extension Unicode mappings without restructuring the built-in OpenCC dictionaries.
This is useful for platforms where some CJK Extension characters may render as tofu boxes, or where you want to provide a temporary project-local fallback before the upstream dictionary data is updated.
from opencc_purepy import DictSlot, OpenCC
from opencc_purepy.dictionary_lib import DictionaryMaxlength
dictionary = DictionaryMaxlength.from_json().with_custom_dicts(
appends={
DictSlot.STPhrases: {
# Project-local fallback pairs for tofu-risk / Extension Unicode cases.
# Keep these patches small, explicit, and easy to remove later.
"骖𬴂": "驂騑",
"𫜩合": "齧合",
"𫜩蘗吞针": "齧蘗吞針",
# Normal custom phrase pairs may be mixed in as well.
"帕兰蒂尔": "帕蘭蒂爾",
},
DictSlot.TWVariantsPhrases: {
"喫茶小舖": "喫茶小舖",
},
DictSlot.HKVariantsPhrases: {
"喫茶小舖": "喫茶小舖",
},
},
)
cc = OpenCC(config="s2t", dictionary=dictionary)
print(cc.convert("骖𬴂"))
print(cc.convert("𫜩合"))
print(cc.convert("帕兰蒂尔"))
This keeps the core dictionary structure unchanged while still allowing applications to patch specific high-risk entries at load time.
Dictionary text format
Custom dictionary files are UTF-8 text files in OpenCC lexicon format. Use one mapping per line:
# Custom company terms
帕兰蒂尔 帕蘭蒂爾
AI模型 AI模型
Each entry uses the first two whitespace-separated columns as key and value. Additional translation candidates
are ignored. Blank lines and lines whose first non-whitespace character is # are skipped; inline comments are
not parsed specially. Duplicate keys use late-comer wins.
Because file parsing follows OpenCC dictionary rules, leading spaces and embedded spaces in keys are not preserved. Use
with_custom_dicts() when the custom key itself contains spaces.
Override mode
Use overrides={...} to replace an entire dictionary slot. This is for advanced users who maintain a full replacement
for a selected OpenCC dictionary slot.
from opencc_purepy import DictSlot, OpenCC
cc = OpenCC.from_dicts(
config="s2t",
overrides={
DictSlot.STPhrases: "./company/STPhrases.txt",
},
)
Post-load override mode works the same way:
from opencc_purepy import DictSlot
from opencc_purepy.dictionary_lib import DictionaryMaxlength
dictionary = DictionaryMaxlength.from_json().with_custom_dict_files(
overrides={
DictSlot.STPhrases: "./CompanyOnlySTPhrases.txt",
},
)
Supported slots
The four Seal slots support the same append/override APIs and CLI --custom-dict syntax as other slots.
SealCharacters maps Seal characters to regular-script transcriptions; SealCharactersRev maps those
transcriptions to Seal characters. SealVariants normalizes modern characters to those transcriptions,
and SealVariantsRev maps them back to modern standard characters.
For example, replace the character-to-Seal mapping with a UTF-8 tab-separated dictionary:
from opencc_purepy import DictSlot, OpenCC
cc = OpenCC.from_dicts(
config="t2seal",
overrides={DictSlot.SealCharactersRev: "./custom/SealCharactersRev.txt"},
)
DictSlot |
Legacy key | Default file |
|---|---|---|
DictSlot.STCharacters |
st_characters |
STCharacters.txt |
DictSlot.STPhrases |
st_phrases |
STPhrases.txt |
DictSlot.STPunctuations |
st_punctuations |
STPunctuations.txt |
DictSlot.TSCharacters |
ts_characters |
TSCharacters.txt |
DictSlot.TSPhrases |
ts_phrases |
TSPhrases.txt |
DictSlot.TSPunctuations |
ts_punctuations |
TSPunctuations.txt |
DictSlot.TWPhrases |
tw_phrases |
TWPhrases.txt |
DictSlot.TWPhrasesRev |
tw_phrases_rev |
TWPhrasesRev.txt |
DictSlot.TWVariantsPhrases |
tw_variants_phrases |
TWVariantsPhrases.txt |
DictSlot.TWVariants |
tw_variants |
TWVariants.txt |
DictSlot.TWVariantsRev |
tw_variants_rev |
TWVariantsRev.txt |
DictSlot.TWVariantsRevPhrases |
tw_variants_rev_phrases |
TWVariantsRevPhrases.txt |
DictSlot.HKPhrases |
hk_phrases |
HKPhrases.txt |
DictSlot.HKPhrasesRev |
hk_phrases_rev |
HKPhrasesRev.txt |
DictSlot.HKVariantsPhrases |
hk_variants_phrases |
HKVariantsPhrases.txt |
DictSlot.HKVariants |
hk_variants |
HKVariants.txt |
DictSlot.HKVariantsRev |
hk_variants_rev |
HKVariantsRev.txt |
DictSlot.HKVariantsRevPhrases |
hk_variants_rev_phrases |
HKVariantsRevPhrases.txt |
DictSlot.JPSCharacters |
jps_characters |
JPShinjitaiCharacters.txt |
DictSlot.JPSCharactersRev |
jps_characters_rev |
JPShinjitaiCharactersRev.txt |
DictSlot.JPSPhrases |
jps_phrases |
JPShinjitaiPhrases.txt |
DictSlot.SealCharacters |
seal_characters |
SealCharacters.txt |
DictSlot.SealCharactersRev |
seal_characters_rev |
SealCharactersRev.txt |
DictSlot.SealVariants |
seal_variants |
SealVariants.txt |
DictSlot.SealVariantsRev |
seal_variants_rev |
SealVariantsRev.txt |
Forward regional variant phrase slots are applied before their character-level variant slots:
| Slot | File | Direction | Purpose |
|---|---|---|---|
DictSlot.TWVariantsPhrases |
TWVariantsPhrases.txt |
Forward TW regional variants | Phrase-level TW variant mappings applied before TWVariants.txt |
DictSlot.HKVariantsPhrases |
HKVariantsPhrases.txt |
Forward HK regional variants | Phrase-level HK variant mappings applied before HKVariants.txt |
Japanese Shinjitai slots follow the upstream OpenCC JP layout:
| Slot | File | Purpose |
|---|---|---|
DictSlot.JPSCharacters |
JPShinjitaiCharacters.txt |
Japanese Shinjitai-to-Traditional Kyujitai characters |
DictSlot.JPSCharactersRev |
JPShinjitaiCharactersRev.txt |
Traditional Kyujitai-to-Japanese Shinjitai characters |
DictSlot.JPSPhrases |
JPShinjitaiPhrases.txt |
Japanese Shinjitai-to-Traditional Kyujitai phrases |
Generate JSON with dictgen
TXT dictionaries are human-editable source files. opencc_purepy/dicts/dictionary_maxlength.json is a generated/cache
format, so prefer
dictgen instead of manually editing JSON.
opencc-purepy dictgen -d ./my_dicts -o dictionary_maxlength.json
# Optionally bake custom dictionary files into the generated JSON
opencc-purepy dictgen -d ./my_dicts -o dictionary_maxlength.json \
-D STPhrases:append:./UserDict.txt
from opencc_purepy import OpenCC
from opencc_purepy.dictionary_lib import DictionaryMaxlength
dictionary = DictionaryMaxlength.from_json("opencc_purepy/dicts/dictionary_maxlength.json")
cc = OpenCC(
config="s2t",
dictionary=dictionary,
)
Which mode should I use?
- Use
OpenCC.from_dict_files()or CLI-D/--custom-dict slot:mode:pathwhen you want packaged JSON dictionaries plus a few custom files. - Use
OpenCC.from_dicts()when you want to load raw OpenCC TXT dictionary files or a custom TXT dictionary directory. - Use
overrideswhen maintaining a full proprietary replacement of an OpenCC dictionary file. - Use
with_custom_dict_files()to apply OpenCC-compatible text files to a private dictionary after loading it. - Use
with_custom_dicts()for exact in-memory pairs, especially keys with leading or embedded spaces. - Use
dictgenwhen you want to bake TXT dictionaries into JSON for reuse or faster loading. - Use direct dictionary injection when sharing one loaded dictionary across many
OpenCCinstances. - Prefer
DictSlotfor new code and IDE-friendly type checking. - Legacy
strslot keys remain fully supported for backward compatibility.
Unicode compatibility normalization
opencc_purepy provides optional Unicode normalization helpers for text that contains CJK compatibility ideographs,
legacy glyph forms, radicals, or other mapped allographs. These helpers are separate from normal OpenCC conversion:
they normalize Unicode forms without selecting a Simplified, Traditional, Taiwan, Hong Kong, or Japanese conversion
configuration.
Three APIs are available:
normalize_compat(text)— normalize CJK Compatibility Ideographs.normalize_unicode_compat(text)— normalize additional Unicode CJK compatibility and allograph mappings.normalize_compat_extended(text)— apply both normalization tables for the broadest normalization.
CJK Compatibility Ideographs
Use normalize_compat() when the input contains characters from the Unicode CJK compatibility ranges:
from opencc_purepy import OpenCC
cc = OpenCC("t2s")
text = "天龍八部書裡的喬峰是契丹人"
normalized = cc.normalize_compat(text)
print(normalized)
# 天龍八部書裡的喬峰是契丹人
# This pass only performs compatibility normalization. It does not automatically run OpenCC conversion:
converted = cc.convert(normalized)
print(converted)
# 天龙八部书里的乔峰是契丹人
Extended Unicode compatibility mappings
Use normalize_unicode_compat() for additional mapped CJK allographs and legacy forms outside the dedicated CJK
Compatibility Ideographs table:
from opencc_purepy import OpenCC
cc = OpenCC("t2s")
text = "聼聼竒羙甁噐"
normalized = cc.normalize_unicode_compat(text)
print(normalized)
# 聽聽奇美瓶器
Unmapped characters are preserved unchanged.
Combined normalization
For text from PDFs, OCR, legacy documents, historical sources, or mixed Unicode data, normalize_compat_extended() is
the convenient-combined pass. It applies the extended Unicode compatibility mappings and CJK Compatibility Ideographs
normalization in the intended order:
from opencc_purepy import OpenCC
cc = OpenCC("t2s")
text = "聼聼竒羙⽟䂖甁噐⾳"
normalized = cc.normalize_compat_extended(text)
print(normalized)
# 聽聽奇美玉石瓶器音
converted = cc.convert(normalized)
print(converted)
# 听听奇美玉石瓶器音
Normalization is intentionally explicit and is not automatically performed by convert(). This keeps normal OpenCC
conversion behavior unchanged and lets applications choose the preprocessing appropriate for their input.
When both Unicode normalization and DeTofu are needed, use the following processing order:
from opencc_purepy import OpenCC
text = "聼聼竒羙⽟䂖甁噐⾳"
cc = OpenCC("t2s")
text = cc.normalize_compat_extended(text)
text = cc.convert(text)
text = cc.detofu(text, "all")
In short:
Unicode normalization -> OpenCC conversion -> DeTofu
DeTofu display compatibility fallback
DeTofu replaces mapped tofu-risk rare CJK extension characters with display-compatible fallback characters. It is useful on systems, browsers, e-book readers, document viewers, or mobile platforms where some non-BMP CJK extension characters may render as tofu boxes, missing glyphs, or empty boxes.
DeTofu is a display-compatibility pass, not normal OpenCC dictionary conversion. It does not modify phrase matching,
regional variant selection, script detection, or punctuation conversion. If you use it with converted text, apply it
after convert(...).
from opencc_purepy import OpenCC
from opencc_purepy.detofu import DeTofuLevel
cc = OpenCC("t2s")
text = "儼驂騑於上路,訪風景於崇阿"
converted = cc.convert(text)
display_safe = cc.detofu(converted, DeTofuLevel.ExtB)
print(display_safe)
# String level names are also supported:
display_safe = cc.detofu(converted, "all")
DeTofu levels
DeTofu levels are threshold-based:
ExtB means ExtB and above
ExtC means ExtC and above
ExtD means ExtD and above
...
ExtI means ExtI only
Supported level names:
all
ext-b / b
ext-c / c
ext-d / d
ext-e / e
ext-f / f
ext-g / g
ext-h / h
ext-i / i
"all" is equivalent to ExtB.
Custom fallback file
Use detofu_with_custom_file(...) to add project-local fallback mappings from a UTF-8 text file:
from opencc_purepy import OpenCC
cc = OpenCC("t2s")
text = "儼驂騑於上路,訪風景於崇阿𱁬"
converted = cc.convert(text)
display_safe = cc.detofu_with_custom_file(
converted,
"all",
"custom_tofu.txt",
)
File format:
tofu_char<TAB>fallback_char<TAB>extension
Example:
𱁬 ? ExtB
Blank lines are ignored, and lines beginning with # are ignored. The extension column accepts B through I or
ExtB through ExtI. Custom file mappings override built-in mappings for the same tofu-risk character.
Custom in-memory pairs
Use detofu_with_custom_pairs(...) to add direct fallback pairs in memory:
from opencc_purepy import OpenCC
cc = OpenCC("t2s")
text = "儼驂騑於上路,訪風景於崇阿𱁬"
converted = cc.convert(text)
display_safe = cc.detofu_with_custom_pairs(
converted,
"all",
{
"𱁬": "?",
},
)
Direct pairs do not have an extension column. They are always added to the selected map, and custom pairs override built-in mappings for the same tofu-risk character. Only the first Unicode scalar from each key and value is used. Empty keys or values are ignored.
DeTofu contract
- Unknown characters are preserved unchanged.
- DeTofu never replaces unknown characters with
?,□,�, or empty text. - DeTofu only replaces characters that exist in the built-in or custom fallback map.
- DeTofu is intended as a final display-compatibility pass.
- For converted text, call
cc.detofu(...)aftercc.convert(...).
🧩 API Reference
Exports
OpenCCOpenccConfigDictSlotDictSlotLikeDeTofuLevelis available fromopencc_purepy.detofuCustomDictSpec,parse_custom_dict_spec,parse_custom_dict_specs, andcustom_dict_specs_to_mapsare available fromopencc_purepy.utils
OpenCC class
-
OpenCC(config: Optional[Union[str, OpenccConfig]] = None, dictionary: Optional[DictionaryMaxlength] = None)Create a converter with a supported config string orOpenccConfigenum value. The config defaults to"s2t"when omitted. Advanced callers may inject a private or preloadedDictionaryMaxlengthinstance. RaisesValueErrorfor unsupported configs. -
OpenCC.from_dicts(config="s2t", base_dir=None, paths=None, overrides=None, appends=None) -> OpenCC
Create a converter by loading raw OpenCC TXT dictionary files throughDictionaryMaxlength.from_dicts(). -
OpenCC.from_dict_files(config="s2t", specs=None) -> OpenCC
Create a converter from packaged JSON dictionaries, then applyCustomDictSpec(slot, mode, path)custom file specs. -
set_config(config: Optional[Union[str, OpenccConfig]]) -> NoneUpdate the active config;Noneresets tos2t. Strings are stripped and lowercased. Invalid values raiseValueErrorand leave the active config unchanged. -
get_config() -> str
Return the current canonical config name. -
supported_configs() -> List[str]
Return all supported config names. -
available_slots() -> List[str]
Return the canonical names of all currently available dictionary slots. -
get_last_error() -> Optional[str]
Return the most recently recorded error, if any. Successful conversions do not clear previous errors. -
convert(input_text: str, punctuation: bool = False) -> str
Convert text using the active config, with optional punctuation conversion. Empty input returns""and records an error. Caught conversion failures return an error message and record it inget_last_error(). Normalization and DeTofu require explicit calls in Python; they are not enabled byconvert(). Direction-specific methods below do not change the active config and propagate conversion exceptions. -
s2t(input_text: str, punctuation: bool = False) -> str
Simplified Chinese to Traditional Chinese. -
t2s(input_text: str, punctuation: bool = False) -> str
Traditional Chinese to Simplified Chinese. -
s2tw(input_text: str, punctuation: bool = False) -> str
Simplified Chinese to Taiwan Traditional. -
tw2s(input_text: str, punctuation: bool = False) -> str
Taiwan Traditional to Simplified Chinese. -
s2twp(input_text: str, punctuation: bool = False) -> str
Simplified Chinese to Taiwan Traditional with phrase and variant normalization. -
tw2sp(input_text: str, punctuation: bool = False) -> str
Taiwan Traditional with idioms to Simplified Chinese. -
s2hk(input_text: str, punctuation: bool = False) -> str
Simplified Chinese to Hong Kong Traditional. -
hk2s(input_text: str, punctuation: bool = False) -> str
Hong Kong Traditional to Simplified Chinese. -
s2hkp(input_text: str, punctuation: bool = False) -> str
Simplified Chinese to Hong Kong Traditional with phrase and variant normalization. -
hk2sp(input_text: str, punctuation: bool = False) -> str
Hong Kong Traditional with phrases/variants to Simplified Chinese. -
t2tw(input_text: str, punctuation: bool = False) -> str
Traditional Chinese to Taiwan Traditional. -
t2twp(input_text: str, punctuation: bool = False) -> str
Traditional Chinese to Taiwan Traditional with phrase mappings. -
tw2t(input_text: str, punctuation: bool = False) -> str
Taiwan Traditional to standard Traditional Chinese. -
tw2tp(input_text: str, punctuation: bool = False) -> str
Taiwan Traditional to standard Traditional Chinese with phrase reversal. -
t2hk(input_text: str, punctuation: bool = False) -> str
Traditional Chinese to Hong Kong variant. -
t2hkp(input_text: str, punctuation: bool = False) -> strTraditional Chinese to Hong Kong Traditional with phrase and variant normalization. -
hk2t(input_text: str, punctuation: bool = False) -> str
Hong Kong Traditional to standard Traditional Chinese. -
hk2tp(input_text: str, punctuation: bool = False) -> strHong Kong Traditional to standard Traditional Chinese with phrase and variant reversal. -
t2jp(input_text: str, punctuation: bool = False) -> str
Traditional Chinese to Japanese variants. -
jp2t(input_text: str, punctuation: bool = False) -> str
Japanese Shinjitai to Traditional Chinese. -
s2seal(input_text: str, punctuation: bool = False) -> strSimplified Chinese to Small Seal Script. -
t2seal(input_text: str, punctuation: bool = False) -> strTraditional Chinese to Small Seal Script. -
seal2s(input_text: str, punctuation: bool = False) -> strSmall Seal Script to Simplified Chinese. -
seal2t(input_text: str, punctuation: bool = False) -> strSmall Seal Script to Traditional Chinese. -
st(input_text: str) -> str
Character-only Simplified to Traditional conversion. -
ts(input_text: str) -> str
Character-only Traditional to Simplified conversion. -
zho_check(input_text: str) -> int
Heuristically classify a short prefix:1= Traditional,2= Simplified,0= empty or indeterminate. Mixed text is not reliably classified; Traditional evidence is checked first. -
normalize_compat(text: Optional[str]) -> str
Normalize mapped CJK Compatibility Ideographs while preserving unmapped characters. -
normalize_unicode_compat(text: Optional[str]) -> str
Normalize additional mapped Unicode CJK compatibility forms and allographs. -
normalize_compat_extended(text: Optional[str]) -> str
Apply extended Unicode compatibility normalization followed by CJK Compatibility Ideographs normalization. -
detofu( text: Optional[str], level: Union[DeTofuLevel, str] = DeTofuLevel.ExtB ) -> strApply built-in DeTofu display-compatible fallback mappings.
-
detofu_with_custom_file( text: Optional[str], level: Union[DeTofuLevel, str], path: str ) -> strApply built-in DeTofu mappings plus a UTF-8 custom fallback file.
-
detofu_with_custom_pairs( text: Optional[str], level: Union[DeTofuLevel, str], pairs: Union[ Mapping[str, str], Iterable[Tuple[str, str]] ] ) -> strApply built-in DeTofu mappings plus direct in-memory fallback pairs.
DictSlot enum
parse(value: Union[DictSlot, str]) -> DictSlot
Normalize an enum member, enum-style name, compact name, or legacy underscore-separated key.canonical_name() -> str
Return the public enum-style slot name, such as"HKPhrasesRev".
OpenccConfig enum
- Members include:
S2T,T2S,S2TW,TW2S,S2TWP,TW2SP,S2HK,HK2S,S2HKP,HK2SP,T2TW,TW2T,T2TWP,TW2TP,T2HK,HK2T,T2HKP,HK2TP,T2JP,JP2T to_canonical_name() -> str
Return the lowercase OpenCC config string.parse(value: str) -> OpenccConfig
Parse a config string into an enum value.
🛠 Development
- Python bindings:
opencc_purepy/__init__.py,opencc_purepy/core.py - CLI:
opencc_purepy/__main__.py
⚡ Benchmark
Measured on GitHub Actions ubuntu-latest using the default s2t configuration.
The benchmark separates cold startup, first post-initialization conversion, and warm cached conversion.
Latest Results — opencc-purepy v1.5.0
GitHub Actions benchmark summary ( v1.5.0)
Runner Platform
| Field | Value |
|---|---|
| Runner | Linux X64 |
| Image | ubuntu24 20261023.334.3 |
| Kernel | 6.17.0-1022-azure |
| CPU | AMD EPYC 9V74 80-Core Processor |
| CPU Cores | 4 |
| Memory | Not reported |
| Python | Python 3.12.15 |
| opencc_purepy | 1.5.0 |
Results
| Input Size | Cold Total (ms) | Post-init Cold (ms) | Warm (ms) |
|---|---|---|---|
| 100 chars | 23.930 | 22.062 | 0.092 |
| 1,000 chars | 23.547 | 22.860 | 0.877 |
| 10,000 chars | 28.790 | 28.036 | 6.852 |
| 100,000 chars | 93.009 | 92.082 | 70.177 |
Previous Results — opencc-purepy v1.4.1
GitHub Actions benchmark summary ( v1.4.1)
Runner Platform
| Field | Value |
|---|---|
| Runner | Linux X64 |
| Image | ubuntu24 20260705.232.1 |
| Kernel | 6.17.0-1018-azure |
| CPU | AMD EPYC 9V74 80-Core Processor |
| CPU Cores | 4 |
| Memory | Not reported |
| Python | Python 3.10.20 |
| opencc_purepy | 1.4.1 |
Results
| Input Size | Cold Total (ms) | Post-init Cold (ms) | Warm (ms) |
|---|---|---|---|
| 100 chars | 16.025 | 14.065 | 0.129 |
| 1,000 chars | 16.136 | 15.459 | 1.287 |
| 10,000 chars | 25.506 | 24.347 | 10.441 |
| 100,000 chars | 121.911 | 119.935 | 104.965 |
Performance Comparison — v1.4.1 vs v1.5.0
| Input Size | Warm v1.4.1 (ms) | Warm v1.5.0 (ms) | Time Reduction |
|---|---|---|---|
| 100 chars | 0.129 | 0.092 | 28.7% |
| 1,000 chars | 1.287 | 0.877 | 31.9% |
| 10,000 chars | 10.441 | 6.852 | 34.4% |
| 100,000 chars | 104.965 | 70.177 | 33.1% |
Notes
The v1.5.0 benchmark shows approximately 29–34% lower warm-conversion latency across the tested input sizes compared with v1.4.1.
Recent core optimizations include:
- Precomputed, descending per-starter key-length indexes.
- Direct iteration over existing candidate lengths, avoiding unnecessary dictionary lookups.
- Range-based conversion that reduces intermediate substring allocations and repeated function calls.
- Reuse of identical starter-length patterns to limit additional indexing overhead.
The observed warm-path improvements are consistent with these optimizations.
However, the benchmark runs used different Python versions (3.10.20 versus 3.12.15) and different GitHub Actions runner images. Consequently, these measurements demonstrate observed end-to-end improvements rather than an isolated measurement of the indexing optimization.
Despite being implemented entirely in pure Python, opencc_purepy achieves practical conversion throughput through
union caching, starter indexing, and optimized hot-path matching.
The warm conversion path is particularly suitable for large-text workloads, including document conversion, GUI applications, and batch processing.
cold_total includes OpenCC(config) setup plus conversion. post_init_cold measures the first conversion after
initialization. warm measures conversion after the union cache has already been built. Lower times are better. Results
depend on Python version, runner hardware, and background system load.
Projects That Use opencc-purepy
📄 License
This project is licensed under the MIT License.
Powered by Pure Python and OpenCC Lexicons.
Metadata
Release files for opencc-purepy 1.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| opencc_purepy-1.5.0.tar.gz | 1.4 MB | With provenance | Details for opencc_purepy-1.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| opencc_purepy-1.5.0-py3-none-any.whl | Python 3 | none | any | Details for opencc_purepy-1.5.0-py3-none-any.whl |
Total release size: 2.8 MB
Release files / opencc_purepy-1.5.0.tar.gz
| Download URL | opencc_purepy-1.5.0.tar.gz |
|---|---|
| Size | 1.4 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
71797c7f6fb61cc8eb8e47eb87541c4def14b56f8af65821d88217e20b6ceff5
|
|
BLAKE2b-256 checksum How to use checksums |
bc54cf725343a0f4050eb597af4c3b0a7f4f97e66bb8fb249fbaf7b4f2b9ed89
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logRelease files / opencc_purepy-1.5.0-py3-none-any.whl
| Download URL | opencc_purepy-1.5.0-py3-none-any.whl |
|---|---|
| Size | 1.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
39872a0018abf5f9dbe277f9007f7ab012e2172adacbfce3e6c1f1a81ef2906f
|
|
BLAKE2b-256 checksum How to use checksums |
61cba703829f09624c9a7d3cf6d04aa99cc1ffefc51674a2f9b3a7293dfa6da9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logSecurity for opencc-purepy 1.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for opencc_purepy-1.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.