<?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: Michal Král</title>
    <description>The latest articles on DEV Community by Michal Král (@kralik12).</description>
    <link>https://dev.to/kralik12</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F21797%2F85ec1aeb-60ac-446f-abc8-9ad2177d1422.jpg</url>
      <title>DEV Community: Michal Král</title>
      <link>https://dev.to/kralik12</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kralik12"/>
    <language>en</language>
    <item>
      <title>I love MJML — I just didn't want a whole templating engine for two tiny things</title>
      <dc:creator>Michal Král</dc:creator>
      <pubDate>Wed, 27 May 2026 11:19:34 +0000</pubDate>
      <link>https://dev.to/kralik12/i-love-mjml-i-just-didnt-want-a-whole-templating-engine-for-two-tiny-things-5a10</link>
      <guid>https://dev.to/kralik12/i-love-mjml-i-just-didnt-want-a-whole-templating-engine-for-two-tiny-things-5a10</guid>
      <description>&lt;p&gt;I love MJML.&lt;/p&gt;

&lt;p&gt;Responsive HTML email is one of the genuinely miserable corners of frontend — nested tables, &lt;code&gt;&amp;lt;!--[if mso]&amp;gt;&lt;/code&gt; incantations, clients from 2007 that still get a vote. MJML takes all of that and lets me write semantic-ish components (&lt;code&gt;&amp;lt;mj-section&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;mj-column&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;mj-button&amp;gt;&lt;/code&gt;) that compile down to email HTML that actually renders everywhere. It's one of those tools that makes a horrible problem boring, which is the highest compliment I can give a library.&lt;/p&gt;

&lt;p&gt;So I was happy. Except for one thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing I was missing
&lt;/h2&gt;

&lt;p&gt;My templates needed two unglamorous things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Variables&lt;/strong&gt; — drop a name, a URL, an order number into the email.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translations&lt;/strong&gt; — the same template in &lt;code&gt;en&lt;/code&gt;, &lt;code&gt;cs&lt;/code&gt;, whatever.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MJML doesn't do either, and fair enough — it's a markup compiler, not a template engine. So like a lot of people, I reached for the template engine I already had and ran my &lt;code&gt;.mjml&lt;/code&gt; files &lt;strong&gt;through Twig first&lt;/strong&gt;, just to get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;mj-text&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="s1"&gt;'welcome.hello'&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;trans&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'%name%'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/mj-text&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it worked. But look at what that actually is: I'm spinning up an entire templating engine — with loops, conditionals, filters, its own parsing pass — to do &lt;em&gt;string interpolation and a dictionary lookup&lt;/em&gt;. Two compile steps (Twig → MJML) for what should be one. A second syntax in my files. And every time I touched a template I had to keep two mental models in sync.&lt;/p&gt;

&lt;p&gt;I didn't want a template engine in my email pipeline. I wanted those &lt;strong&gt;two tiny things&lt;/strong&gt; — and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually wanted
&lt;/h2&gt;

&lt;p&gt;Co-located translations, vue-i18n style — the strings living right next to the markup that uses them — plus dead-simple variable access. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;mjml&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;i18n&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {
      "en": { "hello": "Hello {name}!", "cta": "Shop now" },
      "cs": { "hello": "Ahoj {name}!", "cta": "Nakupovat" }
    }
  &lt;span class="nt"&gt;&amp;lt;/i18n&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;mj-body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mj-section&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;mj-column&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mj-text&amp;gt;&lt;/span&gt;{{ i18n('hello', { name: get('firstName') }) }}&lt;span class="nt"&gt;&amp;lt;/mj-text&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;mj-button&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ get('url') }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ i18n('cta') }}&lt;span class="nt"&gt;&amp;lt;/mj-button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/mj-column&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/mj-section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mj-body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/mjml&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No second template language. No second compile. Just &lt;code&gt;i18n()&lt;/code&gt; and &lt;code&gt;get()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I built it: &lt;strong&gt;&lt;code&gt;@checkthiscloud/mjml-i18n&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;p&gt;It's a single MJML preprocessor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mjml&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mjml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createI18nPreprocessor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@checkthiscloud/mjml-i18n&lt;/span&gt;&lt;span class="dl"&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;preprocessor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createI18nPreprocessor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ada&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;mjml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;preprocessors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;preprocessor&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;That's the whole integration. One preprocessor in, fully resolved email HTML out.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works (and what it deliberately &lt;em&gt;doesn't&lt;/em&gt; do)
&lt;/h2&gt;

&lt;p&gt;The trick is that it runs as an &lt;strong&gt;MJML preprocessor&lt;/strong&gt; — it resolves the &lt;code&gt;{{ … }}&lt;/code&gt; markers on the &lt;strong&gt;raw XML, before MJML parses it&lt;/strong&gt;. Two nice consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markers work &lt;strong&gt;anywhere&lt;/strong&gt;, including inside attributes (&lt;code&gt;href="{{ get('url') }}"&lt;/code&gt;), not just text nodes.&lt;/li&gt;
&lt;li&gt;It composes with MJML instead of fighting it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside each &lt;code&gt;{{ … }}&lt;/code&gt; is a single &lt;strong&gt;function-call expression&lt;/strong&gt;, evaluated by a tiny allowlist (built on &lt;a href="https://github.com/EricSmekens/jsep" rel="noopener noreferrer"&gt;jsep&lt;/a&gt;). Only literals, object arguments, and the registered functions (&lt;code&gt;i18n&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;) are allowed. You can nest them — &lt;code&gt;i18n('hello', { name: get('firstName') })&lt;/code&gt; — and that's it.&lt;/p&gt;

&lt;p&gt;What it is &lt;strong&gt;not&lt;/strong&gt; is a template engine. No loops, no conditionals, no arbitrary expressions, no &lt;code&gt;eval&lt;/code&gt;. If a marker isn't a recognized call, it's left untouched rather than executed. That constraint is the whole point: I wanted the two things I was missing, not a Turing-complete language living in my emails.&lt;/p&gt;

&lt;p&gt;Translations fall back gracefully (missing key → the key, missing variable → an obvious placeholder), so a typo degrades instead of exploding mid-render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;p&gt;It's young — &lt;code&gt;0.x&lt;/code&gt;, MIT, built for MJML v5 — but small and focused, and it does exactly what it says. Params are currently plain &lt;code&gt;{name}&lt;/code&gt; substitution (no ICU plurals yet, though the seam is there if that day comes). If you live in MJML and you've been bolting a template engine onto it just for translations, give it a spin and tell me where it breaks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@checkthiscloud/mjml-i18n" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@checkthiscloud/mjml-i18n&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/CheckThisCloud/mjml-i18n" rel="noopener noreferrer"&gt;https://github.com/CheckThisCloud/mjml-i18n&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @checkthiscloud/mjml-i18n mjml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the feature you want isn't "more" — it's &lt;em&gt;exactly two small things and nothing else&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>mjml</category>
      <category>i18n</category>
      <category>opensource</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Generate Human-Friendly Random Codes in PHP with Crockford’s Base32</title>
      <dc:creator>Michal Král</dc:creator>
      <pubDate>Tue, 30 Sep 2025 18:24:28 +0000</pubDate>
      <link>https://dev.to/kralik12/generate-human-friendly-random-codes-in-php-with-crockfords-base32-fob</link>
      <guid>https://dev.to/kralik12/generate-human-friendly-random-codes-in-php-with-crockfords-base32-fob</guid>
      <description>&lt;p&gt;Generating random codes sounds easy — until real people have to read, type, or say them out loud. Suddenly &lt;code&gt;O&lt;/code&gt; looks like &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;l&lt;/code&gt; looks like &lt;code&gt;1&lt;/code&gt;, and half your support tickets are about users mistyping their coupon or entry code.  &lt;/p&gt;

&lt;p&gt;To solve this, I built a small PHP library that generates &lt;strong&gt;human-friendly random strings&lt;/strong&gt; using &lt;strong&gt;Crockford’s Base32 alphabet&lt;/strong&gt;. It’s perfect for things like &lt;strong&gt;coupon codes, ticket entry codes, invite tokens, or any other short identifiers&lt;/strong&gt; where clarity matters just as much as randomness.  &lt;/p&gt;

&lt;p&gt;👉 GitHub repo: &lt;a href="https://github.com/CheckThisCloud/CrockfordRandom" rel="noopener noreferrer"&gt;CheckThisCloud/CrockfordRandom&lt;/a&gt;  &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Crockford’s Base32?
&lt;/h2&gt;

&lt;p&gt;When generating codes for humans to type in or read back, you don’t want people misreading &lt;code&gt;O&lt;/code&gt; as &lt;code&gt;0&lt;/code&gt; or &lt;code&gt;l&lt;/code&gt; as &lt;code&gt;1&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Crockford’s alphabet solves this by removing ambiguous characters while keeping the string compact and case-insensitive.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Generate random strings of any length.
&lt;/li&gt;
&lt;li&gt;Ideal for human-facing codes: &lt;strong&gt;tickets, coupons, invites, promo codes&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Output uses only unambiguous characters.
&lt;/li&gt;
&lt;li&gt;Built on PHP’s modern &lt;code&gt;Random\Randomizer&lt;/code&gt; class.
&lt;/li&gt;
&lt;li&gt;Tiny footprint — easy to drop into any project.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;CheckThisCloud\CrockfordRandom\CrockfordRandom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CrockfordRandom&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$random&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="c1"&gt;// Example output: 9XF7T2K4HZ&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bonus: SillyNames 🎉
&lt;/h3&gt;

&lt;p&gt;I’ve also built another micro-library: [CheckThisCloud/SillyNames]((&lt;a href="https://github.com/CheckThisCloud/SillyNames" rel="noopener noreferrer"&gt;https://github.com/CheckThisCloud/SillyNames&lt;/a&gt;)&lt;br&gt;
.&lt;br&gt;
It generates random two-word names in the style of Docker container names (e.g. brave_panda, sleepy_hedgehog).&lt;br&gt;
Useful for test data, logs, or giving projects some personality.&lt;/p&gt;

&lt;p&gt;Feedback is very welcome 🙏&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>devtools</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
