Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ ITN converts spoken expressions into written forms, for example `十二` → `12
| German | `de` | Yes | Yes | `german` |
| Hindi | `hi` | Yes | Yes | `hindi` |
| Hindi–English (code-mixed) | `hi_en` | No | Yes | `hindi_english` |
| Italian | `it` | Yes | Yes | `italian` |
| Japanese | `ja` | Yes | Yes | `japanese` |
| Kinyarwanda | `rw` | Yes | Yes | `kinyarwanda` |
| Korean | `ko` | Yes | Yes | `korean` |
Expand Down
Empty file added itn/italian/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions itn/italian/data/electronic/domain.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.com com
.org org
.gov gov
.uk UK
.fr FR
.net net
.html html
.py python
.ru RU
.us US
.de DE
.it IT
.jpg jpeg
.edu edu
11 changes: 11 additions & 0 deletions itn/italian/data/electronic/server_name.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
gmail
nvidia
outlook
hotmail
yahoo
live
yandex
orange
wanadoo
web
comcast
22 changes: 22 additions & 0 deletions itn/italian/data/electronic/symbols.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
. punto
- trattino
_ trattino basso
! punto esclamativo
# cancelletto
$ dollaro
% percento
& e commerciale
' apostrofo
* asterisco
+ più
/ slash
= uguale
? punto interrogativo
^ esponente
` accento
{ apri graffa
| barra verticale
} chiudi graffa
~ tilde
, virgola
: due punti
4 changes: 4 additions & 0 deletions itn/italian/data/measure/denominators.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
s secondo
h ora
min minuto
d giorno
51 changes: 51 additions & 0 deletions itn/italian/data/measure/units.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
km chilometro chilometri
m metro metri
cm centimetro centimetri
mm millimetro millimetri
dm decimetro decimetri
μm micrometro micrometri
nm nanometro nanometri
ha ettaro ettari
mi miglio miglia
m² metro quadrato metri quadrati
km² chilometro quadrato chilometri quadrati
m³ metro cubo metri cubi
g grammo grammi
mg milligrammo milligrammi
μg microgrammo microgrammi
kg chilogrammo chilogrammi
kg chilo chili
t tonnellata tonnellate
l litro litri
ml millilitro millilitri
cl centilitro centilitri
dl decilitro decilitri
ms millisecondo millisecondi
Hz hertz hertz
kHz kilohertz kilohertz
MHz megahertz megahertz
GHz gigahertz gigahertz
V volt volt
mV millivolt millivolt
kV kilovolt kilovolt
W watt watt
kW kilowatt kilowatt
MW megawatt megawatt
GW gigawatt gigawatt
TW terawatt terawatt
A ampere ampere
mA milliampere milliampere
Ω ohm ohm
dB decibel decibel
kB kilobyte kilobyte
MB megabyte megabyte
GB gigabyte gigabyte
TB terabyte terabyte
kcal chilocaloria chilocalorie
cal caloria calorie
bar bar bar
°C grado celsius gradi celsius
°F grado fahrenheit gradi fahrenheit
K kelvin kelvin
% percento percento
% per cento per cento
11 changes: 11 additions & 0 deletions itn/italian/data/money/currency.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
€ euro euro centesimo centesimi
$ dollaro dollari centesimo centesimi
£ sterlina sterline penny penny
£ sterlina sterline penny pence
¥ yen yen
₽ rublo rubli copeca copeche
₹ rupia rupie paisa paise
USD dollaro statunitense dollari statunitensi centesimo centesimi
CAD dollaro canadese dollari canadesi centesimo centesimi
HKD dollaro di hong kong dollari di hong kong centesimo centesimi
CHF franco svizzero franchi svizzeri centesimo centesimi
9 changes: 9 additions & 0 deletions itn/italian/data/numbers/digit.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
1 uno
2 due
3 tre
4 quattro
5 cinque
6 sei
7 sette
8 otto
9 nove
9 changes: 9 additions & 0 deletions itn/italian/data/numbers/hundreds.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
1 cento
2 duecento
3 trecento
4 quattrocento
5 cinquecento
6 seicento
7 settecento
8 ottocento
9 novecento
4 changes: 4 additions & 0 deletions itn/italian/data/numbers/quantities.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
milione
milioni
miliardo
miliardi
10 changes: 10 additions & 0 deletions itn/italian/data/numbers/teen.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
10 dieci
11 undici
12 dodici
13 tredici
14 quattordici
15 quindici
16 sedici
17 diciassette
18 diciotto
19 diciannove
8 changes: 8 additions & 0 deletions itn/italian/data/numbers/tens.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
2 venti
3 trenta
4 quaranta
5 cinquanta
6 sessanta
7 settanta
8 ottanta
9 novanta
19 changes: 19 additions & 0 deletions itn/italian/data/whitelist.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
dott. dottor
dott.ssa dottoressa
prof. professore
prof.ssa professoressa
sig.ra signora
sig.na signorina
avv. avvocato
arch. architetto
ing. ingegnere
rag. ragioniere
geom. geometra
on. onorevole
rev. reverendo
sen. senatore
ecc. eccetera
v.le viale
p.za piazza
dip. dipartimento
c.v.d. come volevasi dimostrare
77 changes: 77 additions & 0 deletions itn/italian/inverse_normalizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright (c) 2026 Zhendong Peng ([email protected])
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pynini
from pynini.lib.pynutil import add_weight

from itn.italian.rules import money
from itn.italian.rules.cardinal import Cardinal
from itn.italian.rules.decimal import Decimal
from itn.italian.rules.electronic import Electronic
from itn.italian.rules.measure import Measure
from itn.italian.rules.money import Money
from itn.italian.rules.punctuation import Punctuation
from itn.italian.rules.time import Time
from itn.italian.rules.whitelist import WhiteList
from itn.italian.rules.word import Word
from tn.processor import Processor, RuleSpec

TOKEN_ORDERS = dict(money.TOKEN_ORDER)

# Written punctuation that attaches to the previous / next token.
CLOSING = ".,;:!?)}"
OPENING = "({"


class InverseNormalizer(Processor):

def __init__(self, cache_dir=None, overwrite_cache=False):
super().__init__(name="it_inverse_normalizer", ordertype="itn", token_orders=TOKEN_ORDERS)
self.build_fst("it_itn", cache_dir, overwrite_cache, {})

def build_tagger_and_verbalizer(self):
cardinal = Cardinal()
decimal = Decimal(cardinal)
punctuation = Punctuation()
word = Word()
rules = (
RuleSpec(WhiteList(), 1.01),
RuleSpec(Time(), 1.05),
RuleSpec(Money(cardinal, decimal), 1.07),
RuleSpec(decimal, 1.08),
RuleSpec(Measure(cardinal, decimal), 1.08),
RuleSpec(cardinal, 1.1),
RuleSpec(Electronic(), 1.1),
RuleSpec(word, 100),
RuleSpec(punctuation, 1.1, verbalize=False),
)
classify = self.tagger_union(rules)
# Punctuation splits off only when attached to another token.
punct = add_weight(punctuation.tagger, 1.1)
token = punct.star + classify + punct.star
self.tagger = self.DELETE_SPACE + token + (self.DELETE_EXTRA_SPACE + token).star + self.DELETE_SPACE
self.tagger |= word.add_tokens(word.tag_field("value", self.SPACE.plus))

def marks(chars):
graph = pynini.union(*[pynini.escape(char) for char in chars])
return punctuation.delete_tokens(punctuation.verbalize_field("value", graph))

other_marks = [mark for mark in "!#$%&'()*+,-./:;<=>?@^_`{|}~" if mark not in CLOSING + OPENING]
closing = marks(CLOSING)
opening = marks(OPENING)
other = self.verbalizer_union(rules) | marks(other_marks)
# Written Italian attaches closing marks to the previous token and
# opening marks to the next one: ``( cinque ) .`` -> ``(5).``.
item = opening.star + other | add_weight(opening.plus, 1.0)
self.verbalizer = (item | closing) + (closing | self.INSERT_SPACE + item).star
Empty file added itn/italian/rules/__init__.py
Empty file.
100 changes: 100 additions & 0 deletions itn/italian/rules/cardinal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Copyright (c) 2026 Zhendong Peng ([email protected])
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pynini
from pynini.lib.pynutil import delete, insert

from itn.italian.rules.utils import load_rows, string_map, table_map
from tn.processor import Processor


def two_digit_pairs():
"""Returns ``(written, spoken)`` pairs for 1..99 with two-digit written forms.

Tens elide their final vowel before ``uno``/``otto`` (``ventuno``,
``ventotto``); compounds of ``tre`` accept both ``ventitre`` and
``ventitré``.
"""

units = {int(written): spoken for written, spoken in load_rows("italian/data/numbers/digit.tsv")}
pairs = [("0{}".format(value), spoken) for value, spoken in units.items()]
pairs += [(written, spoken) for written, spoken in load_rows("italian/data/numbers/teen.tsv")]
for written, ten in load_rows("italian/data/numbers/tens.tsv"):
pairs.append((written + "0", ten))
for value, unit in units.items():
stem = ten[:-1] if unit[0] in "uo" else ten
pairs.append((written + str(value), stem + unit))
if unit == "tre":
pairs.append((written + str(value), stem + "tré"))
return pairs


class Cardinal(Processor):
"""Cardinals, e.g. ``tremila cinquecento quarantaquattro`` -> ``3544``.

The tagger keeps the spoken words; the verbalizer writes the digits.
Components may be joined (``centocinquantacinque``) or spaced (``cento
cinquantacinque``). ``miliardo`` is 10^9; NeMo's TN style of counting
thousands of millions (``tremila ... milioni``) is accepted too.
"""

def __init__(self):
super().__init__("cardinal", token_orders={})
self.build_graphs()
self.sign = pynini.cross("meno", "-")
sign_tag = (self.tag_field("negative", self.sign) + pynini.accep(" ")).ques
sign_verb = (self.verbalize_field("negative", self.sign) + delete(" ")).ques
self.tagger = self.add_tokens(sign_tag + self.tag_field("integer", self.graph))
self.verbalizer = self.delete_tokens(sign_verb + self.verbalize_field("integer", self.graph))

def build_graphs(self):
digit = self.DIGIT
join = delete(" ").ques
space = delete(" ")
two = string_map(two_digit_pairs())
hundreds = table_map("italian/data/numbers/hundreds.tsv")
# ``centotto``/``centottanta`` drop the final ``o`` of the hundred.
elided = string_map([(written, spoken[:-1]) for written, spoken in load_rows("italian/data/numbers/hundreds.tsv")])
two_ott = ("ott" + self.VSIGMA) @ two
three = (
insert("0") + two
| hundreds + insert("00")
| hundreds + join + two
| elided + two_ott
).optimize()
three_no_one = (three @ (pynini.closure(digit, 3, 3) - "001")).optimize()
rest3 = insert("000") | join + three
six = (
insert("000") + three
| pynini.cross("mille", "001") + rest3
| three_no_one + join + delete("mila") + rest3
).optimize()
rest6 = insert("000000") | space + six
millions = pynini.cross("un milione", "001") | three_no_one + delete(" milioni")
billions = pynini.cross("un miliardo", "001") | three_no_one + delete(" miliardi")
thousand_millions = six @ ((pynini.closure(digit, 3, 3) - "000") + pynini.closure(digit, 3, 3))
padded = (
billions + (space + millions | insert("000")) + rest6
| insert("000") + millions + rest6
| insert("000000") + six
| thousand_millions + delete(" milioni") + rest6
)
strip = delete("0").star + (digit - "0") + digit.star
self.number = (pynini.cross("zero", "0") | padded @ strip).optimize()
self.graph = self.number
self.two = two
# Unpadded readings below one thousand, e.g. for decimal digit groups.
self.below_thousand = (pynini.cross("zero", "0") | three @ strip).optimize()
# Counting contexts (measures, money) also say ``un``/``una`` for 1.
self.counted = (self.number | pynini.cross(pynini.union("un", "una"), "1")).optimize()
Loading
Loading