<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mark Anthony Catabona</title>
    <description>The latest articles on DEV Community by Mark Anthony Catabona (@ravenclaw27).</description>
    <link>https://dev.to/ravenclaw27</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4034699%2Fbbc1d2f0-d59d-479f-89a1-efe57db51195.jpg</url>
      <title>DEV Community: Mark Anthony Catabona</title>
      <link>https://dev.to/ravenclaw27</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ravenclaw27"/>
    <language>en</language>
    <item>
      <title>Why Android's Speech Recognizer Struggles with Tagalog and What I Did About It</title>
      <dc:creator>Mark Anthony Catabona</dc:creator>
      <pubDate>Sat, 18 Jul 2026 05:09:20 +0000</pubDate>
      <link>https://dev.to/ravenclaw27/why-androids-speech-recognizer-struggles-with-tagalog-and-what-i-did-about-it-42l3</link>
      <guid>https://dev.to/ravenclaw27/why-androids-speech-recognizer-struggles-with-tagalog-and-what-i-did-about-it-42l3</guid>
      <description>&lt;p&gt;ViVeL — Vida Verdadera De Dios, "True Life of God." I wanted a name shaped like Israel: not just a label, but a word that says something about who the people are. ViVeL is an offline Bible app for Android with voice search over Ang Dating Biblia (Tagalog, 1905) and the KJV.&lt;/p&gt;

&lt;p&gt;In English, voice search works fine. In Tagalog, it fights back — and the ways it fails turned out to be interesting enough to write up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabcgcqmwfahiw20w6wg2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabcgcqmwfahiw20w6wg2.png" alt=" " width="800" height="1745"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a real one from the app. I said &lt;strong&gt;nagkakalakip na gumagawa&lt;/strong&gt;. The recognizer produced:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;magkakalapit na gumagawa&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The recognizer heard a completely &lt;em&gt;different word&lt;/em&gt;, not a typo, not a misspelling, a different word entirely. And here's why that matters more for search than for dictation: with dictation, one wrong word in a sentence is survivable. With verse search, one wrong word means &lt;strong&gt;zero results&lt;/strong&gt;. It's all-or-nothing. The verse either surfaces or it doesn't.&lt;/p&gt;

&lt;p&gt;I'm using the &lt;code&gt;speech_to_text&lt;/code&gt; Flutter plugin, which wraps the platform's on-device recognizer, with the locale set to &lt;code&gt;fil-PH&lt;/code&gt;. Two things go wrong with Tagalog specifically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tagalog is low-resource.&lt;/strong&gt; There's far less Tagalog in the recognizer's training data than English, so its confidence on Tagalog words is shakier and it biases toward more common word sequences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tagalog loves reduplication and long affixed words&lt;/strong&gt;,&lt;code&gt;naglalakip&lt;/code&gt;, &lt;code&gt;magkakalapit&lt;/code&gt;, &lt;code&gt;nangagsisiibig&lt;/code&gt;. These long, morphologically dense words are exactly the ones a shaky recognizer mangles, collapsing one into a nearby-sounding other.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I could fix, spelling, not sound
&lt;/h2&gt;

&lt;p&gt;There's a &lt;em&gt;second&lt;/em&gt; mismatch that I could actually solve: the modern speech recognizer outputs modern spelling, but my bundled text is the 1905 ADB, which spells things the old way. &lt;code&gt;Diyos&lt;/code&gt; vs &lt;code&gt;Dios&lt;/code&gt;. &lt;code&gt;sapagkat&lt;/code&gt; vs &lt;code&gt;Sapagka't&lt;/code&gt;. &lt;code&gt;kanya&lt;/code&gt; vs &lt;code&gt;kaniya&lt;/code&gt;. &lt;code&gt;ngunit&lt;/code&gt; vs &lt;code&gt;Nguni't&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Even if the recognizer hears the word perfectly, the FTS5 search misses because the spellings don't match. So I fold both forms into one canonical form before searching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'normalizeForSearch folds archaic and modern spellings together'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DatabaseHelper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;normalizeForSearch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Diyos'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Dios'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'sapagkat'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sapagka't"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Hesukristo'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Jesucristo'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'kanya'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'kaniya'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ngunit'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Nguni't"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'gayon'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;'gayon'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// unaffected words stay stable&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'nangagsisiibig'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;'nangagsisyibig'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// folds only whole words&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part is folding at the &lt;em&gt;right&lt;/em&gt; granularity. &lt;code&gt;nang&lt;/code&gt; should fold to &lt;code&gt;ng&lt;/code&gt; as a whole word but not when it's buried inside &lt;code&gt;nangagsisiibig&lt;/code&gt;, or you corrupt unrelated words. So the normalizer is word-boundary aware, not a blind find-and-replace.&lt;/p&gt;

&lt;p&gt;I also lean on the recognizer's &lt;strong&gt;alternate hypotheses&lt;/strong&gt;. The plugin returns more than just the top transcription, so when the best guess misses, I retry the search against the runner-up transcriptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;recognizedWords&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;recognizedWords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the correct word is the recognizer's second or third guess, and this quietly rescues the search.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I haven't solved, the sound-level miss
&lt;/h2&gt;

&lt;p&gt;Spelling normalization and alternates handle a lot. What they &lt;em&gt;don't&lt;/em&gt; handle is the case at the top: &lt;code&gt;naglalakip&lt;/code&gt; heard as &lt;code&gt;magkakalapit&lt;/code&gt;. That's not a spelling variant and it's not in the alternates list, it's a genuine acoustic substitution of one whole word for another.&lt;/p&gt;

&lt;p&gt;The approaches I'm weighing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phonetic normalization&lt;/strong&gt; - map both the transcript and the index to a sound-based key (a Tagalog-tuned Soundex/Metaphone) so acoustically-near words collide on purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy matching over FTS5&lt;/strong&gt; - Levenshtein or trigram distance against the verse index, so a near-miss still retrieves candidates instead of nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tagalog-aware morphology&lt;/strong&gt; - strip reduplication and affixes before matching, so &lt;code&gt;naglalakip&lt;/code&gt; and its mangled cousins reduce toward a common root.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each option has a downside. Phonetic keys match too many unrelated words. Fuzzy search across the whole Bible gets slow on cheap phones. Stripping affixes properly needs real Tagalog grammar rules, not shortcuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have you hit this?
&lt;/h2&gt;

&lt;p&gt;If you've built voice or search for Tagalog or any low-resource language with heavy affixation I'd like to know how you handled the acoustic misses. Phonetic keying? Fuzzy retrieval? Something smarter? The spelling and alternates layers were the easy 80%. The last 20%, where the recognizer swaps in a whole wrong word, is the part I'm still chewing on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a software engineer from the Philippines building offline-first Android apps, with a focus on Tagalog-language tooling that most speech and search stacks weren't designed for.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>speechrecognition</category>
      <category>android</category>
      <category>nlp</category>
    </item>
  </channel>
</rss>
