<?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: Muzaffar Ali</title>
    <description>The latest articles on DEV Community by Muzaffar Ali (@muzaffarali).</description>
    <link>https://dev.to/muzaffarali</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%2F4228006%2Fa7bbcc59-2e9e-47dc-9053-9961acb582ec.png</url>
      <title>DEV Community: Muzaffar Ali</title>
      <link>https://dev.to/muzaffarali</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muzaffarali"/>
    <language>en</language>
    <item>
      <title>Building a Free Javanese Script (Aksara Jawa) Converter in Vanilla JavaScript javascript, webdev, i18n, beginners</title>
      <dc:creator>Muzaffar Ali</dc:creator>
      <pubDate>Thu, 05 Nov 2026 08:25:41 +0000</pubDate>
      <link>https://dev.to/muzaffarali/building-a-free-javanese-script-aksara-jawa-converter-in-vanilla-javascript-javascript-webdev-84n</link>
      <guid>https://dev.to/muzaffarali/building-a-free-javanese-script-aksara-jawa-converter-in-vanilla-javascript-javascript-webdev-84n</guid>
      <description>&lt;p&gt;A few months ago I set out to build something small but surprisingly deep: a converter that turns Latin text into &lt;strong&gt;Aksara Jawa&lt;/strong&gt; — the traditional script of the Javanese language, spoken by ~80 million people in Indonesia. What looked like a simple "map letters to symbols" task turned into a fun lesson in writing systems. Here's what I learned.&lt;/p&gt;

&lt;p&gt;You can try the finished tool here: &lt;a href="https://aksarajava.id" rel="noopener noreferrer"&gt;aksarajava.id&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not an alphabet — it's an abugida
&lt;/h2&gt;

&lt;p&gt;My first mistake was assuming Javanese script works like the Latin alphabet, one symbol per sound. It doesn't.&lt;/p&gt;

&lt;p&gt;Aksara Jawa is an &lt;strong&gt;abugida&lt;/strong&gt;. Each of its 20 base consonants already carries an inherent "a" vowel. So the letter &lt;code&gt;ꦏ&lt;/code&gt; isn't "k" — it's &lt;strong&gt;"ka"&lt;/strong&gt;. To get other vowels, you attach small marks called &lt;strong&gt;sandhangan&lt;/strong&gt;. To write a bare consonant (no vowel), you add a "killer" mark called &lt;strong&gt;pangkon&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means you can't process the input character by character. You have to break words into &lt;strong&gt;syllables&lt;/strong&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  A peek at the logic
&lt;/h2&gt;

&lt;p&gt;Here's a simplified version of the core idea — map each syllable to a base glyph plus an optional vowel sign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦏ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦒ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦤ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦩ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦱ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SANDHANGAN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦶ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦸ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦺ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ꦺꦴ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// 'a' is inherent&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syllableToJavanese&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consonant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vowel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BASE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;consonant&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;        &lt;span class="c1"&gt;// already carries 'a'&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vowel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;vowel&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;SANDHANGAN&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;vowel&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;       &lt;span class="c1"&gt;// modify the vowel&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;syllableToJavanese&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ꦏꦶ  → "ki"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real engine handles consonant clusters (&lt;strong&gt;pasangan&lt;/strong&gt;, where a dead consonant stacks under the previous one), numbers, and more — but the mental model above is the heart of it. The whole thing is plain JavaScript, no dependencies, and runs entirely in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug a native speaker caught
&lt;/h2&gt;

&lt;p&gt;Here's my favorite part. After launch, a native Javanese reader pointed out that the word &lt;strong&gt;"kanca"&lt;/strong&gt; (friend) was rendered wrong.&lt;/p&gt;

&lt;p&gt;My engine wrote it literally: &lt;code&gt;ka&lt;/code&gt; + &lt;code&gt;n&lt;/code&gt; + &lt;code&gt;ca&lt;/code&gt;. But Javanese has a rule called &lt;strong&gt;homorganic nasal assimilation&lt;/strong&gt;: when an "n" sound comes right before "c" or "j", it changes to "ny" (&lt;code&gt;ꦚ&lt;/code&gt;). So the correct form of &lt;em&gt;kanca&lt;/em&gt; is &lt;code&gt;ka&lt;/code&gt; + &lt;code&gt;nya&lt;/code&gt; + &lt;code&gt;ca&lt;/code&gt;, not &lt;code&gt;ka&lt;/code&gt; + &lt;code&gt;na&lt;/code&gt; + &lt;code&gt;ca&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It was a one-line fix once I understood it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// a syllable-final "n" before a palatal (c/j) becomes "ny"&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;coda&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextOnset&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;nextOnset&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;j&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;coda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ny&lt;/span&gt;&lt;span class="dl"&gt;'&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;Lesson: when you're encoding a human language, &lt;strong&gt;the native speakers are your best test suite.&lt;/strong&gt; I would never have found this edge case on my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy by design
&lt;/h2&gt;

&lt;p&gt;One decision I'm happy with: the conversion runs &lt;strong&gt;100% in the browser&lt;/strong&gt;. Nothing you type is sent to a server. For a tool people use to write their own names and personal messages, that felt important — and it also makes it instant and free to host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to see it in action (or write your own name in Javanese script), it's live and free at &lt;a href="https://aksarajava.id" rel="noopener noreferrer"&gt;aksarajava.id&lt;/a&gt;. There are also beginner lessons for the script and an interactive quiz.&lt;/p&gt;

&lt;p&gt;Writing systems are a fantastic playground for developers — you learn linguistics, Unicode, and font rendering all at once. If you've built anything similar for another script, I'd love to hear about it in the comments. 🙏&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
