<?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: Julian</title>
    <description>The latest articles on DEV Community by Julian (@juliandreas).</description>
    <link>https://dev.to/juliandreas</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%2F3592207%2Fb93c3672-cf6d-4bea-b00e-4e10b8d51db6.jpg</url>
      <title>DEV Community: Julian</title>
      <link>https://dev.to/juliandreas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juliandreas"/>
    <language>en</language>
    <item>
      <title>I Automated JSON i18n Translation After Hitting the Same Workflow Issues on Every Project</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Sat, 01 Nov 2025 09:21:52 +0000</pubDate>
      <link>https://dev.to/juliandreas/i-automated-json-i18n-translation-after-hitting-the-same-workflow-issues-on-every-project-28b7</link>
      <guid>https://dev.to/juliandreas/i-automated-json-i18n-translation-after-hitting-the-same-workflow-issues-on-every-project-28b7</guid>
      <description>&lt;p&gt;Every multilingual project I've worked on follows the same frustrating pattern:&lt;/p&gt;

&lt;p&gt;Someone adds a new English string, forgets to add it to the other locale files, and two weeks later we discover half the app is untranslated in Swedish. Or worse, translation keys pile up in one locale but get deleted in another, and nobody notices until a customer complains.&lt;/p&gt;

&lt;p&gt;The "proper" solution seems to be paying for a translation management SaaS (Lokalise, Locize, etc.), but I didn't want vendor lock-in or another subscription. &lt;strong&gt;I just wanted the grunt work automated.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I created &lt;a href="https://github.com/juliandreas/dire-cli" rel="noopener noreferrer"&gt;Dire&lt;/a&gt;, a CLI tool that handles the boring parts of i18n maintenance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto-sync locale files&lt;/strong&gt;: Diffs your JSON files, finds missing keys, translates them using your choice of provider (DeepL, Claude, OpenAI, Google Translate, etc.), and updates everything. One command, done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup orphaned keys&lt;/strong&gt;: The &lt;code&gt;--prune&lt;/code&gt; flag removes keys from non-reference locales that don't exist in your most complete locale. No more "keys that should've been deleted months ago" hanging around.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Translation memory + glossary&lt;/strong&gt;: Remembers translations so identical strings don't get re-translated. Saves API costs and keeps terminology consistent across your app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CI/CD validation&lt;/strong&gt;: The &lt;code&gt;--check&lt;/code&gt; flag validates that all translations are complete and exits with the appropriate code. Catches missing translations in PRs before they hit production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero lock-in&lt;/strong&gt;: It's just a CLI that manipulates your JSON files. You own the data, use any provider, switch providers anytime.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fyvuy5yg7tok7i7cqqnlf.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.amazonaws.com%2Fuploads%2Farticles%2Fyvuy5yg7tok7i7cqqnlf.png" alt="Dire in action" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;Install via npm (it's a Go binary distributed through npm for convenience):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; dire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize configuration in your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dire init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;.dire.toml&lt;/code&gt; file. Here's a minimal config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[files]&lt;/span&gt;
&lt;span class="py"&gt;directory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./locales"&lt;/span&gt;

&lt;span class="nn"&gt;[files.locales]&lt;/span&gt;
&lt;span class="py"&gt;"en.json"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"en-US"&lt;/span&gt;
&lt;span class="py"&gt;"fr.json"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"fr-FR"&lt;/span&gt;
&lt;span class="py"&gt;"de.json"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"de-DE"&lt;/span&gt;

&lt;span class="nn"&gt;[providers]&lt;/span&gt;
&lt;span class="py"&gt;active&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"deepl"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The config file is optional - you can configure everything via CLI flags instead. The TOML file is just convenient for storing project settings, managing glossaries, and switching between multiple providers.&lt;/p&gt;

&lt;p&gt;Add your API key to a &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DIRE_DEEPL_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-key-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run translation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Missing keys get translated and your files stay in sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example
&lt;/h2&gt;

&lt;p&gt;Let's say your &lt;code&gt;en.json&lt;/code&gt; has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Log in"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"logout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Log out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"register"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sign up"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But your &lt;code&gt;fr.json&lt;/code&gt; is missing the "register" key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Se connecter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"logout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Se déconnecter"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;dire&lt;/code&gt; and it automatically adds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Se connecter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"logout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Se déconnecter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"register"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"S'inscrire"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Translation Memory
&lt;/h3&gt;

&lt;p&gt;Dire remembers every translation it makes. If you use "Dashboard" in multiple places, it translates it once and reuses the translation everywhere. This:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saves API costs&lt;/li&gt;
&lt;li&gt;Ensures consistency&lt;/li&gt;
&lt;li&gt;Speeds up processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Glossary Management
&lt;/h3&gt;

&lt;p&gt;Define custom terminology that should always be translated the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[glossary]&lt;/span&gt;
&lt;span class="py"&gt;autoSort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;entries&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="py"&gt;"en-US"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"fr-FR"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"de-DE"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"API"&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="py"&gt;"en-US"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"fr-FR"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tableau de bord"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;"de-DE"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Dashboard"&lt;/span&gt; &lt;span class="err"&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;Glossary translations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Override provider translations&lt;/li&gt;
&lt;li&gt;Work bidirectionally (en→fr and fr→en)&lt;/li&gt;
&lt;li&gt;Don't consume API credits&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CI/CD Integration
&lt;/h3&gt;

&lt;p&gt;Add this to your CI pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dire &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It validates that all locales are complete and exits with code 1 if translations are missing. Catches incomplete i18n before deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup Orphaned Keys
&lt;/h3&gt;

&lt;p&gt;Over time, locale files accumulate keys that were deleted from the reference locale but not from translations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dire &lt;span class="nt"&gt;--prune&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes keys from non-reference locales that don't exist in your most complete locale. Keeps your files clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  Translate Specific Keys
&lt;/h3&gt;

&lt;p&gt;Only want to translate specific keys?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dire &lt;span class="nt"&gt;--keys&lt;/span&gt; &lt;span class="s2"&gt;"auth.login,auth.register"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multiple Provider Support
&lt;/h3&gt;

&lt;p&gt;Configure multiple providers and switch between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[providers]&lt;/span&gt;
&lt;span class="py"&gt;active&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude"&lt;/span&gt;

&lt;span class="nn"&gt;[[providers.configuration]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude"&lt;/span&gt;
&lt;span class="py"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"claude-3-5-haiku-latest"&lt;/span&gt;
&lt;span class="py"&gt;maxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;
&lt;span class="py"&gt;temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;

&lt;span class="nn"&gt;[[providers.configuration]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"openai"&lt;/span&gt;
&lt;span class="py"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gpt-4o-mini"&lt;/span&gt;
&lt;span class="py"&gt;maxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16000&lt;/span&gt;
&lt;span class="py"&gt;temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch providers without editing the config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dire &lt;span class="nt"&gt;--provider-active&lt;/span&gt; openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Supported Providers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Translation services&lt;/strong&gt;: DeepL, Google Translate, Azure AI Translator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI models&lt;/strong&gt;: Claude, OpenAI, Gemini, Mistral, DeepSeek&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You bring your own API key. No middleman, no markup, full control over costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This in Go
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native binary&lt;/strong&gt;: Single executable with no runtime dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Concurrent processing and smart batching handle large translation files efficiently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform&lt;/strong&gt;: Builds for Linux, macOS, Windows (amd64 + arm64)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt;: Distribute via npm but runs as a native binary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Design Decisions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No framework assumptions&lt;/strong&gt;: Works with any i18n library that uses JSON files. Doesn't care if you use react-i18next, vue-i18n, or something custom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BYOK (Bring Your Own Key)&lt;/strong&gt;: You own the relationship with your translation provider. Switch providers anytime, no data migration needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local-first&lt;/strong&gt;: Processes files locally, only talks to translation APIs when needed. Your translation data stays in your git repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single command&lt;/strong&gt;: Team members don't need to understand how it works. &lt;code&gt;npm run translate&lt;/code&gt; and they're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Use It
&lt;/h2&gt;

&lt;p&gt;Our workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Developer adds new English strings&lt;/li&gt;
&lt;li&gt;Runs &lt;code&gt;npm run translate&lt;/code&gt; (which calls &lt;code&gt;dire&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Reviews translations (AI gets it right ~80% of the time)&lt;/li&gt;
&lt;li&gt;Commits everything together&lt;/li&gt;
&lt;li&gt;CI validates completeness with &lt;code&gt;dire --check&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;--prune&lt;/code&gt; flag runs periodically in a cleanup PR to remove orphaned keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest Trade-offs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it solves:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual translation grunt work&lt;/li&gt;
&lt;li&gt;Keeping files in sync&lt;/li&gt;
&lt;li&gt;Orphaned key cleanup&lt;/li&gt;
&lt;li&gt;CI validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What it doesn't solve:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time collaboration (it's a CLI, not a translation platform with team features)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;t()&lt;/code&gt; function spam in your components (that's a framework issue)&lt;/li&gt;
&lt;li&gt;Context-specific translations (AI struggles with ambiguity)&lt;/li&gt;
&lt;li&gt;100% accuracy (you still need to review translations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI translation gets you 80% of the way there. You still need human review for context, tone, and domain-specific language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions I Get Asked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: How accurate are the translations?&lt;/strong&gt;&lt;br&gt;
A: Depends on the provider. DeepL and Claude are consistently 80-90% accurate for most languages. You still need human review, but it's much faster than translating from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What if I want to switch providers?&lt;/strong&gt;&lt;br&gt;
A: Change one line in your config or use the &lt;code&gt;--provider-active&lt;/code&gt; flag. Your translation files don't change, so switching is instant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Does it work with my i18n library?&lt;/strong&gt;&lt;br&gt;
A: If your library uses JSON files, yes. It doesn't care about your framework or i18n implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How much does it cost?&lt;/strong&gt;&lt;br&gt;
A: The tool is free. You pay only for API usage to your chosen provider. DeepL, for example, costs ~$5-20/month for most projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can I use it without AI?&lt;/strong&gt;&lt;br&gt;
A: Yes. Use &lt;code&gt;--stub&lt;/code&gt; to create placeholder translations (empty strings), then fill them in manually. Or use &lt;code&gt;--sourced&lt;/code&gt; to only apply glossary and memory translations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/juliandreas/dire-cli" rel="noopener noreferrer"&gt;https://github.com/juliandreas/dire-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built this to scratch my own itch, but I'm curious if it solves the same problems for others. Let me know what you think or what features would make it more useful for your workflow.&lt;/p&gt;

</description>
      <category>i18n</category>
      <category>go</category>
      <category>cli</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
