<?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: Alexander A.</title>
    <description>The latest articles on DEV Community by Alexander A. (@_alexander_a_).</description>
    <link>https://dev.to/_alexander_a_</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%2F3910984%2F3f269ed0-e7dc-4768-9a8b-c6e55038e3a0.png</url>
      <title>DEV Community: Alexander A.</title>
      <link>https://dev.to/_alexander_a_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_alexander_a_"/>
    <language>en</language>
    <item>
      <title>How I stopped my README.md and README.zh.md from drifting apart</title>
      <dc:creator>Alexander A.</dc:creator>
      <pubDate>Sun, 03 May 2026 22:18:03 +0000</pubDate>
      <link>https://dev.to/_alexander_a_/how-i-stopped-my-readmemd-and-readmezhmd-from-drifting-apart-44lm</link>
      <guid>https://dev.to/_alexander_a_/how-i-stopped-my-readmemd-and-readmezhmd-from-drifting-apart-44lm</guid>
      <description>&lt;h2&gt;
  
  
  The drift problem
&lt;/h2&gt;

&lt;p&gt;Every project that ships a translated README has the same lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Someone writes &lt;code&gt;README.md&lt;/code&gt; in English.&lt;/li&gt;
&lt;li&gt;A contributor opens a PR with &lt;code&gt;README.zh.md&lt;/code&gt;. Great.&lt;/li&gt;
&lt;li&gt;Three months later, English has six new sections. Chinese has the original.&lt;/li&gt;
&lt;li&gt;A second translator opens &lt;code&gt;README.es.md&lt;/code&gt;. Spanish gets translated from… which version? The current &lt;code&gt;README.md&lt;/code&gt;? Or &lt;code&gt;README.zh.md&lt;/code&gt;, by accident, because the structure looks tidier?&lt;/li&gt;
&lt;li&gt;By month nine, you have three READMEs that disagree on what the project actually is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can't tell at a glance which file is stale. Reviewers don't read all three. Translations rot, and there's nothing forcing them to stay in sync.&lt;/p&gt;

&lt;p&gt;I got tired of this and built a small Java tool — &lt;a href="https://github.com/nanolaba/readme-generator" rel="noopener noreferrer"&gt;NRG&lt;/a&gt; — to fix it. Looking for honest feedback while it's still small enough to change direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: one source, N outputs
&lt;/h2&gt;

&lt;p&gt;Write one &lt;code&gt;README.src.md&lt;/code&gt;. Get back &lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;README.zh.md&lt;/code&gt;, &lt;code&gt;README.ja.md&lt;/code&gt;, and as many language variants as you list.&lt;/p&gt;

&lt;p&gt;Lines in the template fall into three categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared across all languages&lt;/strong&gt; (badges, code blocks, file paths, anything language-agnostic) — no markup needed, the line just appears in every output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;CI&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://img.shields.io/github/actions/workflow/status/owner/repo/ci.yml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Language-tagged&lt;/strong&gt; — appears only in that language's output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;This library is small but hard to use.&lt;span class="c"&gt;&amp;lt;!--en--&amp;gt;&lt;/span&gt;
Эта библиотека маленькая, но сложная в использовании.&lt;span class="c"&gt;&amp;lt;!--ru--&amp;gt;&lt;/span&gt;
本库虽小，但难以使用。&lt;span class="c"&gt;&amp;lt;!--zh--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Inline per-language phrases&lt;/strong&gt; — useful for short strings like anchor names or button labels where one full line per language would be overkill:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## ${en:'Table of contents', ru:'Содержание', zh:'目录'}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;nrg -f README.src.md&lt;/code&gt;. Out come &lt;code&gt;README.md&lt;/code&gt;, &lt;code&gt;README.ru.md&lt;/code&gt;, &lt;code&gt;README.zh.md&lt;/code&gt;, all stamped with a header comment so a reader knows the file is generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The killer feature: CI drift-check
&lt;/h2&gt;

&lt;p&gt;This is the part I actually care about, and the reason "regenerate the file periodically" wasn't enough.&lt;/p&gt;

&lt;p&gt;NRG ships a GitHub Action (&lt;a href="https://github.com/nanolaba/nrg-action" rel="noopener noreferrer"&gt;&lt;code&gt;nanolaba/nrg-action@v1&lt;/code&gt;&lt;/a&gt;) with a &lt;code&gt;check&lt;/code&gt; mode. On every PR, it regenerates the READMEs into a temp dir and diffs them against what's committed. If they don't match, the build fails with a unified diff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;--- README.md (on disk)
&lt;/span&gt;&lt;span class="gi"&gt;+++ README.md (generated)
&lt;/span&gt;&lt;span class="p"&gt;@@ line 27 @@&lt;/span&gt;
&lt;span class="gd"&gt;-## Quick start
&lt;/span&gt;&lt;span class="gi"&gt;+## Getting started
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means a contributor can't land a hand-edit to &lt;code&gt;README.zh.md&lt;/code&gt; that bypasses the template. Either they edit &lt;code&gt;README.src.md&lt;/code&gt; and regenerate, or CI rejects the PR. No more silent drift.&lt;/p&gt;

&lt;p&gt;The CLI has the same flag: &lt;code&gt;nrg -f README.src.md --check&lt;/code&gt;. Useful in pre-commit hooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in widgets
&lt;/h2&gt;

&lt;p&gt;Anything the template syntax can't express directly is a widget. The shipped set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;${widget:tableOfContents(ordered='true')}&lt;/code&gt; — auto-builds a TOC from heading levels.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${widget:import(path='docs/intro.src.md')}&lt;/code&gt; — composes templates so a giant README isn't one 800-line file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${widget:exec(cmd='git rev-parse --short HEAD')}&lt;/code&gt; — embeds shell output (handy for "last built from commit X").&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${widget:fileTree(path='src/main/java', depth='2')}&lt;/code&gt; — auto-generates a directory tree.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${widget:math(expr='\\pi r^2')}&lt;/code&gt; — renders LaTeX, with an SVG fallback for the cases where GitHub's native MathJax silently fails.&lt;/li&gt;
&lt;li&gt;Plus &lt;code&gt;alert&lt;/code&gt;, &lt;code&gt;badge&lt;/code&gt;, &lt;code&gt;if/endIf&lt;/code&gt;, &lt;code&gt;date&lt;/code&gt;, &lt;code&gt;todo&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Custom widgets are a one-class implementation of an &lt;code&gt;NRGWidget&lt;/code&gt; interface — useful if you have a recurring pattern specific to your project (e.g. a "feature matrix" widget that renders a row per supported runtime).&lt;/p&gt;

&lt;h2&gt;
  
  
  Three integration modes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CLI&lt;/strong&gt; — &lt;code&gt;nrg -f README.src.md&lt;/code&gt;. Zero config beyond declaring &lt;code&gt;&amp;lt;!--@nrg.languages=en,ru,zh--&amp;gt;&lt;/code&gt; in the template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maven plugin&lt;/strong&gt; — for Java projects, hangs off the &lt;code&gt;compile&lt;/code&gt; phase, regenerates on every build:&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;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.nanolaba&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;nrg-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.2&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;file&amp;gt;&amp;lt;item&amp;gt;&lt;/span&gt;README.src.md&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&amp;lt;/file&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;compile&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&amp;lt;goal&amp;gt;&lt;/span&gt;create-files&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub Action&lt;/strong&gt; — for Python, JS, Rust, or any non-Java project. The action provisions Java itself, so you don't need a Java toolchain in your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nanolaba/nrg-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;README.src.md&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole setup. There's also a Java library mode if you want to embed it in some other generator pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Java 8 minimum&lt;/strong&gt; — the binary's portable, but if you despise installing JDKs, the GitHub Action route is the only zero-touch option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a translation tool.&lt;/strong&gt; NRG keeps structure synchronized. Actual prose translation is still a human job (or your favourite LLM).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Markdown AST.&lt;/strong&gt; Substitution and widgets operate on raw text. This is fine 99% of the time but means a clever author can produce broken Markdown that NRG won't catch — that's why there's a separate &lt;code&gt;validate&lt;/code&gt; mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Early days.&lt;/strong&gt; Currently at v1.2, used by a handful of open-source repos. The widget API may still change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd love feedback on
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Drift-check workflow&lt;/strong&gt; — useful safety net, or annoying friction when a translator just wants to fix a typo? Curious how this lands for people who maintain translated docs at scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widget syntax&lt;/strong&gt; — &lt;code&gt;${widget:tableOfContents(title='...', ordered='true')}&lt;/code&gt; — readable, or have I reinvented a worse Mustache?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What would actually make you adopt this&lt;/strong&gt; over your current setup (hand-syncing, custom script, doing nothing)? "I'd never use this because…" answers are the most useful.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Repo, full docs, and the GIF demo: &lt;a href="https://github.com/nanolaba/readme-generator" rel="noopener noreferrer"&gt;github.com/nanolaba/readme-generator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading — happy to answer questions in the comments.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>java</category>
      <category>markdown</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
