diff --git a/Jenkinsfile b/Jenkinsfile
index 38e05bb00..bf42730ea 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -30,6 +30,7 @@ pipeline {
JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1'
HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-04-26-5'
KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-04-25-6'
+ TA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/07-21-26-0'
DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0'
}
stages {
@@ -113,6 +114,24 @@ pipeline {
}
}
+ stage('L0: Create TA TN Grammars') {
+ when {
+ anyOf {
+ branch 'main'
+ branch 'staging/**'
+ branch 'staging_*'
+ changeRequest target: 'main'
+ }
+ }
+ failFast true
+ parallel {
+ stage('L0: TA TN grammars') {
+ steps{
+ sh 'CUDA_VISIBLE_DEVICES="" python nemo_text_processing/text_normalization/normalize.py --lang=ta --text="௧" --cache_dir ${TA_TN_CACHE}'
+ }
+ }
+ }
+ }
stage('L0: Create DE/ES TN/ITN Grammars') {
when {
anyOf {
@@ -408,6 +427,11 @@ pipeline {
sh 'CUDA_VISIBLE_DEVICES="" pytest tests/nemo_text_processing/hi/ -m "not pleasefixme" --cpu --tn_cache_dir ${HI_TN_CACHE}'
}
}
+ stage('L1: Run all TA TN tests (restore grammars from cache)') {
+ steps {
+ sh 'CUDA_VISIBLE_DEVICES="" pytest tests/nemo_text_processing/ta/ -m "not pleasefixme" --cpu --tn_cache_dir ${TA_TN_CACHE}'
+ }
+ }
stage('L1: Run all Codeswitched ES/EN TN/ITN tests (restore grammars from cache)') {
steps {
sh 'CUDA_VISIBLE_DEVICES="" pytest tests/nemo_text_processing/es_en/ -m "not pleasefixme" --cpu --tn_cache_dir ${ES_EN_TN_CACHE}'
diff --git a/nemo_text_processing/text_normalization/normalize.py b/nemo_text_processing/text_normalization/normalize.py
index d8ebf2f4d..a7e001652 100644
--- a/nemo_text_processing/text_normalization/normalize.py
+++ b/nemo_text_processing/text_normalization/normalize.py
@@ -166,6 +166,7 @@ def __init__(
if post_process:
self.post_processor = PostProcessingFst(cache_dir=cache_dir, overwrite_cache=overwrite_cache)
+
elif lang == 'it':
from nemo_text_processing.text_normalization.it.taggers.tokenize_and_classify import ClassifyFst
from nemo_text_processing.text_normalization.it.verbalizers.verbalize_final import VerbalizeFinalFst
@@ -191,6 +192,10 @@ def __init__(
elif lang == 'ko':
from nemo_text_processing.text_normalization.ko.taggers.tokenize_and_classify import ClassifyFst
from nemo_text_processing.text_normalization.ko.verbalizers.verbalize_final import VerbalizeFinalFst
+ elif lang == 'ta':
+ from nemo_text_processing.text_normalization.ta.taggers.tokenize_and_classify import ClassifyFst
+ from nemo_text_processing.text_normalization.ta.verbalizers.verbalize_final import VerbalizeFinalFst
+
else:
raise NotImplementedError(f"Language {lang} has not been supported yet.")
@@ -737,7 +742,7 @@ def parse_args():
parser.add_argument(
"--language",
help="language",
- choices=["en", "de", "es", "fr", "hu", "sv", "zh", "ar", "it", "hy", "ja", "hi", "ko", "vi", "pt"],
+ choices=["en", "de", "es", "fr", "hu", "sv", "zh", "ar", "it", "hy", "ja", "hi", "ta", "ko", "vi", "pt"],
default="en",
type=str,
)
diff --git a/nemo_text_processing/text_normalization/run_evaluate.py b/nemo_text_processing/text_normalization/run_evaluate.py
index 3a1964bbd..cf0b6efb2 100644
--- a/nemo_text_processing/text_normalization/run_evaluate.py
+++ b/nemo_text_processing/text_normalization/run_evaluate.py
@@ -35,7 +35,7 @@ def parse_args():
parser.add_argument(
"--lang",
help="language",
- choices=['ar', 'de', 'en', 'es', 'fr', 'hu', 'it', 'ru', 'sv', 'zh', 'hy', 'hi', 'ko', 'vi', 'pt'],
+ choices=['ar', 'de', 'en', 'es', 'fr', 'hu', 'it', 'ru', 'sv', 'zh', 'hy', 'hi', 'ta', 'ko', 'vi', 'pt'],
default="en",
type=str,
)
diff --git a/nemo_text_processing/text_normalization/ta/__init__.py b/nemo_text_processing/text_normalization/ta/__init__.py
new file mode 100644
index 000000000..4fc25d0d3
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/__init__.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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.
diff --git a/nemo_text_processing/text_normalization/ta/data/__init__.py b/nemo_text_processing/text_normalization/ta/data/__init__.py
new file mode 100644
index 000000000..4fc25d0d3
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/__init__.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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.
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/__init__.py b/nemo_text_processing/text_normalization/ta/data/numbers/__init__.py
new file mode 100644
index 000000000..4fc25d0d3
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/__init__.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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.
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/digit.tsv b/nemo_text_processing/text_normalization/ta/data/numbers/digit.tsv
new file mode 100644
index 000000000..92111ede3
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/digit.tsv
@@ -0,0 +1,18 @@
+௧ ஒன்று
+௨ இரண்டு
+௩ மூன்று
+௪ நான்கு
+௫ ஐந்து
+௬ ஆறு
+௭ ஏழு
+௮ எட்டு
+௯ ஒன்பது
+1 ஒன்று
+2 இரண்டு
+3 மூன்று
+4 நான்கு
+5 ஐந்து
+6 ஆறு
+7 ஏழு
+8 எட்டு
+9 ஒன்பது
\ No newline at end of file
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/hundred_stem.tsv b/nemo_text_processing/text_normalization/ta/data/numbers/hundred_stem.tsv
new file mode 100644
index 000000000..5db400128
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/hundred_stem.tsv
@@ -0,0 +1,18 @@
+1 நூ
+2 இருநூ
+3 முந்நூ
+4 நானூ
+5 ஐநூ
+6 அறுநூ
+7 எழுநூ
+8 எட்டுநூ
+9 ஒன்பதுநூ
+௧ நூ
+௨ இருநூ
+௩ முந்நூ
+௪ நானூ
+௫ ஐநூ
+௬ அறுநூ
+௭ எழுநூ
+௮ எட்டுநூ
+௯ ஒன்பதுநூ
\ No newline at end of file
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/scale.tsv b/nemo_text_processing/text_normalization/ta/data/numbers/scale.tsv
new file mode 100644
index 000000000..9c8efb0bc
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/scale.tsv
@@ -0,0 +1,10 @@
+hundred_suf_e று
+hundred_suf_p ற்று
+thousand_suf_e ம்
+thousand_suf_p த்து
+thousand_word_e ஆயிரம்
+thousand_word_p ஆயிரத்து
+lakh_word_e லட்சம்
+lakh_word_p லட்சத்து
+crore_word_e கோடி
+crore_word_p கோடியே
\ No newline at end of file
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/special_units.tsv b/nemo_text_processing/text_normalization/ta/data/numbers/special_units.tsv
new file mode 100644
index 000000000..8c2ce8bb6
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/special_units.tsv
@@ -0,0 +1,4 @@
+1 ஒரு
+௧ ஒரு
+2 இரண்டு
+௨ இரண்டு
\ No newline at end of file
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/teens_and_ties.tsv b/nemo_text_processing/text_normalization/ta/data/numbers/teens_and_ties.tsv
new file mode 100644
index 000000000..7c73802d2
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/teens_and_ties.tsv
@@ -0,0 +1,68 @@
+10 பத்து
+11 பதினொன்று
+12 பன்னிரண்டு
+13 பதிமூன்று
+14 பதினான்கு
+15 பதினைந்து
+16 பதினாறு
+17 பதினேழு
+18 பதினெட்டு
+19 பத்தொன்பது
+20 இருபது
+21 இருபத்தொன்று
+22 இருபத்திரண்டு
+30 முப்பது
+31 முப்பத்தொன்று
+32 முப்பத்திரண்டு
+40 நாற்பது
+41 நாற்பத்தொன்று
+42 நாற்பத்திரண்டு
+50 ஐம்பது
+51 ஐம்பத்தொன்று
+52 ஐம்பத்திரண்டு
+60 அறுபது
+61 அறுபத்தொன்று
+62 அறுபத்திரண்டு
+70 எழுபது
+71 எழுபத்தொன்று
+72 எழுபத்திரண்டு
+80 எண்பது
+81 எண்பத்தொன்று
+82 எண்பத்திரண்டு
+90 தொண்ணூறு
+91 தொண்ணூற்றொன்று
+92 தொண்ணூற்றிரண்டு
+௧௦ பத்து
+௧௧ பதினொன்று
+௧௨ பன்னிரண்டு
+௧௩ பதிமூன்று
+௧௪ பதினான்கு
+௧௫ பதினைந்து
+௧௬ பதினாறு
+௧௭ பதினேழு
+௧௮ பதினெட்டு
+௧௯ பத்தொன்பது
+௨௦ இருபது
+௨௧ இருபத்தொன்று
+௨௨ இருபத்திரண்டு
+௩௦ முப்பது
+௩௧ முப்பத்தொன்று
+௩௨ முப்பத்திரண்டு
+௪௦ நாற்பது
+௪௧ நாற்பத்தொன்று
+௪௨ நாற்பத்திரண்டு
+௫௦ ஐம்பது
+௫௧ ஐம்பத்தொன்று
+௫௨ ஐம்பத்திரண்டு
+௬௦ அறுபது
+௬௧ அறுபத்தொன்று
+௬௨ அறுபத்திரண்டு
+௭௦ எழுபது
+௭௧ எழுபத்தொன்று
+௭௨ எழுபத்திரண்டு
+௮௦ எண்பது
+௮௧ எண்பத்தொன்று
+௮௨ எண்பத்திரண்டு
+௯௦ தொண்ணூறு
+௯௧ தொண்ணூற்றொன்று
+௯௨ தொண்ணூற்றிரண்டு
\ No newline at end of file
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/tens_stem.tsv b/nemo_text_processing/text_normalization/ta/data/numbers/tens_stem.tsv
new file mode 100644
index 000000000..8fd7ee904
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/tens_stem.tsv
@@ -0,0 +1,16 @@
+2 இருபத்தி
+3 முப்பத்தி
+4 நாற்பத்தி
+5 ஐம்பத்தி
+6 அறுபத்தி
+7 எழுபத்தி
+8 எண்பத்தி
+9 தொண்ணூற்றி
+௨ இருபத்தி
+௩ முப்பத்தி
+௪ நாற்பத்தி
+௫ ஐம்பத்தி
+௬ அறுபத்தி
+௭ எழுபத்தி
+௮ எண்பத்தி
+௯ தொண்ணூற்றி
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/thousand.tsv b/nemo_text_processing/text_normalization/ta/data/numbers/thousand.tsv
new file mode 100644
index 000000000..1a16b1271
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/thousand.tsv
@@ -0,0 +1,18 @@
+1 ஆயிர
+2 இரண்டாயிர
+3 மூவாயிர
+4 நான்காயிர
+5 ஐந்தாயிர
+6 ஆறாயிர
+7 ஏழாயிர
+8 எட்டாயிர
+9 ஒன்பதாயிர
+௧ ஆயிர
+௨ இரண்டாயிர
+௩ மூவாயிர
+௪ நான்காயிர
+௫ ஐந்தாயிர
+௬ ஆறாயிர
+௭ ஏழாயிர
+௮ எட்டாயிர
+௯ ஒன்பதாயிர
\ No newline at end of file
diff --git a/nemo_text_processing/text_normalization/ta/data/numbers/zero.tsv b/nemo_text_processing/text_normalization/ta/data/numbers/zero.tsv
new file mode 100644
index 000000000..c5011b29f
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/data/numbers/zero.tsv
@@ -0,0 +1,2 @@
+௦ சுழியம்
+0 சுழியம்
\ No newline at end of file
diff --git a/nemo_text_processing/text_normalization/ta/graph_utils.py b/nemo_text_processing/text_normalization/ta/graph_utils.py
new file mode 100644
index 000000000..2598f4415
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/graph_utils.py
@@ -0,0 +1,129 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 logging
+import os
+from pathlib import Path
+from typing import Dict
+
+import pynini
+from pynini import Far
+from pynini.export import export
+from pynini.lib import byte, pynutil, utf8
+
+from nemo_text_processing.text_normalization.ta.utils import get_abs_path
+
+NEMO_CHAR = utf8.VALID_UTF8_CHAR
+
+ta_digit_map = pynini.string_file(get_abs_path("data/numbers/digit.tsv"))
+ta_zero_map = pynini.string_file(get_abs_path("data/numbers/zero.tsv"))
+
+ta_digit_only = pynini.project(ta_digit_map, "input").optimize()
+NEMO_ALL_ZERO = pynini.project(ta_zero_map, "input").optimize()
+NEMO_ALL_DIGIT = pynini.union(ta_digit_only, NEMO_ALL_ZERO).optimize()
+
+NEMO_NON_BREAKING_SPACE = u"\u00a0"
+NEMO_SPACE = " "
+NEMO_WHITE_SPACE = pynini.union(" ", "\t", "\n", "\r", u"\u00a0").optimize()
+NEMO_NOT_SPACE = pynini.difference(NEMO_CHAR, NEMO_WHITE_SPACE).optimize()
+NEMO_NOT_QUOTE = pynini.difference(NEMO_CHAR, r'"').optimize()
+NEMO_SIGMA = pynini.closure(NEMO_CHAR)
+
+delete_space = pynutil.delete(pynini.closure(NEMO_WHITE_SPACE))
+insert_space = pynutil.insert(" ")
+delete_extra_space = pynini.cross(pynini.closure(NEMO_WHITE_SPACE, 1), " ")
+
+
+def generator_main(file_name: str, graphs: Dict[str, 'pynini.FstLike']):
+ """
+ Exports graph as OpenFst finite state archive (FAR) file with given file name and rule name.
+
+ Args:
+ file_name: exported file name
+ graphs: Mapping of a rule name and Pynini WFST graph to be exported
+ """
+ exporter = export.Exporter(file_name)
+ for rule, graph in graphs.items():
+ exporter[rule] = graph.optimize()
+ exporter.close()
+ logging.info(f'Created {file_name}')
+
+
+class GraphFst:
+ """
+ Base class for all grammar fsts.
+
+ Args:
+ name: name of grammar class
+ kind: either 'classify' or 'verbalize'
+ deterministic: if True will provide a single transduction option,
+ for False multiple transduction are generated (used for audio-based normalization)
+ """
+
+ def __init__(self, name: str, kind: str, deterministic: bool = True):
+ self.name = name
+ self.kind = kind
+ self._fst = None
+ self.deterministic = deterministic
+
+ self.far_path = Path(os.path.dirname(__file__) + '/grammars/' + kind + '/' + name + '.far')
+ if self.far_exist():
+ self._fst = Far(self.far_path, mode="r", arc_type="standard", far_type="default").get_fst()
+
+ def far_exist(self) -> bool:
+ """
+ Returns true if FAR can be loaded
+ """
+ return self.far_path.exists()
+
+ @property
+ def fst(self) -> 'pynini.FstLike':
+ return self._fst
+
+ @fst.setter
+ def fst(self, fst):
+ self._fst = fst
+
+ def add_tokens(self, fst) -> 'pynini.FstLike':
+ """
+ Wraps class name around to given fst
+
+ Args:
+ fst: input fst
+
+ Returns:
+ Fst: fst
+ """
+ return pynutil.insert(f"{self.name} {{ ") + fst + pynutil.insert(" }")
+
+ def delete_tokens(self, fst) -> 'pynini.FstLike':
+ """
+ Deletes class name wrap around output of given fst
+
+ Args:
+ fst: input fst
+
+ Returns:
+ Fst: fst
+ """
+ res = (
+ pynutil.delete(f"{self.name}")
+ + delete_space
+ + pynutil.delete("{")
+ + delete_space
+ + fst
+ + delete_space
+ + pynutil.delete("}")
+ )
+ return res @ pynini.cdrewrite(pynini.cross(u"\u00a0", " "), "", "", NEMO_SIGMA)
diff --git a/nemo_text_processing/text_normalization/ta/taggers/__init__.py b/nemo_text_processing/text_normalization/ta/taggers/__init__.py
new file mode 100644
index 000000000..4fc25d0d3
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/taggers/__init__.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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.
diff --git a/nemo_text_processing/text_normalization/ta/taggers/cardinal.py b/nemo_text_processing/text_normalization/ta/taggers/cardinal.py
new file mode 100644
index 000000000..f43311b66
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/taggers/cardinal.py
@@ -0,0 +1,184 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 import pynutil
+
+from nemo_text_processing.text_normalization.ta.graph_utils import (
+ NEMO_ALL_DIGIT,
+ NEMO_ALL_ZERO,
+ GraphFst,
+ insert_space,
+)
+from nemo_text_processing.text_normalization.ta.utils import get_abs_path
+
+
+class CardinalFst(GraphFst):
+ """
+ Finite state transducer for classifying cardinals
+ e.g. 23 -> cardinal { integer: "இருபத்திமூன்று" }
+ "9999999999999999" -> cardinal { integer: "தொண்ணூற்றிஒன்பது கோடியே தொண்ணூற்றிஒன்பது லட்சத்து
+ தொண்ணூற்றிஒன்பதுஆயிரத்து ஒன்பதுநூற்று தொண்ணூற்றிஒன்பது கோடியே தொண்ணூற்றிஒன்பது லட்சத்து
+ தொண்ணூற்றிஒன்பதுஆயிரத்து ஒன்பதுநூற்று தொண்ணூற்றிஒன்பது" }
+
+ Covers up to 16 digits (max 9999999999999999, just under 10^16),
+ via composed கோடி (crore) groups.
+
+ Args:
+ deterministic: if True will provide a single transduction option,
+ for False multiple transduction are generated (used for audio-based normalization)
+ """
+
+ def __init__(self, deterministic: bool = True, lm: bool = False):
+ super().__init__(name="cardinal", kind="classify", deterministic=deterministic)
+
+ digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv"))
+ zero = pynini.string_file(get_abs_path("data/numbers/zero.tsv"))
+
+ def _extract_word(fst, name):
+ return next(iter(pynini.compose(pynini.accep(name), fst).paths().ostrings()))
+
+ scale = pynini.string_file(get_abs_path("data/numbers/scale.tsv"))
+
+ special_units = pynini.string_file(get_abs_path("data/numbers/special_units.tsv"))
+ special_units_input = pynini.project(special_units, "input")
+ digit_oru = (
+ special_units | pynini.compose(pynini.difference(NEMO_ALL_DIGIT, special_units_input), digit)
+ ).optimize()
+
+ # TEENS_AND_TIES (10-99)
+ teens_and_ties_literal = pynini.string_file(get_abs_path("data/numbers/teens_and_ties.tsv"))
+ tens_connector_stem = pynini.string_file(get_abs_path("data/numbers/tens_stem.tsv"))
+ digit_3_to_9 = pynini.compose(pynini.difference(NEMO_ALL_DIGIT, special_units_input), digit).optimize()
+ teens_and_ties_compositional = tens_connector_stem + digit_3_to_9
+
+ teens_and_ties = pynini.union(teens_and_ties_literal, teens_and_ties_compositional).optimize()
+
+ # HUNDREDS:
+ hundred_stem = pynini.string_file(get_abs_path("data/numbers/hundred_stem.tsv"))
+ hundred_suf_e = _extract_word(scale, "hundred_suf_e")
+ hundred_suf_p = _extract_word(scale, "hundred_suf_p")
+ hundred_exact = hundred_stem + pynutil.delete(NEMO_ALL_ZERO) ** 2 + pynutil.insert(hundred_suf_e)
+ hundred_prefix = (hundred_stem + pynutil.insert(hundred_suf_p)).optimize()
+
+ # ஆயிரம் (exact) and ஆயிரத்து (combining) share the same stem
+ thousand_stem = pynini.string_file(get_abs_path("data/numbers/thousand.tsv"))
+ thousand_suf_e = _extract_word(scale, "thousand_suf_e")
+ thousand_suf_p = _extract_word(scale, "thousand_suf_p")
+ thousand_exact = thousand_stem + pynutil.insert(thousand_suf_e)
+ thousand_prefix = thousand_stem + pynutil.insert(thousand_suf_p)
+
+ single_digit = digit | zero
+ self.single_digits_graph = single_digit + pynini.closure(insert_space + single_digit)
+ zero_del = pynutil.delete(NEMO_ALL_ZERO)
+
+ def zdel(k):
+ return zero_del**k if k > 0 else pynini.accep("")
+
+ def scale_fn(head_exact, head_tail, n, tails):
+ graph = head_exact
+ for i, sub in enumerate(tails):
+ graph |= head_tail + zdel(n - 1 - i) + insert_space + sub
+ return graph.optimize()
+
+ def band(base, exact_word, tail_word, n, tails):
+ return scale_fn(base + pynutil.insert(exact_word) + zdel(n), base + pynutil.insert(tail_word), n, tails)
+
+ # HUNDREDS (100-999): நூறு / நூற்று forms.
+ graph_hundreds = scale_fn(hundred_exact, hundred_prefix, 2, [digit, teens_and_ties])
+ self.graph_hundreds = graph_hundreds
+
+ # THOUSANDS (1000-9999): ஆயிரம் / ஆயிரத்து forms
+ graph_thousands = scale_fn(
+ thousand_exact + zdel(3), thousand_prefix, 3, [digit, teens_and_ties, graph_hundreds]
+ )
+ self.graph_thousands = graph_thousands
+ tails = [digit, teens_and_ties, graph_hundreds, graph_thousands]
+
+ thousand_word = _extract_word(scale, "thousand_word_e")
+ thousand_prefix_word = _extract_word(scale, "thousand_word_p")
+ lakh_word = " " + _extract_word(scale, "lakh_word_e")
+ lakh_prefix_word = " " + _extract_word(scale, "lakh_word_p")
+ crore_word = " " + _extract_word(scale, "crore_word_e")
+ crore_prefix_word = " " + _extract_word(scale, "crore_word_p")
+
+ def add_scale(base, exact_word, prefix_word, n, tail_slice):
+ """band() + append-to-tails for the common single-branch case."""
+ g = band(base, exact_word, prefix_word, n, tails[:tail_slice])
+ tails.append(g)
+ return g
+
+ # TEN-THOUSANDS (10^4): stem + ஆயிரம்
+ graph_ten_thousands = add_scale(teens_and_ties, thousand_word, thousand_prefix_word, 3, 3)
+ self.graph_ten_thousands = graph_ten_thousands
+
+ # LAKHS / TEN-LAKHS (10^5, 10^6): stem + லட்சம்
+ graph_lakhs = band(digit_oru, lakh_word, lakh_prefix_word, 5, tails[:5])
+ self.graph_lakhs = graph_lakhs
+ graph_ten_lakhs = band(teens_and_ties, lakh_word, lakh_prefix_word, 5, tails[:5])
+ self.graph_ten_lakhs = graph_ten_lakhs
+ tails += [graph_lakhs, graph_ten_lakhs]
+
+ # CRORES and higher (10^7 .. 10^15): stem + கோடி
+ crore_bases = [
+ digit_oru, # crores
+ teens_and_ties, # ten-crores
+ graph_hundreds, # hundreds of crores
+ graph_thousands, # thousands of crores
+ graph_ten_thousands, # ten-thousands of crores
+ graph_lakhs, # lakhs of crores
+ graph_ten_lakhs, # ten-lakhs of crores
+ ]
+ crore_graphs = [band(b, crore_word, crore_prefix_word, 7, tails) for b in crore_bases]
+ graph_crores, graph_ten_crores = crore_graphs[0], crore_graphs[1]
+ crore_graphs += [
+ band(graph_crores, crore_word, crore_prefix_word, 7, tails), # crores of crores
+ band(graph_ten_crores, crore_word, crore_prefix_word, 7, tails), # ten-crores of crores
+ ]
+
+ # FINAL GRAPH
+ graph_without_leading_zeros = pynini.union(
+ digit,
+ zero,
+ teens_and_ties,
+ graph_hundreds,
+ graph_thousands,
+ graph_ten_thousands,
+ graph_lakhs,
+ graph_ten_lakhs,
+ *crore_graphs,
+ )
+ self.graph_without_leading_zeros = graph_without_leading_zeros.optimize()
+
+ cardinal_with_leading_zeros = pynini.compose(
+ NEMO_ALL_ZERO + pynini.closure(NEMO_ALL_DIGIT), self.single_digits_graph
+ )
+
+ delete_comma = pynutil.delete(",")
+ digit3, digit2 = NEMO_ALL_DIGIT**3, NEMO_ALL_DIGIT**2
+
+ western_format = pynini.closure(NEMO_ALL_DIGIT, 1, 3) + pynini.closure(delete_comma + digit3, 1)
+ indian_format = (
+ pynini.closure(NEMO_ALL_DIGIT, 1, 2) + pynini.closure(delete_comma + digit2) + delete_comma + digit3
+ )
+ comma_number = western_format | indian_format
+ cardinal_with_commas = pynini.compose(comma_number, graph_without_leading_zeros)
+
+ self.final_graph = (
+ graph_without_leading_zeros | cardinal_with_leading_zeros | cardinal_with_commas
+ ).optimize()
+
+ optional_minus_graph = pynini.closure(pynutil.insert("negative: ") + pynini.cross("-", "\"true\" "), 0, 1)
+ final_graph = optional_minus_graph + pynutil.insert("integer: \"") + self.final_graph + pynutil.insert("\"")
+ final_graph = self.add_tokens(final_graph)
+ self.fst = final_graph.optimize()
diff --git a/nemo_text_processing/text_normalization/ta/taggers/punctuation.py b/nemo_text_processing/text_normalization/ta/taggers/punctuation.py
new file mode 100644
index 000000000..82aa82c93
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/taggers/punctuation.py
@@ -0,0 +1,61 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 sys
+from unicodedata import category
+
+import pynini
+from pynini.examples import plurals
+from pynini.lib import pynutil
+
+from nemo_text_processing.text_normalization.ta.graph_utils import NEMO_NOT_SPACE, NEMO_SIGMA, GraphFst
+
+
+class PunctuationFst(GraphFst):
+ """
+ Finite state transducer for classifying punctuation
+ e.g. a, -> tokens { name: "a" } tokens { name: "," }
+
+ Args:
+ deterministic: if True will provide a single transduction option,
+ for False multiple transductions are generated (used for audio-based normalization)
+ """
+
+ def __init__(self, deterministic: bool = True):
+ super().__init__(name="punctuation", kind="classify", deterministic=deterministic)
+ s = "!#%&\'()*+,-./:;<=>?@^_`{|}~\""
+
+ punct_symbols_to_exclude = ["[", "]"]
+ punct_unicode = [
+ chr(i)
+ for i in range(sys.maxunicode)
+ if category(chr(i)).startswith("P") and chr(i) not in punct_symbols_to_exclude
+ ]
+
+ self.punct_marks = [p for p in punct_unicode + list(s)]
+
+ punct = pynini.union(*self.punct_marks)
+ punct = pynini.closure(punct, 1)
+
+ emphasis = (
+ pynini.accep("<")
+ + pynini.union(
+ (pynini.closure(NEMO_NOT_SPACE - pynini.union("<", ">"), 1) + pynini.closure(pynini.accep("/"), 0, 1)),
+ (pynini.accep("/") + pynini.closure(NEMO_NOT_SPACE - pynini.union("<", ">"), 1)),
+ )
+ + pynini.accep(">")
+ )
+ punct = plurals._priority_union(emphasis, punct, NEMO_SIGMA)
+
+ self.graph = punct
+ self.fst = (pynutil.insert("name: \"") + self.graph + pynutil.insert("\"")).optimize()
diff --git a/nemo_text_processing/text_normalization/ta/taggers/tokenize_and_classify.py b/nemo_text_processing/text_normalization/ta/taggers/tokenize_and_classify.py
new file mode 100644
index 000000000..af2adf3ba
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/taggers/tokenize_and_classify.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 logging
+import os
+
+import pynini
+from pynini.lib import pynutil
+
+from nemo_text_processing.text_normalization.ta.graph_utils import (
+ NEMO_SPACE,
+ NEMO_WHITE_SPACE,
+ GraphFst,
+ delete_extra_space,
+ delete_space,
+ generator_main,
+)
+from nemo_text_processing.text_normalization.ta.taggers.cardinal import CardinalFst
+from nemo_text_processing.text_normalization.ta.taggers.punctuation import PunctuationFst
+from nemo_text_processing.text_normalization.ta.taggers.word import WordFst
+
+
+class ClassifyFst(GraphFst):
+ """
+ Final class that composes all other classification grammars. This class can process an entire sentence including punctuation.
+ For deployment, this grammar will be compiled and exported to OpenFst Finite State Archive (FAR) File.
+ More details to deployment at NeMo/tools/text_processing_deployment.
+
+ Args:
+ input_case: accepting either "lower_cased" or "cased" input.
+ deterministic: if True will provide a single transduction option,
+ for False multiple options (used for audio-based normalization)
+ cache_dir: path to a dir with .far grammar file. Set to None to avoid using cache.
+ overwrite_cache: set to True to overwrite .far files
+ whitelist: path to a file with whitelist replacements
+ """
+
+ def __init__(
+ self,
+ input_case: str,
+ deterministic: bool = True,
+ cache_dir: str = None,
+ overwrite_cache: bool = False,
+ whitelist: str = None,
+ ):
+ super().__init__(name="tokenize_and_classify", kind="classify", deterministic=deterministic)
+
+ far_file = None
+ if cache_dir is not None and cache_dir != "None":
+ os.makedirs(cache_dir, exist_ok=True)
+ whitelist_file = os.path.basename(whitelist) if whitelist else ""
+ far_file = os.path.join(
+ cache_dir,
+ f"ta_tn_{deterministic}_deterministic_{input_case}_{whitelist_file}_tokenize.far",
+ )
+ if not overwrite_cache and far_file and os.path.exists(far_file):
+ self.fst = pynini.Far(far_file, mode="r")["tokenize_and_classify"]
+ logging.info(f"ClassifyFst.fst was restored from {far_file}.")
+ else:
+ logging.info(f"Creating ClassifyFst grammars.")
+
+ cardinal = CardinalFst(deterministic=deterministic)
+ cardinal_graph = cardinal.fst
+
+ punctuation = PunctuationFst(deterministic=deterministic)
+ punct_graph = punctuation.fst
+
+ classify = pynutil.add_weight(cardinal_graph, 1.1)
+
+ word_graph = WordFst().fst
+
+ punct = pynutil.insert("tokens { ") + pynutil.add_weight(punct_graph, weight=2.1) + pynutil.insert(" }")
+ punct = pynini.closure(
+ pynini.union(
+ pynini.compose(pynini.closure(NEMO_WHITE_SPACE, 1), delete_extra_space),
+ (pynutil.insert(NEMO_SPACE) + punct),
+ ),
+ 1,
+ )
+
+ classify = pynini.union(classify, pynutil.add_weight(word_graph, 100))
+ token = pynutil.insert("tokens { ") + classify + pynutil.insert(" }")
+ token_plus_punct = (
+ pynini.closure(punct + pynutil.insert(NEMO_SPACE))
+ + token
+ + pynini.closure(pynutil.insert(NEMO_SPACE) + punct)
+ )
+
+ graph = token_plus_punct + pynini.closure(
+ pynini.union(
+ pynini.compose(pynini.closure(NEMO_WHITE_SPACE, 1), delete_extra_space),
+ (pynutil.insert(NEMO_SPACE) + punct + pynutil.insert(NEMO_SPACE)),
+ )
+ + token_plus_punct
+ )
+
+ graph = delete_space + graph + delete_space
+ graph = pynini.union(graph, punct)
+
+ self.fst = graph.optimize()
+
+ if far_file:
+ generator_main(far_file, {"tokenize_and_classify": self.fst})
+ logging.info(f"ClassifyFst grammars are saved to {far_file}.")
diff --git a/nemo_text_processing/text_normalization/ta/taggers/word.py b/nemo_text_processing/text_normalization/ta/taggers/word.py
new file mode 100644
index 000000000..c36141a92
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/taggers/word.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 import pynutil
+from nemo_text_processing.text_normalization.ta.graph_utils import NEMO_NOT_SPACE, GraphFst
+
+
+class WordFst(GraphFst):
+ """
+ Finite state transducer for classifying Tamil words.
+ e.g. சோனா -> tokens { name: "சோனா" }
+
+ Args:
+ deterministic: if True will provide a single transduction option,
+ for False multiple transductions are generated (used for audio-based normalization)
+
+ """
+
+ def __init__(self):
+ super().__init__(name="word", kind="classify")
+ word = pynutil.insert("name: \"") + pynini.closure(NEMO_NOT_SPACE, 1) + pynutil.insert("\"")
+ self.fst = word.optimize()
diff --git a/nemo_text_processing/text_normalization/ta/utils.py b/nemo_text_processing/text_normalization/ta/utils.py
new file mode 100644
index 000000000..2bcba780d
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/utils.py
@@ -0,0 +1,27 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 os
+
+
+def get_abs_path(rel_path):
+ """
+ Get absolute path
+
+ Args:
+ rel_path: relative path to this file
+
+ Returns absolute path
+ """
+ return os.path.dirname(os.path.abspath(__file__)) + '/' + rel_path
diff --git a/nemo_text_processing/text_normalization/ta/verbalizers/__init__.py b/nemo_text_processing/text_normalization/ta/verbalizers/__init__.py
new file mode 100644
index 000000000..4fc25d0d3
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/verbalizers/__init__.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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.
diff --git a/nemo_text_processing/text_normalization/ta/verbalizers/cardinal.py b/nemo_text_processing/text_normalization/ta/verbalizers/cardinal.py
new file mode 100644
index 000000000..ad135dd0e
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/verbalizers/cardinal.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 import pynutil
+
+from nemo_text_processing.text_normalization.ta.graph_utils import NEMO_NOT_QUOTE, GraphFst, delete_space
+
+
+class CardinalFst(GraphFst):
+ """
+ Finite state transducer for verbalizing cardinals, e.g.
+ cardinal { integer: "ஐந்து" } -> ஐந்து
+ cardinal { negative: "true" integer: "இருபத்துமூன்று" } -> கழித்தல் இருபத்துமூன்று
+
+ Args:
+ deterministic: if True will provide a single transduction option,
+ for False multiple transduction are generated (used for audio-based normalization)
+ """
+
+ def __init__(self, deterministic: bool = True):
+ super().__init__(name="cardinal", kind="verbalize", deterministic=deterministic)
+
+ self.optional_sign = pynini.cross("negative: \"true\"", "கழித்தல் ")
+ if not deterministic:
+ self.optional_sign |= pynini.cross("negative: \"true\"", "எதிர்மறை ")
+ self.optional_sign |= pynini.cross("negative: \"true\"", "கோடுகுறி ")
+
+ self.optional_sign = pynini.closure(self.optional_sign + delete_space, 0, 1)
+
+ integer = pynini.closure(NEMO_NOT_QUOTE)
+
+ self.integer = delete_space + pynutil.delete("\"") + integer + pynutil.delete("\"")
+ integer = pynutil.delete("integer:") + self.integer
+
+ self.numbers = self.optional_sign + integer
+ delete_tokens = self.delete_tokens(self.numbers)
+
+ self.fst = delete_tokens.optimize()
diff --git a/nemo_text_processing/text_normalization/ta/verbalizers/verbalize.py b/nemo_text_processing/text_normalization/ta/verbalizers/verbalize.py
new file mode 100644
index 000000000..67c87718b
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/verbalizers/verbalize.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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.
+
+from nemo_text_processing.text_normalization.ta.graph_utils import GraphFst
+from nemo_text_processing.text_normalization.ta.verbalizers.cardinal import CardinalFst
+
+
+class VerbalizeFst(GraphFst):
+ """
+ Composes other verbalizer grammars.
+ For deployment, this grammar will be compiled and exported to OpenFst Finite State Archive (FAR) File.
+ More details to deployment at NeMo/tools/text_processing_deployment.
+
+ Args:
+ deterministic: if True will provide a single transduction option,
+ for False multiple options (used for audio-based normalization)
+ """
+
+ def __init__(self, deterministic: bool = True):
+ super().__init__(name="verbalize", kind="verbalize", deterministic=deterministic)
+
+ cardinal = CardinalFst(deterministic=deterministic)
+ cardinal_graph = cardinal.fst
+
+ graph = cardinal_graph
+
+ self.fst = graph
diff --git a/nemo_text_processing/text_normalization/ta/verbalizers/verbalize_final.py b/nemo_text_processing/text_normalization/ta/verbalizers/verbalize_final.py
new file mode 100644
index 000000000..8331d03df
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/verbalizers/verbalize_final.py
@@ -0,0 +1,77 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 logging
+import os
+
+import pynini
+from pynini.lib import pynutil
+
+from nemo_text_processing.text_normalization.ta.graph_utils import (
+ GraphFst,
+ delete_extra_space,
+ delete_space,
+ generator_main,
+)
+from nemo_text_processing.text_normalization.ta.verbalizers.verbalize import VerbalizeFst
+from nemo_text_processing.text_normalization.ta.verbalizers.word import WordFst
+
+
+class VerbalizeFinalFst(GraphFst):
+ """
+ Finite state transducer that verbalizes an entire sentence
+ tokens { cardinal { integer: "இரண்டு" } } tokens { name: "சிக்ஸர்கள்" }
+ tokens { cardinal { integer: "பன்னிரண்டு" } } tokens
+ { name: "ரன்கள்" } -> இரண்டு சிக்ஸர்கள் பன்னிரண்டு ரன்கள்
+ Args:
+ deterministic: if True will provide a single transduction option,
+ for False multiple options (used for audio-based normalization)
+ cache_dir: path to a dir with .far grammar file. Set to None to avoid using cache.
+ overwrite_cache: set to True to overwrite .far files
+ """
+
+ def __init__(self, deterministic: bool = True, cache_dir: str = None, overwrite_cache: bool = False):
+ super().__init__(name="verbalize_final", kind="verbalize", deterministic=deterministic)
+
+ far_file = None
+ if cache_dir is not None and cache_dir != "None":
+ os.makedirs(cache_dir, exist_ok=True)
+ far_file = os.path.join(cache_dir, f"ta_tn_{deterministic}_deterministic_verbalizer.far")
+ if not overwrite_cache and far_file and os.path.exists(far_file):
+ self.fst = pynini.Far(far_file, mode="r")["verbalize"]
+ logging.info(f'VerbalizeFinalFst graph was restored from {far_file}.')
+ else:
+ verbalize = VerbalizeFst(deterministic=deterministic).fst
+ word = WordFst(deterministic=deterministic).fst
+ types = verbalize | word
+
+ if deterministic:
+ graph = (
+ pynutil.delete("tokens")
+ + delete_space
+ + pynutil.delete("{")
+ + delete_space
+ + types
+ + delete_space
+ + pynutil.delete("}")
+ )
+ else:
+ graph = delete_space + types + delete_space
+
+ graph = delete_space + pynini.closure(graph + delete_extra_space) + graph + delete_space
+
+ self.fst = graph.optimize()
+ if far_file:
+ generator_main(far_file, {"verbalize": self.fst})
+ logging.info(f"VerbalizeFinalFst grammars are saved to {far_file}.")
diff --git a/nemo_text_processing/text_normalization/ta/verbalizers/word.py b/nemo_text_processing/text_normalization/ta/verbalizers/word.py
new file mode 100644
index 000000000..054edbdd9
--- /dev/null
+++ b/nemo_text_processing/text_normalization/ta/verbalizers/word.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 import pynutil
+from nemo_text_processing.text_normalization.ta.graph_utils import NEMO_CHAR, NEMO_SIGMA, GraphFst, delete_space
+
+
+class WordFst(GraphFst):
+ """
+ Finite state transducer for verbalizing Tamil words.
+ e.g. tokens { name: "சின்ன" } -> சின்ன
+
+ Args:
+ deterministic: if True will provide a single transduction option,
+ for False multiple transduction are generated (used for audio-based normalization)
+ """
+
+ def __init__(self, deterministic: bool = True):
+ super().__init__(name="word", kind="verbalize", deterministic=deterministic)
+ chars = pynini.closure(NEMO_CHAR - " ", 1)
+ char = pynutil.delete("name:") + delete_space + pynutil.delete("\"") + chars + pynutil.delete("\"")
+
+ self.fst = char.optimize()
diff --git a/tests/nemo_text_processing/ta/__init__.py b/tests/nemo_text_processing/ta/__init__.py
new file mode 100644
index 000000000..4fc25d0d3
--- /dev/null
+++ b/tests/nemo_text_processing/ta/__init__.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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.
diff --git a/tests/nemo_text_processing/ta/data_text_normalization/test_cases_cardinal.txt b/tests/nemo_text_processing/ta/data_text_normalization/test_cases_cardinal.txt
new file mode 100644
index 000000000..723f88602
--- /dev/null
+++ b/tests/nemo_text_processing/ta/data_text_normalization/test_cases_cardinal.txt
@@ -0,0 +1,123 @@
+4 நான்குகள்~நான்கு நான்குகள்
+௬ வீரர்கள் அவுட்~ஆறு வீரர்கள் அவுட்
+4 ஓவர்களில் 17 ரன்கள்~நான்கு ஓவர்களில் பதினேழு ரன்கள்
+௫ சாக்லெட்டுகள் ௯ டாஃபிகள்~ஐந்து சாக்லெட்டுகள் ஒன்பது டாஃபிகள்
+10099~பத்துஆயிரத்து தொண்ணூற்றிஒன்பது
+100001~ஒரு லட்சத்து ஒன்று
+௫ நான்குகள்~ஐந்து நான்குகள்
+4 பேர் உயிரிழப்பு 18 பேருக்கு காயம்~நான்கு பேர் உயிரிழப்பு பதினெட்டு பேருக்கு காயம்
+51022345567~ஐந்தாயிரத்து நூற்று இரண்டு கோடியே இருபத்திமூன்று லட்சத்து நாற்பத்திஐந்துஆயிரத்து ஐநூற்று அறுபத்திஏழு
+அத்தியாயம் ௧௦ ஸ்லோகம் ௨~அத்தியாயம் பத்து ஸ்லோகம் இரண்டு
+101~நூற்று ஒன்று
+100~நூறு
+௧௦௦~நூறு
+௧௦௫~நூற்று ஐந்து
+200~இருநூறு
+௨௦௦~இருநூறு
+300~முந்நூறு
+௩௦௦~முந்நூறு
+203~இருநூற்று மூன்று
+௫௦௨~ஐநூற்று இரண்டு
+111~நூற்று பதினொன்று
+௧௧௯~நூற்று பத்தொன்பது
+130~நூற்று முப்பது
+௧௫௦~நூற்று ஐம்பது
+312~முந்நூற்று பன்னிரண்டு
+௫௨௫~ஐநூற்று இருபத்திஐந்து
+425~நானூற்று இருபத்திஐந்து
+௯௧௫~ஒன்பதுநூற்று பதினைந்து
+-1001~கழித்தல் ஆயிரத்து ஒன்று
+5551 வாழைப்பழங்கள்~ஐந்தாயிரத்து ஐநூற்று ஐம்பத்தொன்று வாழைப்பழங்கள்
+௫௫௫௧ வாழைப்பழங்கள்~ஐந்தாயிரத்து ஐநூற்று ஐம்பத்தொன்று வாழைப்பழங்கள்
+-85~கழித்தல் எண்பத்திஐந்து
+௯௧~தொண்ணூற்றொன்று
+௧௩௨௩~ஆயிரத்து முந்நூற்று இருபத்திமூன்று
+1345~ஆயிரத்து முந்நூற்று நாற்பத்திஐந்து
+௧௩௪௫௬~பதிமூன்றுஆயிரத்து நானூற்று ஐம்பத்திஆறு
+12,346~பன்னிரண்டுஆயிரத்து முந்நூற்று நாற்பத்திஆறு
+௧௨௩௪௫௬~ஒரு லட்சத்து இருபத்திமூன்றுஆயிரத்து நானூற்று ஐம்பத்திஆறு
+8,72,987~எட்டு லட்சத்து எழுபத்திரண்டுஆயிரத்து ஒன்பதுநூற்று எண்பத்திஏழு
+௯௮௭௬௦௯~ஒன்பது லட்சத்து எண்பத்திஏழுஆயிரத்து அறுநூற்று ஒன்பது
+9876789~தொண்ணூற்றிஎட்டு லட்சத்து எழுபத்திஆறுஆயிரத்து எழுநூற்று எண்பத்திஒன்பது
+௨௩௪௫௫௬௭~இருபத்திமூன்று லட்சத்து நாற்பத்திஐந்துஆயிரத்து ஐநூற்று அறுபத்திஏழு
+12345567~ஒரு கோடியே இருபத்திமூன்று லட்சத்து நாற்பத்திஐந்துஆயிரத்து ஐநூற்று அறுபத்திஏழு
+௧௨௧௨௧௨௧௨~ஒரு கோடியே இருபத்தொன்று லட்சத்து இருபத்தொன்றுஆயிரத்து இருநூற்று பன்னிரண்டு
+1122345567~நூற்று பன்னிரண்டு கோடியே இருபத்திமூன்று லட்சத்து நாற்பத்திஐந்துஆயிரத்து ஐநூற்று அறுபத்திஏழு
+௧௦௨௨௩௪௫௫௬௭~நூற்று இரண்டு கோடியே இருபத்திமூன்று லட்சத்து நாற்பத்திஐந்துஆயிரத்து ஐநூற்று அறுபத்திஏழு
+11022345567~ஆயிரத்து நூற்று இரண்டு கோடியே இருபத்திமூன்று லட்சத்து நாற்பத்திஐந்துஆயிரத்து ஐநூற்று அறுபத்திஏழு
+௫௧௦௨௨௩௪௫௫௬௭~ஐந்தாயிரத்து நூற்று இரண்டு கோடியே இருபத்திமூன்று லட்சத்து நாற்பத்திஐந்துஆயிரத்து ஐநூற்று அறுபத்திஏழு
+௦௫~சுழியம் ஐந்து
+01~சுழியம் ஒன்று
+௦௭௩~சுழியம் ஏழு மூன்று
+0001~சுழியம் சுழியம் சுழியம் ஒன்று
+௦௦௦~சுழியம் சுழியம் சுழியம்
+10000~பத்துஆயிரம்
+20000~இருபதுஆயிரம்
+45000~நாற்பத்திஐந்துஆயிரம்
+99000~தொண்ணூற்றிஒன்பதுஆயிரம்
+100000000000~பத்துஆயிரம் கோடி
+800~எட்டுநூறு
+850~எட்டுநூற்று ஐம்பது
+900~ஒன்பதுநூறு
+905~ஒன்பதுநூற்று ஐந்து
+987~ஒன்பதுநூற்று எண்பத்திஏழு
+25~இருபத்திஐந்து
+28~இருபத்திஎட்டு
+36~முப்பத்திஆறு
+47~நாற்பத்திஏழு
+59~ஐம்பத்திஒன்பது
+65~அறுபத்திஐந்து
+78~எழுபத்திஎட்டு
+89~எண்பத்திஒன்பது
+99~தொண்ணூற்றிஒன்பது
+1000~ஆயிரம்
+2000~இரண்டாயிரம்
+3000~மூவாயிரம்
+5000~ஐந்தாயிரம்
+9000~ஒன்பதாயிரம்
+100000~ஒரு லட்சம்
+200000~இரண்டு லட்சம்
+1000000~பத்து லட்சம்
+10,00,000~பத்து லட்சம்
+1,000,000~பத்து லட்சம்
+10000000~ஒரு கோடி
+1,00,00,000~ஒரு கோடி
+20000000~இரண்டு கோடி
+100000000~பத்து கோடி
+1000000000~நூறு கோடி
+1,000,000,000~நூறு கோடி
+10000000000~ஆயிரம் கோடி
+1000000000000~ஒரு லட்சம் கோடி
+5000000000000~ஐந்து லட்சம் கோடி
+10000000000000~பத்து லட்சம் கோடி
+1,001,000,000,000~ஒரு லட்சத்து நூறு கோடி
+12,342,300,000~ஆயிரத்து இருநூற்று முப்பத்திநான்கு கோடியே இருபத்திமூன்று லட்சம்
+10000001~ஒரு கோடியே ஒன்று
+10000100~ஒரு கோடியே நூறு
+10001000~ஒரு கோடியே ஆயிரம்
+10100000~ஒரு கோடியே ஒரு லட்சம்
+12000000~ஒரு கோடியே இருபது லட்சம்
+10000000001~ஆயிரம் கோடியே ஒன்று
+500~ஐநூறு
+600~அறுநூறு
+700~எழுநூறு
+19~பத்தொன்பது
+91~தொண்ணூற்றொன்று
+92~தொண்ணூற்றிரண்டு
+0~சுழியம்
+00~சுழியம் சுழியம்
+-100001~கழித்தல் ஒரு லட்சத்து ஒன்று
+-1,00,00,000~கழித்தல் ஒரு கோடி
+-10000000~கழித்தல் ஒரு கோடி
+99999~தொண்ணூற்றிஒன்பதுஆயிரத்து ஒன்பதுநூற்று தொண்ணூற்றிஒன்பது
+1,234,567~பன்னிரண்டு லட்சத்து முப்பத்திநான்குஆயிரத்து ஐநூற்று அறுபத்திஏழு
+123,456,789~பன்னிரண்டு கோடியே முப்பத்திநான்கு லட்சத்து ஐம்பத்திஆறுஆயிரத்து எழுநூற்று எண்பத்திஒன்பது
+1,23,45,00,00,000~பன்னிரண்டுஆயிரத்து முந்நூற்று நாற்பத்திஐந்து கோடி
+99999999999999~தொண்ணூற்றிஒன்பது லட்சத்து தொண்ணூற்றிஒன்பதுஆயிரத்து ஒன்பதுநூற்று தொண்ணூற்றிஒன்பது கோடியே தொண்ணூற்றிஒன்பது லட்சத்து தொண்ணூற்றிஒன்பதுஆயிரத்து ஒன்பதுநூற்று தொண்ணூற்றிஒன்பது
+10000000000000000~10000000000000000
+9999999999999999~தொண்ணூற்றிஒன்பது கோடியே தொண்ணூற்றிஒன்பது லட்சத்து தொண்ணூற்றிஒன்பதுஆயிரத்து ஒன்பதுநூற்று தொண்ணூற்றிஒன்பது கோடியே தொண்ணூற்றிஒன்பது லட்சத்து தொண்ணூற்றிஒன்பதுஆயிரத்து ஒன்பதுநூற்று தொண்ணூற்றிஒன்பது
+1,0000~ஒன்று , சுழியம் சுழியம் சுழியம் சுழியம்
+12,34~பன்னிரண்டு , முப்பத்திநான்கு
+1,23,4567~ஒன்று , இருபத்திமூன்று , நான்காயிரத்து ஐநூற்று அறுபத்திஏழு
+1,௨௩,456~ஒரு லட்சத்து இருபத்திமூன்றுஆயிரத்து நானூற்று ஐம்பத்திஆறு
+-0~கழித்தல் சுழியம்
\ No newline at end of file
diff --git a/tests/nemo_text_processing/ta/data_text_normalization/test_cases_punctuation.txt b/tests/nemo_text_processing/ta/data_text_normalization/test_cases_punctuation.txt
new file mode 100644
index 000000000..59163f383
--- /dev/null
+++ b/tests/nemo_text_processing/ta/data_text_normalization/test_cases_punctuation.txt
@@ -0,0 +1,79 @@
+.~.
+,~,
+!~!
+?~?
+;~;
+:~:
+।~।
+॥~॥
+॰~॰
+—~—
+–~–
+…~…
+‥~‥
+·~·
+•~•
+₹~₹
+§~§
+°~°
+′~′
+″~″
+|~|
+౷~౷
+౿~౿
+?,~?,
+...~...
+???~???
+வணக்கம்.~வணக்கம்.
+வணக்கம்!~வணக்கம்!
+வணக்கம்?~வணக்கம்?
+வணக்கம்,~வணக்கம்,
+வணக்கம்;~வணக்கம்;
+வணக்கம்:~வணக்கம்:
+வணக்கம்।~வணக்கம்।
+வணக்கம்॥~வணக்கம்॥
+வணக்கம்…~வணக்கம்…
+வணக்கம்—~வணக்கம்—
+வணக்கம்!!~வணக்கம்!!
+வணக்கம்?!~வணக்கம்?!
+வணக்கம் .~வணக்கம் .
+வணக்கம் !~வணக்கம் !
+ஃபேஸ்புக்!~ஃபேஸ்புக்!
+வணக்கம்౷~வணக்கம்౷
+வணக்கம்౿~வணக்கம்౿
+ஸ்ரீ॰~ஸ்ரீ॰
+‘வணக்கம்’~‘வணக்கம்’
+“தெலுங்கு”~“தெலுங்கு”
+'வணக்கம்'~'வணக்கம்'
+«தெலுங்கு»~«தெலுங்கு»
+‹வணக்கம்›~‹வணக்கம்›
+„வணக்கம்“~„வணக்கம்“
+(சோதனை)~(சோதனை)
+[சோதனை]~[சோதனை]
+{சோதனை}~{சோதனை}
+மேற்கோள்~மேற்கோள்
+ஆம், சரி.~ஆம், சரி.
+நீங்கள் எப்படி இருக்கிறீர்கள்?~நீங்கள் எப்படி இருக்கிறீர்கள்?
+டாக்டர் ராமு~டாக்டர் ராமு
+ஏ.பி.~ஏ.பி.
+ரூ. நூறு~ரூ. நூறு
+2!~இரண்டு !
+௧!~ஒன்று !
+௨௩?~இருபத்திமூன்று ?
+1!!!!~ஒன்று !!!!
+1,~ஒன்று ,
+-௫~கழித்தல் ஐந்து
+(1)வணக்கம்~(1)வணக்கம்
+விலங்குகள்: (1), (2), (3)~விலங்குகள்: ( ஒன்று ), ( இரண்டு ), ( மூன்று )
+114...48~நூற்று பதினான்கு ... நாற்பத்திஎட்டு
+1961–1965~ஆயிரத்து ஒன்பதுநூற்று அறுபத்தொன்று – ஆயிரத்து ஒன்பதுநூற்று அறுபத்திஐந்து
+2006—2012~இரண்டாயிரத்து ஆறு — இரண்டாயிரத்து பன்னிரண்டு
+100%~நூறு %
+₹100~₹100
+வணக்கம்~வணக்கம்
+test-~test-
+-5 test -~கழித்தல் ஐந்து test -
+?,இல்லை~?,இல்லை
+வணக்கம்...உலகம்~வணக்கம்...உலகம்
+வணக்கம் & அங்கே~வணக்கம் & அங்கே
+• ஐட்டம்~• ஐட்டம்
\ No newline at end of file
diff --git a/tests/nemo_text_processing/ta/data_text_normalization/test_cases_word.txt b/tests/nemo_text_processing/ta/data_text_normalization/test_cases_word.txt
new file mode 100644
index 000000000..d38a80537
--- /dev/null
+++ b/tests/nemo_text_processing/ta/data_text_normalization/test_cases_word.txt
@@ -0,0 +1,16 @@
+தூக்கம்~தூக்கம்
+யாஹூ!~யாஹூ!
+।~।
+ஆஆஆ~ஆஆஆ
+ஆகாயகங்கை~ஆகாயகங்கை
+லடர்படர்~லடர்படர்
+கச்சா-பக்கா~கச்சா-பக்கா
+பலூன்~பலூன்
+சீட்டு~சீட்டு
+தேடுவது~தேடுவது
+இரும்பின்!~இரும்பின்!
+வாஹ்!~வாஹ்!
+டாடா~டாடா
+~
+ழ~ழ
+இசை~இசை
\ No newline at end of file
diff --git a/tests/nemo_text_processing/ta/test_cardinal.py b/tests/nemo_text_processing/ta/test_cardinal.py
new file mode 100644
index 000000000..f0944c24c
--- /dev/null
+++ b/tests/nemo_text_processing/ta/test_cardinal.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 pytest
+from parameterized import parameterized
+from nemo_text_processing.text_normalization.normalize import Normalizer
+from ..utils import CACHE_DIR, parse_test_case_file
+
+
+class TestCardinal:
+ normalizer = Normalizer(
+ input_case='cased', lang='ta', cache_dir=CACHE_DIR, overwrite_cache=False, post_process=False
+ )
+
+ @parameterized.expand(parse_test_case_file('ta/data_text_normalization/test_cases_cardinal.txt'))
+ @pytest.mark.run_only_on('CPU')
+ @pytest.mark.unit
+ def test_norm(self, test_input, expected):
+ pred = self.normalizer.normalize(test_input, verbose=False)
+ assert pred == expected
diff --git a/tests/nemo_text_processing/ta/test_punctuation.py b/tests/nemo_text_processing/ta/test_punctuation.py
new file mode 100644
index 000000000..5788f6048
--- /dev/null
+++ b/tests/nemo_text_processing/ta/test_punctuation.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 pytest
+from parameterized import parameterized
+
+from nemo_text_processing.text_normalization.normalize import Normalizer
+
+from ..utils import CACHE_DIR, parse_test_case_file
+
+
+class TestPunctuation:
+ normalizer = Normalizer(
+ input_case='cased',
+ lang='ta',
+ cache_dir=CACHE_DIR,
+ overwrite_cache=False,
+ post_process=False,
+ )
+
+ @parameterized.expand(parse_test_case_file('ta/data_text_normalization/test_cases_punctuation.txt'))
+ @pytest.mark.run_only_on('CPU')
+ @pytest.mark.unit
+ def test_norm(self, test_input, expected):
+ pred = self.normalizer.normalize(test_input, verbose=False)
+ assert pred == expected
diff --git a/tests/nemo_text_processing/ta/test_sparrowhawk_normalization.sh b/tests/nemo_text_processing/ta/test_sparrowhawk_normalization.sh
new file mode 100644
index 000000000..705eb67f7
--- /dev/null
+++ b/tests/nemo_text_processing/ta/test_sparrowhawk_normalization.sh
@@ -0,0 +1,40 @@
+#! /bin/sh
+
+PROJECT_DIR=/workspace/tests
+
+runtest () {
+ input=$1
+ cd /workspace/sparrowhawk/documentation/grammars
+
+ # read test file
+ while IFS= read -r testcase; do
+ IFS='~' read -r written spoken <<< "$testcase"
+
+ # Escape backslashes and replace non breaking space with breaking space
+ escaped_written=$(printf '%s' "$written" | sed 's/\\/\\\\/g')
+ denorm_pred=$(echo "$escaped_written" | normalizer_main --config=sparrowhawk_configuration.ascii_proto 2>&1 | tail -n 1 | sed 's/\xC2\xA0/ /g')
+
+ # trim white space and remove space before punctuation
+ spoken="$(echo -e "${spoken}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/ \([!?.]\)/\1/g')"
+ denorm_pred="$(echo -e "${denorm_pred}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/ \([!?.]\)/\1/g')"
+
+ # input expected actual
+ assertEquals "$written" "$spoken" "$denorm_pred"
+ done < "$input"
+}
+
+testTNCardinal() {
+ input=$PROJECT_DIR/ta/data_text_normalization/test_cases_cardinal.txt
+ runtest $input
+}
+testTNWord() {
+ input=$PROJECT_DIR/ta/data_text_normalization/test_cases_word.txt
+ runtest $input
+}
+
+testTNPunctuation() {
+ input=$PROJECT_DIR/ta/data_text_normalization/test_cases_punctuation.txt
+ runtest $input
+}
+# Load shUnit2
+. $PROJECT_DIR/../shunit2/shunit2
\ No newline at end of file
diff --git a/tests/nemo_text_processing/ta/test_word.py b/tests/nemo_text_processing/ta/test_word.py
new file mode 100644
index 000000000..ad159d942
--- /dev/null
+++ b/tests/nemo_text_processing/ta/test_word.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
+#
+# 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 pytest
+from parameterized import parameterized
+from nemo_text_processing.text_normalization.normalize import Normalizer
+from ..utils import CACHE_DIR, parse_test_case_file
+
+
+class TestWord:
+ normalizer = Normalizer(
+ input_case='cased', lang='ta', cache_dir=CACHE_DIR, overwrite_cache=False, post_process=True
+ )
+
+ @parameterized.expand(parse_test_case_file('ta/data_text_normalization/test_cases_word.txt'))
+ @pytest.mark.run_only_on('CPU')
+ @pytest.mark.unit
+ def test_norm(self, test_input, expected):
+ pred = self.normalizer.normalize(test_input, verbose=False, punct_post_process=True)
+ assert pred == expected
diff --git a/tools/text_processing_deployment/pynini_export.py b/tools/text_processing_deployment/pynini_export.py
index 73a4fc138..7c100f80a 100644
--- a/tools/text_processing_deployment/pynini_export.py
+++ b/tools/text_processing_deployment/pynini_export.py
@@ -103,6 +103,7 @@ def parse_args():
'es_en',
'he',
'hi',
+ 'ta',
'hi_en',
'hy',
'mr',
@@ -224,6 +225,11 @@ def parse_args():
PostProcessingFst as TNPostProcessingFst,
)
from nemo_text_processing.text_normalization.hi.verbalizers.verbalize import VerbalizeFst as TNVerbalizeFst
+ elif args.language == 'ta':
+ from nemo_text_processing.text_normalization.ta.taggers.tokenize_and_classify import (
+ ClassifyFst as TNClassifyFst,
+ )
+ from nemo_text_processing.text_normalization.ta.verbalizers.verbalize import VerbalizeFst as TNVerbalizeFst
elif args.language == 'hu':
from nemo_text_processing.text_normalization.hu.taggers.tokenize_and_classify import (
ClassifyFst as TNClassifyFst,