Unit Converter
A local, fully offline unit converter skill for OVOS - no cloud dependency, no API key. Conversion math is done with pint, spoken numbers are parsed with ovos-number-parser.
This is an early-stage scaffold (0.0.x). 19 of 21 categories from the original brief are wired up in English and Danish; 6 more languages have a first machine-translated pass (see "Status" - quality bar is lower than en-us/da-dk for those). Not ready for the OVOS Skill Store yet.
Why this exists
Back in the day, most Windows PCs had a little utility like this one - a simple offline converter with tabs for every unit category you could think of (distance, mass, temperature, volume, and so on), no internet connection required. This skill is the voice-assistant equivalent of that program: local, offline, no account, no API key, just "convert X to Y".
There's a Wolfram Alpha plugin/skill in the OVOS ecosystem
(ovos-wolfram-alpha-plugin / ovos-skill-wolfie) that covers unit
conversion as part of a much broader "single-answer question" tool -
but it requires an internet connection and a Wolfram Alpha API key.
Nothing in the ecosystem does just unit conversion, locally, with no
dependency on a cloud service. This fills that gap.
Install
pip install ovos-skill-convert
Usage
"convert 10 centimeters to inches"
"how many feet is 2 meters"
"konverter 10 centimeter til tommer" (Danish)
"hvor mange fod er 2 meter" (Danish)
Follow-up conversions ("what is that in meters?")
The skill listens for a number+unit spoken by any skill (not just itself) and remembers the last one for 60 seconds, so a follow-up works without repeating the value:
Other skill: "the distance to the Moon is 384400 kilometers"
You: "what is that in miles"
Skill: "384400.0 kilometers is 238855.0863 miles"
This also chains off the skill's own results:
You: "convert 10 centimeters to inches"
Skill: "10 centimeters is 3.937 inches"
You: "and what is that in feet"
Skill: "3.937 inches is 0.3281 feet"
If nothing recent was heard, it says so rather than guessing. See the
module docstring in __init__.py ("FOLLOW-UP CONVERSION") for the
known limitations of this heuristic (digit-written numbers only, no
scientific notation, locale-guessed decimal separator).
Status
Categories (19 of 21): length, mass, temperature, time, speed, area, volume, volume_dry (en-us only), angle, density, pressure, force, energy, power, torque, acceleration, concentration, computer, flow, light. Deliberately not implemented: "Custom" (the original program's user-defined-unit tab - doesn't map onto a voice interface).
Light was added after initially being written off entirely: pint
has no built-in foot-candle, but illuminance (lux vs foot-candle) is
a single interconvertible dimension, unlike luminous flux (lumen) or
intensity (candela) - a one-line custom unit definition
(UREG.define("foot_candle = lumen / foot ** 2 = fc")) was enough to
cover it. Lumen/candela conversions remain out of scope.
Languages:
| Language | Confidence |
|---|---|
en-us |
Reference implementation, ~300 aliases, verified against pint unit-by-unit |
da-dk |
Native-speaker level - every false-friend trap actively hunted, not just translated |
de-de, es-es, fr-fr, it-it, nl-nl, pt-pt |
First machine-translated pass, NOT native-speaker verified. Same category structure as en-us, smaller alias sets (~80 each), the highest-confidence false-friend patterns applied by generalizing from the da-dk findings (see below) - but not fact-checked with the same rigor. Treat as a starting point; each file's "_notes" key flags what's unverified. |
Every unit string used anywhere was checked against the installed
pint registry before being added (see DEVELOPMENT.md for the
verification method) - none of it is guessed, even in the lower-
confidence languages. What's not verified in the new 6 is whether
the chosen alias words match how people actually speak in each
language - that needs native-speaker review.
Unit aliases live in locale/<lang>/unit_aliases.json per language,
not hardcoded in Python - see "Adding a new unit category or language"
in DEVELOPMENT.md for how to add or improve a language via the same
ovos-localize workflow used for the rest of this project's locale
content.
False-friend traps found and handled
A few unit names collide across languages/contexts with a different
real-world value - each is called out in that language's
unit_aliases.json "_notes" key:
| Word | Wrong assumption | Correct handling |
|---|---|---|
| Danish "mil" | = English "mile" | It's the Scandinavian mile (10 km) - omitted, not mapped |
| Danish "ton" | = pint's bare ton (US short ton, 907 kg) |
Mapped to metric_ton (1000 kg) - matches everyday Danish usage |
| Danish "pund" | = imperial pound (453.6 g) | It's exactly 500 g historically - omitted, not mapped |
| Danish "hk"/"hestekraft" | = pint's bare horsepower |
Mapped to metric_horsepower (~1.4% different) |
cfm (as an alias) |
= cubic feet per minute | pint parses it as centi-fermi, an obscure length unit - spelled out in full instead |
| bare "g" | could mean gram or "free fall"/gravity | Only ever aliased to gram; acceleration uses "standard gravity"/"free fall" |
| bare "grader"/"degrees" (da/en) | could mean angle or temperature | Defaults to angle; temperature requires naming celsius/fahrenheit/kelvin explicitly |
oz vs fluid_ounce |
same English word, different dimension (mass vs volume) | Kept as separate keys, never merged |
| French "livre" | harmless if just omitted | Wasn't - fuzzy-matched "litre" (liter) at exactly the 0.80 threshold. Omitting a word from the alias table only blocks exact matching, not fuzzy matching or the raw-pint fallback below. |
| Dutch "pond" | harmless if just omitted | Wasn't - pint itself defines pond as an unrelated CGS force unit (gram-force), so the raw-pint fallback silently accepted it |
| Danish "mil" | harmless if just omitted | Resolves via the raw-pint fallback too - pint has "mil" as the dimensionless milli- SI prefix, so it doesn't crash, it just produces a confusing DimensionalityError instead of "I don't understand" |
The last three rows are the same kind of bug as the others, but a
category ahead: a word can be correctly excluded from the alias
table and still resolve to something wrong, via fuzzy matching or
the pint-string fallback. This is now handled with an explicit
per-language "_never_map" list in each unit_aliases.json - words
that must never resolve to anything, checked before either of those
two paths, not just absent from the main table. Every flagged word in
every language above (French/German/Italian/Portuguese "mile"-words,
"pound"-words, etc.) is in its language's _never_map, whether or not
it was actually provably dangerous - cheap insurance, and it documents
intent even where nothing currently breaks.
A startup consistency check (_load_unit_aliases_from_disk()) also
raises an error at import time if any two categories ever define the
same spoken alias with two different target units for the same
language - so a future collision like these can't ship silently.
Architecture: mostly not Common Query
This is primarily a regular intent skill (padatious), not a Common Query provider - Common Query exists to pick the best answer among multiple skills that might plausibly answer the same question, and unit conversion has exactly one correct answer with no competing sources to arbitrate between.
A narrow Common Query safety net (handle_common_query()) exists
alongside the intents anyway - live testing found that a platform-
level semantic router (ovos-m2v-pipeline-high) can intercept a
"what is X in Y"-shaped utterance before this skill's own Padatious
intent gets a chance, on installations where pipeline confidence
tuning differs from ours (see ovos-skill-geometry's DEVELOPMENT.md
for the full finding, filed upstream as
OpenVoiceOS/ovos-m2v-pipeline#68).
Deliberately narrow: only the "what is {value} {from_unit} in
{to_unit}" phrasing (the one line per language actually shaped like
a question) - not the imperative "convert X to Y" forms, and not the
context-dependent "what is that in X" follow-up (which depends on
mutable per-instance state that could be stale during a Common Query
race).
Unit resolution strategy
Spoken unit text is resolved in four steps:
- Check the language's
_never_mapfirst - if the word is on it, stop immediately and return "not understood", regardless of what the later steps would have matched. - Exact match against the current language's alias table.
- Fuzzy match (
match_one, threshold 0.8) against that same table - covers minor STT mishearings. - Fallback: try the raw text directly against
pint(covers someone literally saying a unit symbol like "cm").
If none of those resolve, the skill says so rather than guessing.
Development
See DEVELOPMENT.md.
Category
Utility
Tags
#converter #units #measurement #utility #offline
Release files for ovos-skill-convert 0.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ovos_skill_convert-0.0.6.tar.gz | 47.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ovos_skill_convert-0.0.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 105.5 kB
Release files / ovos_skill_convert-0.0.6.tar.gz
| Download URL | ovos_skill_convert-0.0.6.tar.gz |
|---|---|
| Size | 47.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
51c0e7377e1bc45f7b53f4c5c51e6fcc9d54e20e9223363b89c6ed9afdd3c326
|
|
BLAKE2b-256 checksum How to use checksums |
0f2def0cf2967e8d28a832ee05b020aa79ad3fa7b9254068b1ad32de757db14c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 17, 2026.
Transparency logRelease files / ovos_skill_convert-0.0.6-py3-none-any.whl
| Download URL | ovos_skill_convert-0.0.6-py3-none-any.whl |
|---|---|
| Size | 58.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
00013114ac4f9f6c757b0e1853a326e488041203332b264cc01d885b87f49596
|
|
BLAKE2b-256 checksum How to use checksums |
cd62abd9b4842c6a49b7961201526a8ee1657b387d6aaf642ac24f0a46c5e4aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 17, 2026.
Transparency log