<?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: Nikolaos Protopapas</title>
    <description>The latest articles on DEV Community by Nikolaos Protopapas (@nikolaos_protopapas_d3bd6).</description>
    <link>https://dev.to/nikolaos_protopapas_d3bd6</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%2F1974383%2Ffd3bd146-0179-4b5a-832c-6debce121880.png</url>
      <title>DEV Community: Nikolaos Protopapas</title>
      <link>https://dev.to/nikolaos_protopapas_d3bd6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikolaos_protopapas_d3bd6"/>
    <language>en</language>
    <item>
      <title>Building Terminal UIs in .NET: How SharpConsoleUI Complements Terminal.Gui</title>
      <dc:creator>Nikolaos Protopapas</dc:creator>
      <pubDate>Fri, 06 Mar 2026 23:02:31 +0000</pubDate>
      <link>https://dev.to/nikolaos_protopapas_d3bd6/building-terminal-uis-in-net-how-sharpconsoleui-complements-terminalgui-hb9</link>
      <guid>https://dev.to/nikolaos_protopapas_d3bd6/building-terminal-uis-in-net-how-sharpconsoleui-complements-terminalgui-hb9</guid>
      <description>&lt;p&gt;The .NET terminal UI space has a clear champion: &lt;a href="https://github.com/gui-cs/Terminal.Gui" rel="noopener noreferrer"&gt;Terminal.Gui&lt;/a&gt;, created by Miguel de Icaza — the same mind behind Mono, Xamarin, and GNOME. With 10.7K stars, 1.6M NuGet downloads, and 199 contributors, it's the most mature and widely-used TUI framework in the .NET ecosystem. It set the standard for what's possible in a terminal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nickprotop/ConsoleEx" rel="noopener noreferrer"&gt;SharpConsoleUI&lt;/a&gt; is a much younger, much smaller project that takes a different architectural approach. It doesn't aim to replace Terminal.Gui — it fills a niche that Terminal.Gui wasn't designed for. This post explains the differences so you can pick the right tool for your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different Problems, Different Architectures
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terminal.Gui&lt;/strong&gt; follows the classic single-threaded event loop model — the same proven pattern as WinForms. One main loop handles input, layout, and painting. Background work is marshaled back via &lt;code&gt;Application.Invoke()&lt;/code&gt;. This is well-understood, safe, and works perfectly for form-based applications, dialogs, and configuration UIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SharpConsoleUI&lt;/strong&gt; uses a multi-threaded compositor model. Each window can have its own async thread updating independently, and the rendering engine composites everything together using a double-buffered cell pipeline with dirty-region tracking. This is closer to how a desktop compositor works — each window paints to its own buffer, and the screen-level buffer diff-scans and outputs only changed cells.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SharpConsoleUI: Each window updates on its own thread&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;monitor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WindowBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;windowSystem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"System Monitor [1s refresh]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithAsyncWindowThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;UpdateMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Doesn't block other windows&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither approach is objectively better — they solve different problems. Terminal.Gui's model is simpler and safer. SharpConsoleUI's model enables scenarios that are awkward in a single-threaded loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering Pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terminal.Gui v2&lt;/strong&gt; has a modern rendering system with TrueColor support, automatic 16-color fallback, and a LineCanvas that handles line intersection merging. Views draw within their bounds, and the framework manages repainting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SharpConsoleUI&lt;/strong&gt; implements a DOM-based Measure → Arrange → Paint pipeline (inspired by WPF/Avalonia) with three additional features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Occlusion culling&lt;/strong&gt; — content hidden behind other windows is never rendered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive rendering&lt;/strong&gt; — each line is analyzed and rendered as cells or full lines based on coverage heuristics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compositor hooks&lt;/strong&gt; — &lt;code&gt;PreBufferPaint&lt;/code&gt;/&lt;code&gt;PostBufferPaint&lt;/code&gt; let you manipulate the rendered buffer for effects like blur, fade, or animated backgrounds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All rendering flows through typed &lt;code&gt;Cell&lt;/code&gt; structs. ANSI is generated once at the terminal output boundary. On Unix, SharpConsoleUI bypasses .NET's Console infrastructure entirely using raw libc I/O — a technique inspired by Terminal.Gui v2's own approach to solving the terminal echo leak bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controls Comparison
&lt;/h2&gt;

&lt;p&gt;Both frameworks provide rich control libraries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Terminal.Gui v2&lt;/th&gt;
&lt;th&gt;SharpConsoleUI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Button, CheckBox, RadioGroup, TextField, TextView&lt;/td&gt;
&lt;td&gt;ButtonControl, CheckboxControl, PromptControl, MultilineEditControl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ListView, TableView (sorting/filtering), TreeView&lt;/td&gt;
&lt;td&gt;ListControl, TableControl, TreeControl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Menus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MenuBar, PopoverMenu&lt;/td&gt;
&lt;td&gt;MenuControl (horizontal/vertical, nested submenus)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tabs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TabView&lt;/td&gt;
&lt;td&gt;TabControl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Layout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FrameView, TileView, ScrollView&lt;/td&gt;
&lt;td&gt;ColumnContainer, HorizontalGridControl, ScrollablePanelControl, SplitterControl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dialogs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dialog, FileDialog, Wizard&lt;/td&gt;
&lt;td&gt;Built-in file picker, folder browser, notification system&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Terminal.Gui has more specialized controls:&lt;/strong&gt; ColorPicker, DatePicker, NumericUpDown, Wizard, CharMap, GraphView, FlagSelector. Its control library is broader — that's the advantage of 8 years and 199 contributors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SharpConsoleUI has unique controls&lt;/strong&gt; that don't exist in Terminal.Gui:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CanvasControl&lt;/strong&gt; — Free-form drawing surface with 30+ primitives (circles, polygons, gradients, arcs), supporting retained and immediate rendering modes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TerminalControl&lt;/strong&gt; — Embedded PTY-based terminal emulator (run bash, vim, htop inside your TUI app)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SparklineControl&lt;/strong&gt; — Real-time sparkline graphs (block, braille, bidirectional modes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BarGraphControl&lt;/strong&gt; — Horizontal bar graphs with gradient color thresholds&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Spectre.Console
&lt;/h2&gt;

&lt;p&gt;Terminal.Gui has its own rendering and theming system — it's self-contained.&lt;/p&gt;

&lt;p&gt;SharpConsoleUI is built on top of Spectre.Console. Any &lt;code&gt;[bold red]markup[/]&lt;/code&gt; works anywhere, and any Spectre &lt;code&gt;IRenderable&lt;/code&gt; (Tables, Trees, BarCharts, Panels) can be used as a control via &lt;code&gt;SpectreRenderableControl&lt;/code&gt;. If you're already in the Spectre.Console ecosystem, SharpConsoleUI extends it rather than replacing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layout
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Terminal.Gui&lt;/strong&gt; uses &lt;code&gt;Pos&lt;/code&gt; and &lt;code&gt;Dim&lt;/code&gt; types for powerful relative positioning — &lt;code&gt;Pos.Center()&lt;/code&gt;, &lt;code&gt;Pos.Percent(50)&lt;/code&gt;, &lt;code&gt;Dim.Fill()&lt;/code&gt;, &lt;code&gt;Dim.Auto()&lt;/code&gt;. This is more expressive for complex layouts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SharpConsoleUI&lt;/strong&gt; uses a simpler stack/grid/fill model with alignment properties. It covers common layouts well but doesn't match Terminal.Gui's positioning flexibility.&lt;/p&gt;

&lt;p&gt;Terminal.Gui wins on layout expressiveness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Terminal.Gui&lt;/th&gt;
&lt;th&gt;SharpConsoleUI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stars&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10,700&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NuGet Downloads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.6M&lt;/td&gt;
&lt;td&gt;6.4K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contributors&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;199&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;First Release&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2017&lt;/td&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;v2 beta&lt;/td&gt;
&lt;td&gt;v2.4 stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.NET&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;.NET Standard 2.0+&lt;/td&gt;
&lt;td&gt;.NET 9.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These numbers tell the story. Terminal.Gui is a mature, community-driven project with years of battle-testing. SharpConsoleUI is a solo project that's been in active development for about a year. If community size and ecosystem maturity are your primary concerns, Terminal.Gui is the obvious choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Reach for SharpConsoleUI
&lt;/h2&gt;

&lt;p&gt;SharpConsoleUI makes sense when you need something Terminal.Gui wasn't designed around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-window dashboards&lt;/strong&gt; where each panel updates independently at different rates — the per-window async thread model makes this natural&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time animations or high-frequency updates&lt;/strong&gt; — the compositor architecture achieves 25+ fps over SSH&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compositor effects&lt;/strong&gt; — buffer-level post-processing for transitions, blur, custom rendering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedded terminal emulators&lt;/strong&gt; — the PTY-based TerminalControl runs real shell processes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free-form drawing&lt;/strong&gt; — CanvasControl for games, visualizations, or interactive graphics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spectre.Console extension&lt;/strong&gt; — adding windowing to an existing Spectre.Console project&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Reach for Terminal.Gui
&lt;/h2&gt;

&lt;p&gt;For most .NET terminal applications, Terminal.Gui is the right choice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Largest community and most third-party resources&lt;/li&gt;
&lt;li&gt;Broadest .NET compatibility (.NET Standard 2.0)&lt;/li&gt;
&lt;li&gt;More specialized controls (ColorPicker, DatePicker, Wizard)&lt;/li&gt;
&lt;li&gt;Proven in production across the ecosystem&lt;/li&gt;
&lt;li&gt;Familiar WinForms-like programming model&lt;/li&gt;
&lt;li&gt;Better layout system for complex relative positioning&lt;/li&gt;
&lt;li&gt;Active development with 199 contributors&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Acknowledgments
&lt;/h2&gt;

&lt;p&gt;Terminal.Gui paved the way for serious TUI development in .NET. SharpConsoleUI's own Unix input handling was directly inspired by Terminal.Gui v2's approach to bypassing .NET's Console APIs. Building on the shoulders of the work done by Miguel de Icaza and the Terminal.Gui community is what open source is about.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Terminal.Gui&lt;/strong&gt; powers numerous production tools across the .NET ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SharpConsoleUI&lt;/strong&gt; powers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/nickprotop/ServerHub" rel="noopener noreferrer"&gt;ServerHub&lt;/a&gt; — Linux server monitoring dashboard with 14 real-time widgets&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nickprotop/lazynuget" rel="noopener noreferrer"&gt;LazyNuGet&lt;/a&gt; — Terminal-based NuGet package manager&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nickprotop/lazydotide" rel="noopener noreferrer"&gt;LazyDotIDE&lt;/a&gt; — Console-based .NET IDE with LSP IntelliSense&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SharpConsoleUI: &lt;a href="https://github.com/nickprotop/ConsoleEx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://www.nuget.org/packages/SharpConsoleUI/" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt; | &lt;a href="https://nickprotop.github.io/ConsoleEx/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt; | &lt;a href="https://www.youtube.com/watch?v=sl5C9jrJknM" rel="noopener noreferrer"&gt;Video Demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Terminal.Gui: &lt;a href="https://github.com/gui-cs/Terminal.Gui" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://www.nuget.org/packages/Terminal.Gui/" rel="noopener noreferrer"&gt;NuGet&lt;/a&gt; | &lt;a href="https://gui-cs.github.io/Terminal.Gui/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>terminal</category>
      <category>opensource</category>
    </item>
    <item>
      <title>lazygit-style TUI for NuGet</title>
      <dc:creator>Nikolaos Protopapas</dc:creator>
      <pubDate>Sat, 21 Feb 2026 17:22:09 +0000</pubDate>
      <link>https://dev.to/nikolaos_protopapas_d3bd6/lazygit-style-tui-for-nuget-25fh</link>
      <guid>https://dev.to/nikolaos_protopapas_d3bd6/lazygit-style-tui-for-nuget-25fh</guid>
      <description>&lt;p&gt;If you've ever used &lt;a href="https://github.com/jesseduffield/lazygit" rel="noopener noreferrer"&gt;lazygit&lt;/a&gt;, you know the feeling. Git stops being a chore and becomes almost enjoyable. You can see everything at once, navigate with the keyboard, and do in seconds what used to take several commands.&lt;/p&gt;

&lt;p&gt;I wanted that for NuGet.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet list package --outdated&lt;/code&gt; gives you a wall of text. Updating means running a separate command for each package. Checking for vulnerabilities is another command. Searching NuGet.org is yet another. And if you're working across a solution with 10+ projects, you're context-switching constantly.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;LazyNuGet&lt;/strong&gt;.&lt;/p&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%2Fuwm27h98nzymk9jm3tub.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%2Fuwm27h98nzymk9jm3tub.png" alt="LazyNuGet dashboard" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You point it at a folder and it scans for all &lt;code&gt;.csproj&lt;/code&gt; files. Every project, every package, every version — all in one place. In the background it quietly checks NuGet.org for updates and vulnerability data. When it's done, the outdated packages light up yellow. Vulnerable ones show a red badge. You don't have to ask.&lt;/p&gt;

&lt;p&gt;From there you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Update&lt;/strong&gt; a single package or everything at once (&lt;code&gt;Ctrl+U&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search NuGet.org&lt;/strong&gt; without leaving the terminal (&lt;code&gt;Ctrl+S&lt;/code&gt;) — browse results, read descriptions, pick a version, install to one or multiple projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View the dependency tree&lt;/strong&gt; for any project or package (&lt;code&gt;Ctrl+D&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrate deprecated packages&lt;/strong&gt; to their recommended replacements in one step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browse version history&lt;/strong&gt;, release notes, security advisories, and download stats in tabbed panels (F1–F5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track operation history&lt;/strong&gt; and undo or retry recent changes (&lt;code&gt;Ctrl+H&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Work with private feeds&lt;/strong&gt; — authenticated NuGet sources with stored credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's fully usable with both keyboard and mouse.&lt;/p&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%2Fvbtnlxeilxnhwsj4chn5.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%2Fvbtnlxeilxnhwsj4chn5.png" alt="Package details" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selecting a package gives you a tabbed view — overview, dependencies, version history, release notes, and security advisories. All fetched live from NuGet.org.&lt;/p&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%2F1wkknrphgicut0yk5aew.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%2F1wkknrphgicut0yk5aew.png" alt="Search NuGet.org" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The search modal lets you browse NuGet.org, read package descriptions, and install to one or multiple projects — without leaving the terminal.&lt;/p&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%2Fvnondzmtczwpamdn6sso.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%2Fvnondzmtczwpamdn6sso.png" alt="Dependency tree" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you have .NET 10 installed:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; LazyNuGet
lazynuget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Or grab the self-contained binary&lt;/strong&gt; (no .NET required):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/nickprotop/lazynuget/main/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then just run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lazynuget /path/to/your/solution
&lt;span class="c"&gt;# or just cd there and run lazynuget&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Built with .NET 10, &lt;a href="https://spectreconsole.net/" rel="noopener noreferrer"&gt;Spectre.Console&lt;/a&gt;, and &lt;a href="https://github.com/nickprotop/ConsoleEx" rel="noopener noreferrer"&gt;SharpConsoleUI&lt;/a&gt; — a TUI layout library I also wrote that handles responsive panels, window management, and mouse support in the terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  It's early
&lt;/h2&gt;

&lt;p&gt;If you try it, I'd genuinely love to hear what breaks or what's missing. Issues and stars both appreciated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/nickprotop/lazynuget" rel="noopener noreferrer"&gt;github.com/nickprotop/lazynuget&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>nuget</category>
      <category>terminal</category>
    </item>
    <item>
      <title>How I Translated 277 Strings in 5 Minutes (Real-World Case Study)</title>
      <dc:creator>Nikolaos Protopapas</dc:creator>
      <pubDate>Sun, 04 Jan 2026 18:44:15 +0000</pubDate>
      <link>https://dev.to/nikolaos_protopapas_d3bd6/how-i-translated-277-strings-in-5-minutes-real-world-case-study-4j47</link>
      <guid>https://dev.to/nikolaos_protopapas_d3bd6/how-i-translated-277-strings-in-5-minutes-real-world-case-study-4j47</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ShareX/ShareX" rel="noopener noreferrer"&gt;ShareX&lt;/a&gt; is an amazing open-source screen capture tool with 35K+ GitHub stars. It supports 24 languages with 170 translatable strings.&lt;/p&gt;

&lt;p&gt;I wanted to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;strong&gt;Greek&lt;/strong&gt; as a new language&lt;/li&gt;
&lt;li&gt;Complete &lt;strong&gt;Spanish&lt;/strong&gt; translations&lt;/li&gt;
&lt;li&gt;Find any unused localization keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I wanted to do it &lt;strong&gt;for free&lt;/strong&gt; - no paid translation APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool: LRM
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager" rel="noopener noreferrer"&gt;LRM (Localization Resource Manager)&lt;/a&gt; is an open-source CLI tool I built for managing localization files. It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.resx&lt;/strong&gt; (ShareX uses this), JSON, i18next, Android, iOS, PO, XLIFF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free translation providers&lt;/strong&gt;: MyMemory, Lingva, LibreTranslate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local LLMs&lt;/strong&gt;: Ollama (Llama, Mistral, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid providers&lt;/strong&gt;: DeepL, Google, Azure, OpenAI, Claude (if you need them)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Check Current Status
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/ShareX/ShareX/Properties
lrm stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                     Localization Statistics                     │
├──────────┬──────┬────────────┬───────┬─────────┬─────────┬──────┤
│ Language │ Keys │ Translated │ Empty │ Missing │ Coverage│ Size │
├──────────┼──────┼────────────┼───────┼─────────┼─────────┼──────┤
│ default  │  170 │        170 │     0 │       0 │  100.0% │ 48KB │
│ de       │  163 │        163 │     0 │       7 │   95.9% │ 47KB │
│ es       │   63 │         63 │     0 │     107 │   37.1% │ 20KB │
│ fr       │  170 │        170 │     0 │       0 │  100.0% │ 50KB │
│ ...      │      │            │       │         │         │      │
└──────────┴──────┴────────────┴───────┴─────────┴─────────┴──────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spanish at 37%? Let's fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add Greek (New Language)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lrm add-language el
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Created: Resources.el.resx
  Keys: 170 (copied from default)
  All values empty - ready for translation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Translate with Free Providers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option A: MyMemory (Free, No API Key)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Translate Greek (all strings)&lt;/span&gt;
lrm translate &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; mymemory &lt;span class="nt"&gt;--from&lt;/span&gt; en &lt;span class="nt"&gt;--to&lt;/span&gt; el

&lt;span class="c"&gt;# Complete Spanish (only missing)&lt;/span&gt;
lrm translate &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; mymemory &lt;span class="nt"&gt;--from&lt;/span&gt; en &lt;span class="nt"&gt;--to&lt;/span&gt; es &lt;span class="nt"&gt;--only-missing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option B: Lingva (Free Google Translate Proxy)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lrm translate &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; lingva &lt;span class="nt"&gt;--from&lt;/span&gt; en &lt;span class="nt"&gt;--to&lt;/span&gt; el
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option C: Local Ollama (100% Offline)
&lt;/h3&gt;

&lt;p&gt;If you have &lt;a href="https://ollama.ai" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; running locally:&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="c"&gt;# Use Llama 3.2 or any model you have&lt;/span&gt;
lrm translate &lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; ollama &lt;span class="nt"&gt;--from&lt;/span&gt; en &lt;span class="nt"&gt;--to&lt;/span&gt; el
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure the model in &lt;code&gt;lrm.json&lt;/code&gt;:&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;"Translation"&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;"AIProviders"&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;"Ollama"&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;"Model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"llama3.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ApiUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:11434"&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;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;
  
  
  Step 4: Validate
&lt;/h2&gt;



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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Validation passed
  Files: 25
  Issues: 0
  Placeholder mismatches: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LRM automatically checks that &lt;code&gt;{0}&lt;/code&gt;, &lt;code&gt;{1}&lt;/code&gt; placeholders are preserved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Find Unused Keys
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lrm scan &lt;span class="nt"&gt;--show-unused&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code Scan Results
─────────────────
Source files scanned: 847
Localization keys: 170

Unused Keys (5):
  • HotkeyType_DisableHotkeys
  • QuickTaskMenuEditorForm_Separator
  • TaskSettings_CaptureAutoHideTaskbar
  • ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Found 5 keys that exist in .resx but aren't used in code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Strings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Greek&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;170 new&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spanish&lt;/td&gt;
&lt;td&gt;37%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;107 added&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Total: 277 strings in ~5 minutes&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Team Collaboration with LRM Cloud
&lt;/h2&gt;

&lt;p&gt;For team projects, sync to &lt;a href="https://lrm-cloud.com" rel="noopener noreferrer"&gt;LRM Cloud&lt;/a&gt; (free tier available):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lrm cloud push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fqpu6rlk1jjfjco696f14.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%2Fqpu6rlk1jjfjco696f14.png" alt="LRM Cloud Dashboard" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web-based translation editor&lt;/li&gt;
&lt;li&gt;Translation Memory &amp;amp; Glossary&lt;/li&gt;
&lt;li&gt;GitHub integration&lt;/li&gt;
&lt;li&gt;Role-based team access&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Full Case Study
&lt;/h2&gt;

&lt;p&gt;See the complete walkthrough with all CLI output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager/blob/main/docs/case-studies/sharex.md" rel="noopener noreferrer"&gt;ShareX Case Study on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download LRM&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://github.com/nickprotop/LocalizationManager/releases/latest/download/lrm-linux-x64.tar.gz | &lt;span class="nb"&gt;tar &lt;/span&gt;xz

&lt;span class="c"&gt;# Or on Windows&lt;/span&gt;
&lt;span class="c"&gt;# Download from GitHub Releases&lt;/span&gt;

&lt;span class="c"&gt;# Check your project&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ./YourProject/Resources
lrm stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager" rel="noopener noreferrer"&gt;LRM GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lrm-cloud.com" rel="noopener noreferrer"&gt;LRM Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager/blob/main/docs/case-studies/sharex.md" rel="noopener noreferrer"&gt;Full ShareX Case Study&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have questions? Drop a comment or open a &lt;a href="https://github.com/nickprotop/LocalizationManager/discussions" rel="noopener noreferrer"&gt;GitHub Discussion&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>localization</category>
      <category>opensource</category>
      <category>dotnet</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The $0 Localization Stack for Solo .NET Developers</title>
      <dc:creator>Nikolaos Protopapas</dc:creator>
      <pubDate>Mon, 29 Dec 2025 22:58:02 +0000</pubDate>
      <link>https://dev.to/nikolaos_protopapas_d3bd6/the-0-localization-stack-for-solo-net-developers-3i62</link>
      <guid>https://dev.to/nikolaos_protopapas_d3bd6/the-0-localization-stack-for-solo-net-developers-3i62</guid>
      <description>&lt;h1&gt;
  
  
  How I Translated My .NET App to 5 Languages for $0 (As a Solo Dev)
&lt;/h1&gt;

&lt;p&gt;I'm a solo developer with a medical practice app built in .NET. It started as an English-only app, then users asked for Greek. Then more languages. That's when I discovered localization platforms cost &lt;strong&gt;$100-500/month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a side project? No way.&lt;/p&gt;

&lt;p&gt;So I built my own solution. Here's how you can translate your .NET app for free too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I had 400+ strings in my app. I needed translations for at least Greek and English, ideally more. My options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lokalise&lt;/td&gt;
&lt;td&gt;$120/mo&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phrase&lt;/td&gt;
&lt;td&gt;$25/mo&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual Google Translate&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Hours of copy-paste hell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hire translator&lt;/td&gt;
&lt;td&gt;$100+&lt;/td&gt;
&lt;td&gt;Days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these worked for a solo dev with a side project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Local LLM Translation
&lt;/h2&gt;

&lt;p&gt;What if you could run a translation model &lt;strong&gt;on your own machine&lt;/strong&gt;? No API costs, no rate limits, no data leaving your computer.&lt;/p&gt;

&lt;p&gt;That's exactly what I built into LRM (Localization Resource Manager).&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup (5 minutes)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Install Ollama&lt;/strong&gt; (the local LLM runtime):&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="c"&gt;# Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://ollama.ai/install.sh | sh

&lt;span class="c"&gt;# Windows/Mac - download from https://ollama.ai&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Pull a model:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.2
&lt;span class="c"&gt;# Or for better quality: ollama pull llama3.1:8b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Install LRM:&lt;/strong&gt;&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="c"&gt;# Linux (one-liner)&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://raw.githubusercontent.com/nickprotop/LocalizationManager/main/install-lrm.sh | bash

&lt;span class="c"&gt;# Or via APT (Ubuntu/Debian)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;add-apt-repository ppa:nickprotop/lrm-tool
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;lrm-standalone

&lt;span class="c"&gt;# Windows/macOS - download from GitHub releases&lt;/span&gt;
&lt;span class="c"&gt;# https://github.com/nickprotop/LocalizationManager/releases&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No API keys, no accounts, no credit cards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Translate Everything
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Navigate to your project&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;MyApp/Resources

&lt;span class="c"&gt;# Translate all missing keys to Spanish, French, German&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; ollama &lt;span class="nt"&gt;--target-languages&lt;/span&gt; es,fr,de
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. LRM finds all your resource files, identifies missing translations, and fills them in using the local LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;400+ keys × 3 languages = 1,200+ translations. Zero dollars. Under 10 minutes.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No GPU? No Problem.
&lt;/h2&gt;

&lt;p&gt;Don't have a beefy GPU? Use the free online providers - still $0:&lt;/p&gt;

&lt;h3&gt;
  
  
  MyMemory (No signup required)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; mymemory &lt;span class="nt"&gt;--target-languages&lt;/span&gt; es,fr,de
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5,000 characters/day free. Enough for most projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lingva (Google Translate quality, free)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; lingva &lt;span class="nt"&gt;--target-languages&lt;/span&gt; es,fr,de
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uses open-source proxy to Google Translate. No API key needed.&lt;/p&gt;

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

&lt;p&gt;Here's my actual workflow for adding Italian to my app:&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="c"&gt;# Add a new language file&lt;/span&gt;
lrm add-language &lt;span class="nt"&gt;--culture&lt;/span&gt; it

&lt;span class="c"&gt;# Check what's missing&lt;/span&gt;
lrm stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Localization Statistics
┌───────────────┬────────────┬───────────┬───────┬──────────┬───────────┐
│ Language      │ Total Keys │ Completed │ Empty │ Coverage │ File Size │
├───────────────┼────────────┼───────────┼───────┼──────────┼───────────┤
│ Default       │ 12         │ 12        │ 0     │ 100.0%   │ 1.8 KB    │
│ Deutsch (de)  │ 12         │ 5         │ 7     │ 41.7%    │ 1.6 KB    │
│ français (fr) │ 12         │ 7         │ 5     │ 58.3%    │ 1.7 KB    │
│ italiano (it) │ 12         │ 0         │ 12    │ 0.0%     │ 1.6 KB    │
└───────────────┴────────────┴───────────┴───────┴──────────┴───────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Italian at 0%! Let's fix that:&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="c"&gt;# Translate Italian with one command&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; ollama &lt;span class="nt"&gt;--target-languages&lt;/span&gt; it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╭────────────────────┬──────────┬─────────────────────────────────────┬────────╮
│ Key                │ Language │ Translation                         │ Status │
├────────────────────┼──────────┼─────────────────────────────────────┼────────┤
│ AppTitle           │ it       │ Responsabile dello studio medico    │ ✓      │
│ WelcomeMessage     │ it       │ Eccoci di nuovo, {0}!               │ ✓      │
│ LoginButton        │ it       │ Accedi                              │ ✓      │
│ LogoutButton       │ it       │ Disconnettere                       │ ✓      │
│ SaveButton         │ it       │ Salva                               │ ✓      │
│ CancelButton       │ it       │ Cancella                            │ ✓      │
│ DeleteConfirmation │ it       │ Sei sicuro di voler eliminare...    │ ✓      │
│ ErrorOccurred      │ it       │ Si è verificato un errore...        │ ✓      │
│ PatientName        │ it       │ Nome del paziente                   │ ✓      │
│ AppointmentDate    │ it       │ Data prenotazione                   │ ✓      │
│ NoResultsFound     │ it       │ Nessun risultato trovato            │ ✓      │
│ SearchPlaceholder  │ it       │ Cerca pazienti                      │ ✓      │
╰────────────────────┴──────────┴─────────────────────────────────────┴────────╯

Translated 12 of 12 items.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;12 keys translated in seconds. Done.&lt;/p&gt;

&lt;p&gt;The CLI uses colorized output - missing translations show in red, complete in green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality Comparison
&lt;/h2&gt;

&lt;p&gt;"But is the quality good enough?"&lt;/p&gt;

&lt;p&gt;I compared Ollama (llama3.2) vs Google Translate vs DeepL for my app strings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepL&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;$20/mo&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Very Good&lt;/td&gt;
&lt;td&gt;Pay-per-use&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ollama (llama3.2)&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MyMemory&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For UI strings like "Save", "Cancel", "Error occurred"? Ollama is &lt;strong&gt;more than good enough&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For marketing copy or legal text? Maybe pay for DeepL.&lt;/p&gt;

&lt;p&gt;For 90% of app localization? Free works fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Than Just Translation
&lt;/h2&gt;

&lt;p&gt;Once I built the translation part, I kept going. LRM now does:&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="c"&gt;# Validate resource files (find issues)&lt;/span&gt;
lrm validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠ Validation found 12 issue(s)

                                  Empty Values
┌──────────┬───────────────────────────────────────────────────────────────────┐
│ Language │ Empty Keys                                                        │
├──────────┼───────────────────────────────────────────────────────────────────┤
│ de       │ CancelButton, DeleteConfirmation, ErrorOccurred, PatientName...   │
│ fr       │ ErrorOccurred, PatientName, AppointmentDate, NoResultsFound...    │
└──────────┴───────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Find Dead Code (.NET only)
&lt;/h3&gt;

&lt;p&gt;This one's a game-changer. LRM scans your C#, Razor, and XAML files to find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Missing keys&lt;/strong&gt; - used in code but not defined in .resx (runtime errors waiting to happen!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unused keys&lt;/strong&gt; - defined in .resx but never used in code (dead translations)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lrm scan &lt;span class="nt"&gt;--source-path&lt;/span&gt; ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Scanned 3 files
Found 18 key references (16 unique keys)

              Missing Keys (in code, not in .resx)
╭─────────────────────┬────────────┬───────────────────────────╮
│ Key                 │ References │ Locations                 │
├─────────────────────┼────────────┼───────────────────────────┤
│ AboutUs             │ 1          │ HomeController.cs:31      │
│ AppSubtitle         │ 1          │ HomeController.cs:22      │
│ CompanyDescription  │ 1          │ HomeController.cs:32      │
│ GetStarted          │ 1          │ Index.cshtml:9            │
│ OperationFailed     │ 1          │ NotificationService.cs:23 │
│ OperationSuccessful │ 1          │ NotificationService.cs:17 │
│ ... and 7 more      │            │                           │
╰─────────────────────┴────────────┴───────────────────────────╯

✗ Found 13 missing keys and 2 unused keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;13 keys used in code that don't exist in my .resx files! Those would be runtime errors. Now I can add them before users see broken UI.&lt;/p&gt;

&lt;p&gt;On a larger project (my actual app), the scan also found the opposite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Scanned 497 files
Found 607 key references (394 unique keys)

 Unused Keys (in .resx, not in code)
╭────────────────────────────┬───────╮
│ Key                        │ Count │
├────────────────────────────┼───────┤
│ ActiveAdmissions           │ -     │
│ AdmissionCount             │ -     │
│ DailyAdmissions            │ -     │
│ LanguageAutoDetect         │ -     │
│ MarkAsComplete             │ -     │
│ ... and 51 more            │       │
╰────────────────────────────┴───────╯

✗ Found 0 missing keys and 56 unused keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;56 dead keys I can clean up. Less clutter, smaller resource files.&lt;/p&gt;

&lt;h3&gt;
  
  
  More Tools
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Interactive editor (TUI)&lt;/span&gt;
lrm edit

&lt;span class="c"&gt;# Convert between formats&lt;/span&gt;
lrm convert &lt;span class="nt"&gt;--from&lt;/span&gt; resx &lt;span class="nt"&gt;--to&lt;/span&gt; json

&lt;span class="c"&gt;# Export for translators&lt;/span&gt;
lrm &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Works With Your Stack
&lt;/h2&gt;

&lt;p&gt;LRM supports what you're already using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.resx&lt;/strong&gt; (WPF, WinForms, ASP.NET)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON&lt;/strong&gt; (ASP.NET Core, Blazor)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;i18next&lt;/strong&gt; (React, Vue, Angular)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android&lt;/strong&gt; strings.xml&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS&lt;/strong&gt; .strings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All formats, one tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workflow That Actually Works
&lt;/h2&gt;

&lt;p&gt;Here's my complete localization workflow now:&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="c"&gt;# 1. Add new English strings normally in your app&lt;/span&gt;

&lt;span class="c"&gt;# 2. Validate (catch issues early)&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# 3. Translate missing keys&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; ollama

&lt;span class="c"&gt;# 4. Quick review in TUI&lt;/span&gt;
lrm edit

&lt;span class="c"&gt;# 5. Commit&lt;/span&gt;
git add Resources/
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add translations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total time: 5 minutes. Total cost: $0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Open-Sourced It
&lt;/h2&gt;

&lt;p&gt;I built this for myself. Then I realized other solo devs have the same problem.&lt;/p&gt;

&lt;p&gt;So I open-sourced it: &lt;strong&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager" rel="noopener noreferrer"&gt;github.com/nickprotop/LocalizationManager&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MIT License (use it however you want)&lt;/li&gt;
&lt;li&gt;Works 100% offline&lt;/li&gt;
&lt;li&gt;No accounts, no tracking&lt;/li&gt;
&lt;li&gt;Self-host everything&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  From Side Project to Product
&lt;/h2&gt;

&lt;p&gt;What started as "I need to translate my app" grew into something bigger. The CLI worked so well that I kept adding features. Then I built a web UI. Then cloud sync. Then OTA updates.&lt;/p&gt;

&lt;p&gt;Now it's a full platform: &lt;strong&gt;&lt;a href="https://lrm-cloud.com" rel="noopener noreferrer"&gt;lrm-cloud.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The cloud adds what solo devs need when they grow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Format agnostic&lt;/strong&gt; - One project syncs to RESX, JSON, or i18next&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web editor&lt;/strong&gt; - Edit translations from anywhere, no CLI needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTA updates&lt;/strong&gt; - Update translations without redeploying (first OTA for .NET!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub sync&lt;/strong&gt; - Bidirectional sync with your repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation Memory&lt;/strong&gt; - Reuses past translations, saves API costs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glossary&lt;/strong&gt; - Enforce consistent terminology&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invite translators&lt;/strong&gt; - They edit in browser, you review and approve&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free tier&lt;/strong&gt; - 3 projects, 5K chars/month - enough for side projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here's the thing: &lt;strong&gt;the CLI is still 100% free and open source.&lt;/strong&gt; The cloud is optional - and self-hosted if you want!&lt;/p&gt;

&lt;p&gt;I'm not ashamed of building a product from solving my own problem. That's how the best tools get built. You scratch your own itch, then share the solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install (Linux)&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://raw.githubusercontent.com/nickprotop/LocalizationManager/main/install-lrm.sh | bash
ollama pull llama3.2

&lt;span class="c"&gt;# Translate your app for free&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;YourProject/Resources
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; ollama &lt;span class="nt"&gt;--target-languages&lt;/span&gt; es,fr,de,it,pt

&lt;span class="c"&gt;# Done. $0. 5 minutes.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Got questions?&lt;/strong&gt; Open an issue on GitHub or find me on Reddit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Found it useful?&lt;/strong&gt; A GitHub star helps others discover it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: dotnet, localization, i18n, translation, ollama, llm, open-source, csharp, blazor, aspnetcore&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Automating .NET Localization: From Code Scan to Auto-Translate to Pull Request</title>
      <dc:creator>Nikolaos Protopapas</dc:creator>
      <pubDate>Fri, 14 Nov 2025 21:34:56 +0000</pubDate>
      <link>https://dev.to/nikolaos_protopapas_d3bd6/automating-net-localization-from-code-scan-to-auto-translate-to-pull-request-2ond</link>
      <guid>https://dev.to/nikolaos_protopapas_d3bd6/automating-net-localization-from-code-scan-to-auto-translate-to-pull-request-2ond</guid>
      <description>&lt;p&gt;A cross-platform CLI/TUI for managing .resx files with free AI translation, code scanning, and CI/CD automation&lt;/p&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%2Fu66x5lf8cm5v7sng6qqu.gif" 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%2Fu66x5lf8cm5v7sng6qqu.gif" alt=" " width="1028" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Every .NET Developer Faces
&lt;/h2&gt;

&lt;p&gt;You've built a great .NET application. Now you need to support multiple languages. You create your first &lt;code&gt;.resx&lt;/code&gt; file, add some keys, and everything works beautifully in English. Then the requests come in: "Can we support French? German? Greek?"&lt;/p&gt;

&lt;p&gt;You create &lt;code&gt;Resources.fr.resx&lt;/code&gt;, copy all the keys, send them to a translator, paste the translations back... and your build breaks. An XML tag got corrupted. A key is missing. A duplicate slipped in. You're now manually diff-ing XML files trying to figure out what went wrong.&lt;/p&gt;

&lt;p&gt;If you're on Linux or macOS, it's even worse. Visual Studio's ResX Resource Manager? Windows-only. Editing XML by hand? Error-prone and painful. You end up writing bash scripts that barely work, or worse, you spin up a Windows VM just to manage resource files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There had to be a better way.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding .NET Localization: The Foundation
&lt;/h2&gt;

&lt;p&gt;Before we dive into the solution, let's understand how .NET actually handles localization. If you've ever wondered what those &lt;code&gt;.resx&lt;/code&gt; files really are and how they work at runtime, this section is for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Are .resx Files?
&lt;/h3&gt;

&lt;p&gt;Resource files (&lt;code&gt;.resx&lt;/code&gt;) are XML-based files that store localized strings, images, and other resources. Here's what a typical resource file looks 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="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;root&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"WelcomeMessage"&lt;/span&gt; &lt;span class="na"&gt;xml:space=&lt;/span&gt;&lt;span class="s"&gt;"preserve"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;Welcome to our application!&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;comment&amp;gt;&lt;/span&gt;Shown on the home page&lt;span class="nt"&gt;&amp;lt;/comment&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/data&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"SaveButton"&lt;/span&gt; &lt;span class="na"&gt;xml:space=&lt;/span&gt;&lt;span class="s"&gt;"preserve"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/data&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;data&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"CancelButton"&lt;/span&gt; &lt;span class="na"&gt;xml:space=&lt;/span&gt;&lt;span class="s"&gt;"preserve"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;Cancel&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/data&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/root&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; element contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt;: The key you use in code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;value&lt;/strong&gt;: The translated text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;comment&lt;/strong&gt; (optional): Context for translators&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Resource File Naming Convention
&lt;/h3&gt;

&lt;p&gt;.NET uses a specific naming convention to organize translations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Resources/
├── SharedResources.resx          ← Default/invariant culture (usually English)
├── SharedResources.fr.resx       ← French
├── SharedResources.de.resx       ← German
├── SharedResources.el.resx       ← Greek
└── SharedResources.fr-CA.resx    ← French Canadian (specific culture)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is simple: &lt;code&gt;{BaseName}.{culture-code}.resx&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How the ResourceManager Works
&lt;/h3&gt;

&lt;p&gt;When your application runs, .NET's &lt;code&gt;ResourceManager&lt;/code&gt; automatically loads the correct resource file based on the current culture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User's Culture: fr-CA (French Canadian)
                  │
                  ↓
         Look for: SharedResources.fr-CA.resx
                  │
                  ↓ (not found)
         Fall back to: SharedResources.fr.resx
                  │
                  ↓ (not found)
         Fall back to: SharedResources.resx (default)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fallback mechanism ensures users always see &lt;em&gt;something&lt;/em&gt;, even if a specific translation is missing. But it also means missing translations can go unnoticed until production.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Culture Fallback Chain
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│  Application Runtime                                    │
│                                                         │
│  CurrentUICulture: "el-GR" (Greek - Greece)             │
└────────────────────┬────────────────────────────────────┘
                     │
                     ↓
        ┌─────────────────────────────────────────────────┐
        │ ResourceManager.GetString("WelcomeMessage")     │
        └────────────┬────────────────────────────────────┘
                     │
                     ↓
        ┌─────────────────────────────────────────────────┐
        │ 1. Try: Resources.el-GR.resx                    │
        │     File not found                              │
        └────────────┬────────────────────────────────────┘
                     │
                     ↓
        ┌─────────────────────────────────────────────────┐
        │ 2. Try: Resources.el.resx                       │
        │     Found! Return Greek translation             │
        └─────────────────────────────────────────────────┘
                     │
                     ↓ (if not found)
        ┌─────────────────────────────────────────────────┐
        │ 3. Fallback: Resources.resx                     │
        │     Return default (English)                    │
        └─────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Pain Points in .NET Localization
&lt;/h3&gt;

&lt;p&gt;Through building LocalizationManager, I encountered (and solved) these recurring issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;XML Corruption&lt;/strong&gt;: One misplaced &lt;code&gt;&amp;lt;&lt;/code&gt; or &lt;code&gt;&amp;amp;&lt;/code&gt; breaks your entire build&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing Translations&lt;/strong&gt;: Keys exist in the default file but not in translated files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate Keys&lt;/strong&gt;: Same key defined twice = build error&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty Values&lt;/strong&gt;: Is an empty &lt;code&gt;&amp;lt;value&amp;gt;&amp;lt;/value&amp;gt;&lt;/code&gt; intentional or a mistake?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync Issues&lt;/strong&gt;: You add a key to &lt;code&gt;Resources.resx&lt;/code&gt; but forget to add it to all language files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Validation&lt;/strong&gt;: You only discover issues when the build fails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual Translation&lt;/strong&gt;: Expensive, slow, and doesn't scale&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why Existing Tools Fall Short
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual Studio&lt;/strong&gt;: Windows-only, requires full IDE installation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ResX Resource Manager&lt;/strong&gt;: Excellent tool, but also Windows-only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual XML Editing&lt;/strong&gt;: Works everywhere, but error-prone and lacks validation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Online Tools&lt;/strong&gt;: Require uploading sensitive data, no local workflow integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Scripts&lt;/strong&gt;: Everyone reinvents the wheel poorly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The gap was clear&lt;/strong&gt;: We needed a cross-platform, command-line-first tool that could handle the entire localization workflow from validation to automated translation to CI/CD integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: LocalizationManager
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://github.com/nickprotop/LocalizationManager" rel="noopener noreferrer"&gt;LocalizationManager (lrm)&lt;/a&gt; to be the tool I wished existed when I started working with .NET localization on Linux.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Philosophy
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform first&lt;/strong&gt;: Native binaries for Linux, Windows, macOS, including ARM64&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI-first, with a bonus TUI&lt;/strong&gt;: Perfect for automation and CI/CD, but also great for interactive use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete workflow&lt;/strong&gt;: Not just a translator, but the entire pipeline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern approach&lt;/strong&gt;: AI translation with caching, smart batch processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer-friendly&lt;/strong&gt;: JSON output, exit codes, extensive documentation&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Key Features at a Glance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Validation&lt;/strong&gt;: Detect missing translations, duplicates, XML errors&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Statistics&lt;/strong&gt;: Translation coverage with progress bars&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Code Scanning&lt;/strong&gt;: Find unused keys and missing references in your source code&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Auto-Translation&lt;/strong&gt;: 7 providers including Google, DeepL, OpenAI, Claude, and local Ollama&lt;/li&gt;
&lt;li&gt;📺 &lt;strong&gt;Interactive TUI&lt;/strong&gt;: Visual editing with keyboard shortcuts&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;CI/CD Ready&lt;/strong&gt;: JSON output, exit codes, GitHub Actions integration&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;Import/Export&lt;/strong&gt;: CSV format for working with translators&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;Full CLI&lt;/strong&gt;: 15+ commands for every localization task&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture &amp;amp; Technical Design
&lt;/h2&gt;

&lt;p&gt;Let me share some of the key architectural decisions that make LocalizationManager work well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technology Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: C# / .NET 9.0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TUI Framework&lt;/strong&gt;: Spectre.Console (amazing for rich terminal interfaces)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI Framework&lt;/strong&gt;: Spectre.Console.Cli (command routing and parsing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation APIs&lt;/strong&gt;: Native HTTP clients (no heavy SDK dependencies)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: SQLite for translation cache&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packaging&lt;/strong&gt;: Self-contained single-file executables&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Translation Provider Architecture
&lt;/h3&gt;

&lt;p&gt;One of the most important design decisions was creating an abstraction for translation providers. This allows users to choose the best provider for their needs while keeping the code maintainable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                   TranslationProviderFactory                │
│                                                             │
│  Create(providerName) → ITranslationProvider                │
└───────────────────┬─────────────────────────────────────────┘
                    │
        ┌───────────┴───────────┬──────────────┬──────────────┐
        ↓                       ↓              ↓              ↓
┌───────────────┐    ┌───────────────┐  ┌──────────┐  ┌──────────┐
│ GoogleProvider│    │  DeepLProvider│  │ Ollama   │  │ OpenAI   │
│               │    │               │  │ Provider │  │ Provider │
│ - Translate   │    │ - Translate   │  │          │  │          │
│ - Batch       │    │ - Batch       │  │ - Local  │  │ - GPT-4  │
│ - Cache       │    │ - Cache       │  │ - Free   │  │ - Claude │
└───────────────┘    └───────────────┘  └──────────┘  └──────────┘
        │                       │              │              │
        └───────────────────────┴──────────────┴──────────────┘
                                │
                   ┌────────────▼─────────────┐
                   │    TranslationCache      │
                   │    (SQLite, 30-day)      │
                   │                          │
                   │  - Reduces API costs     │
                   │  - Provider-specific     │
                   │  - SHA-256 cache keys    │
                   └──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new providers without changing existing code&lt;/li&gt;
&lt;li&gt;Each provider handles its own rate limiting&lt;/li&gt;
&lt;li&gt;Centralized caching reduces costs by 80%+&lt;/li&gt;
&lt;li&gt;Providers can be traditional (Google) or AI-powered (Claude)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using the Translation System
&lt;/h3&gt;

&lt;p&gt;Switch between providers and control translation behavior:&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="c"&gt;# Translate all missing keys&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; google

&lt;span class="c"&gt;# Use specific provider&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--provider&lt;/span&gt; ollama  &lt;span class="c"&gt;# Free local translation&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--provider&lt;/span&gt; deepl   &lt;span class="c"&gt;# High quality&lt;/span&gt;

&lt;span class="c"&gt;# Target specific languages only&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--target-languages&lt;/span&gt; fr,de,es &lt;span class="nt"&gt;--provider&lt;/span&gt; google

&lt;span class="c"&gt;# Re-translate specific keys&lt;/span&gt;
lrm translate &lt;span class="s2"&gt;"Error*"&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; deepl &lt;span class="nt"&gt;--overwrite&lt;/span&gt;

&lt;span class="c"&gt;# Preview before saving&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# Batch control for rate limiting&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--batch-size&lt;/span&gt; 5 &lt;span class="nt"&gt;--provider&lt;/span&gt; google
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--provider&lt;/code&gt; - google, deepl, openai, claude, ollama, libretranslate, azureopenai, azuretranslator&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--only-missing&lt;/code&gt; - Skip keys with existing translations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--target-languages&lt;/code&gt; - Comma-separated language codes (fr,de,es)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--overwrite&lt;/code&gt; - Allow re-translating existing values (use with key pattern)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--dry-run&lt;/code&gt; - Preview without saving&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--batch-size&lt;/code&gt; - Control concurrent API calls&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-cache&lt;/code&gt; - Bypass the 30-day SQLite cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Caching:&lt;/strong&gt; All translations cached 30 days in &lt;code&gt;~/.local/share/LocalizationManager/translations.db&lt;/code&gt;, reducing API costs by 80%+.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AI Translation Revolution
&lt;/h3&gt;

&lt;p&gt;Here's something exciting: we added support for modern AI translation providers alongside traditional machine translation services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider Comparison:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;th&gt;Privacy&lt;/th&gt;
&lt;th&gt;API Key Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google Translate&lt;/td&gt;
&lt;td&gt;Traditional MT&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepL&lt;/td&gt;
&lt;td&gt;Traditional MT&lt;/td&gt;
&lt;td&gt;Highest (EU)&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LibreTranslate&lt;/td&gt;
&lt;td&gt;Traditional MT&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Self-host&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI GPT&lt;/td&gt;
&lt;td&gt;AI (LLM)&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude&lt;/td&gt;
&lt;td&gt;AI (LLM)&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ollama&lt;/td&gt;
&lt;td&gt;AI (Local LLM)&lt;/td&gt;
&lt;td&gt;Very Good&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Local&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure OpenAI&lt;/td&gt;
&lt;td&gt;AI (LLM)&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why AI Translation Matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context Awareness&lt;/strong&gt;: LLMs understand context better than traditional MT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical Terminology&lt;/strong&gt;: Better at preserving technical terms and code snippets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tone Preservation&lt;/strong&gt;: Maintains the original tone (formal, casual, technical)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free Option&lt;/strong&gt;: Ollama runs locally for free with surprisingly good results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: With Ollama, your data never leaves your machine&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Code Scanning: Keeping Code and Resources in Sync
&lt;/h3&gt;

&lt;p&gt;One unique feature is the code scanner that analyzes your source code to find localization key usage:&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="c"&gt;# Scan C#, Razor, XAML files for key usage&lt;/span&gt;
lrm scan &lt;span class="nt"&gt;--source-path&lt;/span&gt; ./src &lt;span class="nt"&gt;--format&lt;/span&gt; json

&lt;span class="c"&gt;# Output shows:&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Scanned 245 files&lt;/span&gt;
&lt;span class="c"&gt;# Found 156 key references&lt;/span&gt;
&lt;span class="c"&gt;# ⚠ 3 keys used in code but missing from .resx&lt;/span&gt;
&lt;span class="c"&gt;# ⚠ 12 keys in .resx but never used&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner detects patterns like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Resources.WelcomeMessage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetString("SaveButton")&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Translate("ErrorText")&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{x:Static res:Strings.PageTitle}&lt;/code&gt; (XAML)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Resources.Message&lt;/code&gt; (Razor)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scan command &lt;strong&gt;reports&lt;/strong&gt; the issues - you then use other commands to fix them:&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="c"&gt;# 1. Scan and save results&lt;/span&gt;
lrm scan &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; scan-results.json

&lt;span class="c"&gt;# 2. Add missing keys interactively (prompts for proper values)&lt;/span&gt;
jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.missingKeys[]?.key // empty'&lt;/span&gt; scan-results.json | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;key&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;lrm add &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;  &lt;span class="c"&gt;# Interactive mode - prompts for values in each language&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# OR: Add as placeholders for manual review later&lt;/span&gt;
jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.missingKeys[]?.key // empty'&lt;/span&gt; scan-results.json | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;key&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;lrm add &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--lang&lt;/span&gt; default:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"TODO: Add proper text (from code scan)"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="c"&gt;# ⚠️ Important: Edit .resx files to add proper English text before running translate!&lt;/span&gt;

&lt;span class="c"&gt;# 3. After adding proper values, then translate&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps prevent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dead keys in resource files (unused keys)&lt;/li&gt;
&lt;li&gt;Missing keys that will fail at runtime&lt;/li&gt;
&lt;li&gt;Localization drift over time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Complete Workflow: From Chaos to Automation
&lt;/h2&gt;

&lt;p&gt;Here's the workflow that LocalizationManager enables, from manual pain to full automation:&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: The Manual Nightmare
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer adds English string
    ↓
Export .resx to Excel/CSV somehow
    ↓
Email to translator (wait 3 days)
    ↓
Receive translated Excel/CSV
    ↓
Manually copy-paste into .resx files
    ↓
Build fails (XML corruption)
    ↓
Fix XML manually
    ↓
Build succeeds, but French is missing 3 keys
    ↓
Repeat entire process for those 3 keys
    ↓
Week wasted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After: The Automated Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Step 1: VALIDATE                                            │
│  ├─ Check XML syntax                                         │
│  ├─ Find duplicate keys                                      │
│  └─ Detect empty values                                      │
│                                                              │
└────────────────┬─────────────────────────────────────────────┘
                 ↓
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Step 2: SCAN CODE (optional)                                │
│  ├─ Find keys used in source files                           │
│  ├─ Detect missing keys (in code but not in .resx)           │
│  ├─ Detect unused keys (in .resx but not in code)            │
│  └─ Generate JSON report for processing                      │
│                                                              │
└────────────────┬─────────────────────────────────────────────┘
                 ↓
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Step 2a: ADD MISSING KEYS (if scan found any)               │
│  ├─ Parse scan results JSON                                  │
│  ├─ Use 'lrm add' command for each missing key               │
│  └─ Keys now added to default .resx file                     │
│                                                              │
└────────────────┬─────────────────────────────────────────────┘
                 ↓
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Step 3: CHECK MISSING                                       │
│  ├─ Find untranslated keys                                   │
│  ├─ Generate report per language                             │
│  └─ Count: 47 keys need translation                          │
│                                                              │
└────────────────┬─────────────────────────────────────────────┘
                 ↓
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Step 4: AUTO-TRANSLATE                                      │
│  ├─ Batch translate missing keys                             │
│  ├─ Use cache for repeated strings                           │
│  ├─ Apply rate limiting                                      │
│  └─ Result: 47/47 translations completed                     │
│                                                              │
└────────────────┬─────────────────────────────────────────────┘
                 ↓
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Step 5: RE-VALIDATE                                         │
│  ├─ Verify all XML is still valid                            │
│  ├─ Confirm no keys were corrupted                           │
│  └─ Status: All checks passed                                │
│                                                              │
└────────────────┬─────────────────────────────────────────────┘
                 ↓
┌──────────────────────────────────────────────────────────────┐
│                                                              │
│  Step 6: COMMIT &amp;amp; PR                                         │
│  ├─ Git commit with detailed message                         │
│  ├─ Create pull request                                      │
│  └─ Time elapsed: 2 minutes                                  │
│                                                              │
└──────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running the Workflow: Five Ways
&lt;/h3&gt;

&lt;p&gt;Choose the approach that matches your team's needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manual CLI&lt;/strong&gt; - Step-by-step learning and exploration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bash Script A&lt;/strong&gt; - Auto-translate existing keys with proper values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bash Script B&lt;/strong&gt; - Scan code and add placeholders for review&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama (Free!)&lt;/strong&gt; - Private local translation, zero cost 🆓&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate Cleanup&lt;/strong&gt; - Merge or delete duplicate keys&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;1. Manual CLI (step by step):&lt;/strong&gt;&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="c"&gt;# Step 1: Validate&lt;/span&gt;
lrm validate &lt;span class="nt"&gt;--path&lt;/span&gt; ./Resources

&lt;span class="c"&gt;# Step 2: Check what's missing&lt;/span&gt;
lrm validate &lt;span class="nt"&gt;--missing-only&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; missing.json

&lt;span class="c"&gt;# Step 3: Translate only what's missing&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; openai

&lt;span class="c"&gt;# Step 4: Verify&lt;/span&gt;
lrm validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Automated Script (Option A - Translate Existing Keys):&lt;/strong&gt;&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Workflow for auto-translating keys that already have proper English text&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Validate&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# Translate missing translations&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; google

&lt;span class="c"&gt;# Final validation&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# Commit and create PR&lt;/span&gt;
git add Resources/
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"🌐 Auto-translate missing localization keys"&lt;/span&gt;
gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Auto-translate localizations"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2b. Automated Script (Option B - Scan and Add Keys for Review):&lt;/strong&gt;&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Workflow for scanning code and adding placeholder keys that need manual review&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Validate&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# Scan code and add missing keys&lt;/span&gt;
lrm scan &lt;span class="nt"&gt;--source-path&lt;/span&gt; ./src &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; scan-results.json
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; scan-results.json &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.missingKeys[]?.key // empty'&lt;/span&gt; scan-results.json | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;key&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; lrm add &lt;span class="nt"&gt;--key&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--value&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"TODO: Add proper English text (from code scan)"&lt;/span&gt;
  &lt;span class="k"&gt;done
fi&lt;/span&gt;

&lt;span class="c"&gt;# Final validation&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# Commit and create PR for manual review&lt;/span&gt;
git add Resources/
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"📋 Add placeholder keys from code scan - manual review required"&lt;/span&gt;
gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Review and add text for new localization keys"&lt;/span&gt; &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"⚠️ These keys were added with placeholder values. Please edit the .resx files to add proper English text before running auto-translation."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Important&lt;/strong&gt;: Option B adds keys with placeholder values (key name as the value). Do NOT run &lt;code&gt;lrm translate&lt;/code&gt; until after manually editing the .resx files to add proper English text. Otherwise, it will translate the placeholder "WelcomeMessage" literally instead of your intended English message.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;3a. GitHub Actions - Translate Existing Keys:&lt;/strong&gt;&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto-Translate Existing Keys&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Resources/**/*.resx'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Only .resx changes&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;translate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&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;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup LRM&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;wget https://github.com/nickprotop/LocalizationManager/releases/latest/download/lrm-linux-x64.tar.gz&lt;/span&gt;
          &lt;span class="s"&gt;tar -xzf lrm-linux-x64.tar.gz&lt;/span&gt;
          &lt;span class="s"&gt;chmod +x linux-x64/lrm&lt;/span&gt;
          &lt;span class="s"&gt;echo "$PWD/linux-x64" &amp;gt;&amp;gt; $GITHUB_PATH&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate → Translate → Validate&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;LRM_OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.OPENAI_API_KEY }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Validate&lt;/span&gt;
          &lt;span class="s"&gt;lrm validate&lt;/span&gt;

          &lt;span class="s"&gt;# Translate all missing translations&lt;/span&gt;
          &lt;span class="s"&gt;lrm translate --only-missing --provider openai&lt;/span&gt;

          &lt;span class="s"&gt;# Final validation&lt;/span&gt;
          &lt;span class="s"&gt;lrm validate&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Commit Changes&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name "github-actions[bot]"&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email "github-actions[bot]@users.noreply.github.com"&lt;/span&gt;
          &lt;span class="s"&gt;git add Resources/&lt;/span&gt;
          &lt;span class="s"&gt;git commit -m "🌐 Auto-translate missing localization keys" || echo "No changes"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3b. GitHub Actions - Scan and Add Placeholders:&lt;/strong&gt;&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan Code and Add Placeholders&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;src/**/*.cs'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Only code changes&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&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;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup LRM&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;wget https://github.com/nickprotop/LocalizationManager/releases/latest/download/lrm-linux-x64.tar.gz&lt;/span&gt;
          &lt;span class="s"&gt;tar -xzf lrm-linux-x64.tar.gz&lt;/span&gt;
          &lt;span class="s"&gt;chmod +x linux-x64/lrm&lt;/span&gt;
          &lt;span class="s"&gt;echo "$PWD/linux-x64" &amp;gt;&amp;gt; $GITHUB_PATH&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Scan → Add → Create PR&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Validate&lt;/span&gt;
          &lt;span class="s"&gt;lrm validate&lt;/span&gt;

          &lt;span class="s"&gt;# Scan code for missing keys&lt;/span&gt;
          &lt;span class="s"&gt;lrm scan --source-path ./src --format json &amp;gt; scan-results.json&lt;/span&gt;

          &lt;span class="s"&gt;# Add missing keys found in code&lt;/span&gt;
          &lt;span class="s"&gt;jq -r '.missingKeys[]?.key // empty' scan-results.json | while read key; do&lt;/span&gt;
            &lt;span class="s"&gt;if [ -n "$key" ]; then&lt;/span&gt;
              &lt;span class="s"&gt;lrm add --key "$key" --value "$key" --comment "TODO: Add proper English text (from code scan)"&lt;/span&gt;
            &lt;span class="s"&gt;fi&lt;/span&gt;
          &lt;span class="s"&gt;done&lt;/span&gt;

          &lt;span class="s"&gt;# Validate additions&lt;/span&gt;
          &lt;span class="s"&gt;lrm validate&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create PR for Review&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name "github-actions[bot]"&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email "github-actions[bot]@users.noreply.github.com"&lt;/span&gt;
          &lt;span class="s"&gt;git checkout -b scan/add-keys-$(date +%s)&lt;/span&gt;
          &lt;span class="s"&gt;git add Resources/&lt;/span&gt;
          &lt;span class="s"&gt;git commit -m "📋 Add placeholder keys from code scan" || exit 0&lt;/span&gt;
          &lt;span class="s"&gt;git push --set-upstream origin HEAD&lt;/span&gt;

          &lt;span class="s"&gt;gh pr create \&lt;/span&gt;
            &lt;span class="s"&gt;--title "Review: Add proper text for new localization keys" \&lt;/span&gt;
            &lt;span class="s"&gt;--body "⚠️ **Manual Review Required**&lt;/span&gt;

          &lt;span class="s"&gt;These keys were added with placeholder values (key name as value).&lt;/span&gt;

          &lt;span class="s"&gt;**Next steps:**&lt;/span&gt;
          &lt;span class="s"&gt;1. Edit the .resx files to replace placeholder values with proper English text&lt;/span&gt;
          &lt;span class="s"&gt;2. After merging, the auto-translate workflow will handle other languages"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Important&lt;/strong&gt;: These are two separate workflows. Workflow 3a handles translation of keys that already have proper English text. Workflow 3b adds new keys found in code but does NOT translate them - it creates a PR for manual review first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;4. Privacy-First Local Translation (Zero Cost with Ollama):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best translation is free! Use Ollama to translate locally without cloud APIs:&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# One-time setup: Install Ollama and pull a model&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://ollama.com/install.sh | sh
ollama pull llama3.2

&lt;span class="c"&gt;# Validate resources&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# Translate locally - no cloud, no cost, no data leaving your machine&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--provider&lt;/span&gt; ollama

&lt;span class="c"&gt;# Validate&lt;/span&gt;
lrm validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Ollama is amazing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💰 &lt;strong&gt;$0 cost&lt;/strong&gt; - Unlimited translations forever&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;Complete privacy&lt;/strong&gt; - Data never leaves your machine&lt;/li&gt;
&lt;li&gt;✈️ &lt;strong&gt;Works offline&lt;/strong&gt; - No internet required after model download&lt;/li&gt;
&lt;li&gt;🔑 &lt;strong&gt;No API keys&lt;/strong&gt; - One less secret to manage&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Good quality&lt;/strong&gt; - llama3.2 handles technical UI text well&lt;/li&gt;
&lt;li&gt;⚖️ &lt;strong&gt;GDPR/HIPAA friendly&lt;/strong&gt; - Perfect for sensitive data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Perfect for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Medical records UI (HIPAA compliance)&lt;/li&gt;
&lt;li&gt;Banking applications (PCI/SOX compliance)&lt;/li&gt;
&lt;li&gt;Internal tools (security policies)&lt;/li&gt;
&lt;li&gt;Bootstrapped projects (zero budget)&lt;/li&gt;
&lt;li&gt;High-volume needs (no quota limits)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Clean Up Duplicate Keys (Merge or Delete):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Resource files can accumulate duplicate keys from merges, imports, or accidents. LRM helps clean them up:&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="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Step 1: Detect duplicates&lt;/span&gt;
lrm validate &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; validation.json

&lt;span class="nv"&gt;duplicate_count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="s1"&gt;'[.issues[]? | select(.type=="Duplicate")] | length'&lt;/span&gt; validation.json&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$duplicate_count&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"⚠️  Found &lt;/span&gt;&lt;span class="nv"&gt;$duplicate_count&lt;/span&gt;&lt;span class="s2"&gt; duplicate keys"&lt;/span&gt;

  &lt;span class="c"&gt;# Step 2: View duplicates&lt;/span&gt;
  lrm validate  &lt;span class="c"&gt;# Shows table with duplicate warnings&lt;/span&gt;

  &lt;span class="c"&gt;# Step 3: Choose your cleanup strategy&lt;/span&gt;

  &lt;span class="c"&gt;# Option A: Merge interactively (choose which value to keep per language)&lt;/span&gt;
  lrm merge-duplicates ErrorMessage
  &lt;span class="c"&gt;# Shows:&lt;/span&gt;
  &lt;span class="c"&gt;# ErrorMessage [1]: "An error occurred"&lt;/span&gt;
  &lt;span class="c"&gt;# ErrorMessage [2]: "Error occurred"&lt;/span&gt;
  &lt;span class="c"&gt;# Which to keep for default language? [1/2]: 1&lt;/span&gt;
  &lt;span class="c"&gt;# (repeats for each language)&lt;/span&gt;

  &lt;span class="c"&gt;# Option B: Auto-merge keeping first occurrence&lt;/span&gt;
  lrm merge-duplicates ErrorMessage &lt;span class="nt"&gt;--auto-first&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;

  &lt;span class="c"&gt;# Option C: Merge ALL duplicates at once (keeps first of each)&lt;/span&gt;
  lrm merge-duplicates &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--auto-first&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;

  &lt;span class="c"&gt;# Option D: Delete all occurrences entirely&lt;/span&gt;
  lrm delete ErrorMessage &lt;span class="nt"&gt;--all-duplicates&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;

  &lt;span class="c"&gt;# Step 4: Validate cleanup&lt;/span&gt;
  lrm validate
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✅ Duplicates cleaned up"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common scenarios that create duplicates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Git merge conflicts in .resx files&lt;/li&gt;
&lt;li&gt;❌ Importing CSV files with existing keys&lt;/li&gt;
&lt;li&gt;❌ Manual XML editing mistakes&lt;/li&gt;
&lt;li&gt;❌ Merging resource files from different branches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Interactive merge example:&lt;/strong&gt;&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;$ &lt;/span&gt;lrm merge-duplicates WelcomeMessage

Found 2 occurrences of &lt;span class="s1"&gt;'WelcomeMessage'&lt;/span&gt;:

Default language:
  &lt;span class="o"&gt;[&lt;/span&gt;1] &lt;span class="s2"&gt;"Welcome to our app!"&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt;2] &lt;span class="s2"&gt;"Welcome to our application!"&lt;/span&gt;

Which to keep? &lt;span class="o"&gt;[&lt;/span&gt;1/2]: 1

French &lt;span class="o"&gt;(&lt;/span&gt;fr&lt;span class="o"&gt;)&lt;/span&gt;:
  &lt;span class="o"&gt;[&lt;/span&gt;1] &lt;span class="s2"&gt;"Bienvenue dans notre app !"&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt;2] &lt;span class="s2"&gt;"Bienvenue dans notre application !"&lt;/span&gt;

Which to keep? &lt;span class="o"&gt;[&lt;/span&gt;1/2]: 2

✓ Merged WelcomeMessage: kept occurrence 1 &lt;span class="k"&gt;for &lt;/span&gt;default, occurrence 2 &lt;span class="k"&gt;for &lt;/span&gt;fr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Always validate after cleanup to ensure no issues remain!&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Usage Examples
&lt;/h2&gt;

&lt;p&gt;Let's see LocalizationManager in action with real examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation (One Command)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux/macOS&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://raw.githubusercontent.com/nickprotop/LocalizationManager/main/install-lrm.sh | bash

&lt;span class="c"&gt;# Windows (PowerShell)&lt;/span&gt;
iwr &lt;span class="nt"&gt;-useb&lt;/span&gt; https://raw.githubusercontent.com/nickprotop/LocalizationManager/main/install-lrm.ps1 | iex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No dependencies, no .NET SDK required. Self-contained binary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Navigate to your Resources folder&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;MyApp/Resources

&lt;span class="c"&gt;# Check current status&lt;/span&gt;
lrm stats

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# ┌──────────────────┬───────┬──────────┬───────────┐&lt;/span&gt;
&lt;span class="c"&gt;# │ Language         │ Keys  │ Coverage │ File Size │&lt;/span&gt;
&lt;span class="c"&gt;# ├──────────────────┼───────┼──────────┼───────────┤&lt;/span&gt;
&lt;span class="c"&gt;# │ English (en)     │ 252   │ 100.0%   │ 45.2 KB   │&lt;/span&gt;
&lt;span class="c"&gt;# │ French (fr)      │ 245   │  97.2%   │ 43.1 KB   │&lt;/span&gt;
&lt;span class="c"&gt;# │ German (de)      │ 238   │  94.4%   │ 41.8 KB   │&lt;/span&gt;
&lt;span class="c"&gt;# │ Greek (el)       │ 252   │ 100.0%   │ 46.3 KB   │&lt;/span&gt;
&lt;span class="c"&gt;# └──────────────────┴───────┴──────────┴───────────┘&lt;/span&gt;

&lt;span class="c"&gt;# Translate missing French and German keys&lt;/span&gt;
lrm translate &lt;span class="nt"&gt;--only-missing&lt;/span&gt; &lt;span class="nt"&gt;--target-languages&lt;/span&gt; fr,de &lt;span class="nt"&gt;--provider&lt;/span&gt; openai

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# 🌐 Translating 14 keys to 2 languages...&lt;/span&gt;
&lt;span class="c"&gt;# ✓ fr: 7/7 keys translated&lt;/span&gt;
&lt;span class="c"&gt;# ✓ de: 7/7 keys translated&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Translations saved&lt;/span&gt;
&lt;span class="c"&gt;# ⚡ Used cache for 3 translations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Interactive TUI Mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Launch the Terminal UI&lt;/span&gt;
lrm edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╔═══════════════════════════════════════════════════════════╗
║  LocalizationManager - Interactive Editor                 ║
╠═══════════════════════════════════════════════════════════╣
║                                                           ║
║  Key: WelcomeMessage                                      ║
║  ┌─────────────────────────────────────────────────────┐  ║
║  │ Default (en): Welcome to our application!           │  ║
║  │ French (fr):  Bienvenue dans notre application!     │  ║
║  │ German (de):  Willkommen in unserer Anwendung!      │  ║
║  │ Greek (el):   Καλώς ήρθατε στην εφαρμογή μας!       │  ║
║  └─────────────────────────────────────────────────────┘  ║
║                                                           ║
║  [Ctrl+T] Translate  [Ctrl+S] Save  [Ctrl+Q] Quit         ║
╚═══════════════════════════════════════════════════════════╝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AI Translation Comparison
&lt;/h3&gt;

&lt;p&gt;Let's translate a technical string with different providers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source (English):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Click the Save button to persist your changes to the database."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Traditional MT (Google Translate) → French:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Cliquez sur le bouton Enregistrer pour conserver vos modifications dans la base de données."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Accurate but mechanical&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI (GPT-4o-mini) → French:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Cliquez sur le bouton Enregistrer pour sauvegarder vos modifications dans la base de données."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;More natural, preserves "Save" as "Enregistrer" (common UI term)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI (Claude) → French:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Cliquez sur le bouton Enregistrer pour sauvegarder définitivement vos modifications dans la base de données."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Adds "définitivement" (permanently) - excellent context awareness&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traditional APIs: Consistent quality, proven reliability&lt;/li&gt;
&lt;li&gt;AI (Cloud): Excellent context awareness, requires API key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama (Local): Free to use, runs on your machine, no API key needed&lt;/strong&gt; ⭐&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuration for AI Providers
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;lrm.json&lt;/code&gt; in your Resources folder:&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;"defaultLanguageCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"translation"&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;"defaultProvider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aiProviders"&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;"openai"&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;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-4o-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"customSystemPrompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Translate UI text naturally, preserve technical terms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rateLimitPerMinute"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;"ollama"&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;"apiUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:11434"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"llama3.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rateLimitPerMinute"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&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;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;Set your API keys:&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="c"&gt;# Via environment variables (recommended)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LRM_OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-..."&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LRM_CLAUDE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-ant-..."&lt;/span&gt;

&lt;span class="c"&gt;# Or via secure credential store&lt;/span&gt;
lrm config set-api-key &lt;span class="nt"&gt;--provider&lt;/span&gt; openai &lt;span class="nt"&gt;--key&lt;/span&gt; &lt;span class="s2"&gt;"sk-..."&lt;/span&gt;

&lt;span class="c"&gt;# Ollama needs no API key!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture Highlights: Why It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Self-Contained Binaries
&lt;/h3&gt;

&lt;p&gt;Using .NET's publishing options, LocalizationManager compiles to a single, self-contained executable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No .NET runtime installation required&lt;/li&gt;
&lt;li&gt;No dependencies to manage&lt;/li&gt;
&lt;li&gt;~50MB binary includes everything&lt;/li&gt;
&lt;li&gt;Fast startup (~100ms)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Cross-Platform Terminal UI
&lt;/h3&gt;

&lt;p&gt;Spectre.Console makes the TUI work beautifully on all platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Windows Command Prompt  ✓
Windows Terminal       ✓
PowerShell            ✓
Linux Terminal        ✓
macOS Terminal        ✓
SSH Sessions          ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. JSON Output for Automation
&lt;/h3&gt;

&lt;p&gt;Every command supports &lt;code&gt;--format json&lt;/code&gt; for easy parsing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lrm validate &lt;span class="nt"&gt;--format&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.isValid'&lt;/span&gt;
&lt;span class="c"&gt;# true&lt;/span&gt;

lrm stats &lt;span class="nt"&gt;--format&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.statistics[].coverage'&lt;/span&gt;
&lt;span class="c"&gt;# 100.0&lt;/span&gt;
&lt;span class="c"&gt;# 97.2&lt;/span&gt;
&lt;span class="c"&gt;# 94.4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Custom dashboards&lt;/li&gt;
&lt;li&gt;Monitoring and alerts&lt;/li&gt;
&lt;li&gt;Integration with other tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Intelligent Rate Limiting
&lt;/h3&gt;

&lt;p&gt;Each translation provider has configurable rate limits to avoid hitting API quotas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Automatic rate limiting&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RateLimiter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;_requestsPerMinute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;WaitAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Automatically throttles requests&lt;/span&gt;
        &lt;span class="c1"&gt;// Tracks request times in sliding window&lt;/span&gt;
        &lt;span class="c1"&gt;// Prevents API rate limit errors&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;h3&gt;
  
  
  5. Batch Processing with Fallback
&lt;/h3&gt;

&lt;p&gt;Translations are processed in batches when providers support it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Try batch translation first&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;batchResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;TranslateBatchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Fall back to individual translation if batch fails&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;batchResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasErrors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;failedRequests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;TranslateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Try It Now
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://raw.githubusercontent.com/nickprotop/LocalizationManager/main/install-lrm.sh | bash

&lt;span class="c"&gt;# Go to your Resources folder&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;YourProject/Resources

&lt;span class="c"&gt;# Check status&lt;/span&gt;
lrm stats

&lt;span class="c"&gt;# Validate everything&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# Start interactive mode&lt;/span&gt;
lrm edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the docs&lt;/strong&gt;: &lt;a href="https://github.com/nickprotop/LocalizationManager/docs" rel="noopener noreferrer"&gt;Full documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Star on GitHub&lt;/strong&gt;: &lt;a href="https://github.com/nickprotop/LocalizationManager" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try the workflow&lt;/strong&gt;: Follow the Quick Start guide&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up CI/CD&lt;/strong&gt;: Check out the &lt;a href="https://github.com/nickprotop/LocalizationManager/docs/CICD.md" rel="noopener noreferrer"&gt;CI/CD examples&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join the community&lt;/strong&gt;: Report issues, request features, contribute!&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Contributing
&lt;/h3&gt;

&lt;p&gt;LocalizationManager is MIT licensed and welcomes contributions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐛 &lt;strong&gt;Bug reports&lt;/strong&gt;: Found an issue? Let us know!&lt;/li&gt;
&lt;li&gt;✨ &lt;strong&gt;Feature requests&lt;/strong&gt;: Have an idea? Open a discussion!&lt;/li&gt;
&lt;li&gt;🔧 &lt;strong&gt;Pull requests&lt;/strong&gt;: Code contributions welcome!&lt;/li&gt;
&lt;li&gt;📖 &lt;strong&gt;Documentation&lt;/strong&gt;: Help improve the docs!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: Making .NET Localization Painless
&lt;/h2&gt;

&lt;p&gt;LocalizationManager started as a personal itch I needed to scratch: managing .resx files on Linux without Windows tools. It evolved into a comprehensive solution that:&lt;/p&gt;

&lt;p&gt;✅ Works on &lt;strong&gt;any platform&lt;/strong&gt; (Linux, Windows, macOS, ARM64)&lt;br&gt;
✅ Automates the &lt;strong&gt;complete workflow&lt;/strong&gt; (validate → translate → commit → PR)&lt;br&gt;
✅ Integrates &lt;strong&gt;modern AI translation&lt;/strong&gt; (OpenAI, Claude, Ollama)&lt;br&gt;
✅ Smart caching reduces redundant API calls&lt;br&gt;
✅ Provides both &lt;strong&gt;CLI and TUI&lt;/strong&gt; for automation and interactive use&lt;br&gt;
✅ Scans your &lt;strong&gt;source code&lt;/strong&gt; to keep resources in sync&lt;br&gt;
✅ Integrates seamlessly with &lt;strong&gt;CI/CD pipelines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whether you're a solo developer managing translations for a side project or a team handling enterprise-scale localization, LocalizationManager provides the tools you need to work efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The best part?&lt;/strong&gt; It's completely free and open source. No subscriptions, no locked features, no vendor lock-in. Just a tool that works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links &amp;amp; Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/nickprotop/LocalizationManager" rel="noopener noreferrer"&gt;https://github.com/nickprotop/LocalizationManager&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://github.com/nickprotop/LocalizationManager/tree/main/docs" rel="noopener noreferrer"&gt;https://github.com/nickprotop/LocalizationManager/tree/main/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installation Guide&lt;/strong&gt;: &lt;a href="https://github.com/nickprotop/LocalizationManager/docs/INSTALLATION.md" rel="noopener noreferrer"&gt;https://github.com/nickprotop/LocalizationManager/docs/INSTALLATION.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD Examples&lt;/strong&gt;: &lt;a href="https://github.com/nickprotop/LocalizationManager/docs/CICD.md" rel="noopener noreferrer"&gt;https://github.com/nickprotop/LocalizationManager/docs/CICD.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation Guide&lt;/strong&gt;: &lt;a href="https://github.com/nickprotop/LocalizationManager/docs/TRANSLATION.md" rel="noopener noreferrer"&gt;https://github.com/nickprotop/LocalizationManager/docs/TRANSLATION.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue Tracker&lt;/strong&gt;: &lt;a href="https://github.com/nickprotop/LocalizationManager/issues" rel="noopener noreferrer"&gt;https://github.com/nickprotop/LocalizationManager/issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with ❤️ by &lt;a href="https://github.com/nickprotop" rel="noopener noreferrer"&gt;Nikolaos Protopapas&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Licensed under MIT - Free to use, modify, and distribute&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Did you find this article helpful? Please share it with your fellow .NET developers!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt;: #dotnet #devops #opensource #localization #ai #linux #translation #csharp #cli #automation&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>localization</category>
      <category>devops</category>
      <category>translation</category>
    </item>
    <item>
      <title>Terminal UI for .NET .resx Files Because Nothing Else Worked</title>
      <dc:creator>Nikolaos Protopapas</dc:creator>
      <pubDate>Sun, 09 Nov 2025 16:59:47 +0000</pubDate>
      <link>https://dev.to/nikolaos_protopapas_d3bd6/terminal-ui-for-net-resx-files-because-nothing-else-worked-310</link>
      <guid>https://dev.to/nikolaos_protopapas_d3bd6/terminal-ui-for-net-resx-files-because-nothing-else-worked-310</guid>
      <description>&lt;h1&gt;
  
  
  There Was No Way to Manage .NET Localization from the Console, So I Built One
&lt;/h1&gt;

&lt;p&gt;I work primarily in a Linux terminal. SSH into my dev box, tmux sessions, vim, the works. When I started building a .NET WASM application with a .NET API server, I knew I'd need localization support from the start—English and Greek.&lt;/p&gt;

&lt;p&gt;That's when I discovered a glaring gap in the .NET ecosystem: &lt;strong&gt;there's no proper command-line tool for managing &lt;code&gt;.resx&lt;/code&gt; localization files.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Localization Tooling is GUI-Only
&lt;/h2&gt;

&lt;p&gt;Here's what's available for managing &lt;code&gt;.resx&lt;/code&gt; files in .NET:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visual Studio&lt;/strong&gt; - Windows only, GUI only, requires gigabytes of disk space&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rider&lt;/strong&gt; - Another GUI-only IDE&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt; - No decent extension for &lt;code&gt;.resx&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ResXResourceManager&lt;/strong&gt; - Excellent tool, but Windows-only GUI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual XML editing&lt;/strong&gt; - Good luck with XML namespaces and encoding&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For someone who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Develops on a Linux box&lt;/li&gt;
&lt;li&gt;Lives in the terminal&lt;/li&gt;
&lt;li&gt;Works exclusively over SSH&lt;/li&gt;
&lt;li&gt;Needs to add localization to a WASM app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...none of these options work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment of Realization
&lt;/h2&gt;

&lt;p&gt;I was adding Greek language support to my application. Simple task, right? Add &lt;code&gt;Resources.el.resx&lt;/code&gt;, copy the keys from &lt;code&gt;Resources.resx&lt;/code&gt;, translate the values.&lt;/p&gt;

&lt;p&gt;But how do I actually do this from my terminal?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Manual XML editing&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vim Resources.el.resx
&lt;span class="c"&gt;# Now manually replicate the XML structure&lt;/span&gt;
&lt;span class="c"&gt;# Copy each &amp;lt;data&amp;gt; element&lt;/span&gt;
&lt;span class="c"&gt;# Hope you don't mess up the encoding&lt;/span&gt;
&lt;span class="c"&gt;# Pray you don't introduce XML syntax errors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No. Just no.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Write a script&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I wrote a Python script. Then I needed to validate both files. Wrote another script. Then I needed to add a new key to both languages. Modified the script again.&lt;/p&gt;

&lt;p&gt;After the third time writing variations of "quick scripts" for basic operations, I realized: &lt;strong&gt;this is a fundamental workflow gap.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Needed
&lt;/h2&gt;

&lt;p&gt;I wanted a Visual Studio-like experience, but from the command line. Something that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work entirely in the terminal&lt;/li&gt;
&lt;li&gt;Work over SSH (no GUI)&lt;/li&gt;
&lt;li&gt;Add, update, delete keys across all languages atomically&lt;/li&gt;
&lt;li&gt;Validate completeness and correctness&lt;/li&gt;
&lt;li&gt;Let me edit translations interactively without leaving the terminal&lt;/li&gt;
&lt;li&gt;Be future-proof for when I eventually work with translators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The vision was clear: &lt;strong&gt;modern CLI tooling with an interactive TUI, built specifically for &lt;code&gt;.resx&lt;/code&gt; management on Linux.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Gap Exists
&lt;/h2&gt;

&lt;p&gt;.NET is genuinely cross-platform now. You can build WASM apps, API servers, microservices—all on Linux with no issues. But the tooling ecosystem is still catching up.&lt;/p&gt;

&lt;p&gt;Most .NET localization tools assume:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're on Windows&lt;/li&gt;
&lt;li&gt;You have a GUI available&lt;/li&gt;
&lt;li&gt;You want to use an IDE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But modern development—especially on Linux—is increasingly terminal-first. SSH into a VPS, work in tmux, use terminal tools. It's faster, cleaner, and works everywhere.&lt;/p&gt;

&lt;p&gt;The .NET ecosystem just didn't have tooling for this workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I Built LRM
&lt;/h2&gt;

&lt;p&gt;After realizing I'd be writing throwaway scripts forever, I decided to build a proper tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Native Linux support (no Mono, no Wine, just .NET)&lt;/li&gt;
&lt;li&gt;✅ Command-line first (scriptable, automatable)&lt;/li&gt;
&lt;li&gt;✅ Interactive TUI (Visual Studio-like experience in terminal)&lt;/li&gt;
&lt;li&gt;✅ Works over SSH (no GUI dependencies)&lt;/li&gt;
&lt;li&gt;✅ Full CRUD operations (add, update, delete keys)&lt;/li&gt;
&lt;li&gt;✅ Validation (detect missing keys, duplicates, empty values)&lt;/li&gt;
&lt;li&gt;✅ Future-proof (CSV import/export for translators)&lt;/li&gt;
&lt;li&gt;✅ Fast and self-contained (no runtime dependencies)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is &lt;strong&gt;Localization Resource Manager (LRM)&lt;/strong&gt;—a modern CLI tool with an interactive Terminal UI for managing &lt;code&gt;.resx&lt;/code&gt; files from the command line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Workflow: Adding Greek Support
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before LRM:&lt;/strong&gt;&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="c"&gt;# Copy the file&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;Resources.resx Resources.el.resx

&lt;span class="c"&gt;# Open in vim and manually translate 200+ strings&lt;/span&gt;
vim Resources.el.resx
&lt;span class="c"&gt;# Edit XML by hand... for hours&lt;/span&gt;

&lt;span class="c"&gt;# No way to validate I got everything&lt;/span&gt;
&lt;span class="c"&gt;# No way to check for consistency&lt;/span&gt;
&lt;span class="c"&gt;# Pure manual labor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With LRM:&lt;/strong&gt;&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="c"&gt;# Add Greek language file&lt;/span&gt;
lrm add-language &lt;span class="nt"&gt;--culture&lt;/span&gt; el &lt;span class="nt"&gt;--copy-from&lt;/span&gt; default

&lt;span class="c"&gt;# See what needs translation&lt;/span&gt;
lrm stats
&lt;span class="c"&gt;# ┌──────────────────┬───────┬──────────┐&lt;/span&gt;
&lt;span class="c"&gt;# │ Language         │ Keys  │ Coverage │&lt;/span&gt;
&lt;span class="c"&gt;# ├──────────────────┼───────┼──────────┤&lt;/span&gt;
&lt;span class="c"&gt;# │ English (Default)│ 203   │ 100.0%   │&lt;/span&gt;
&lt;span class="c"&gt;# │ Ελληνικά (el)    │ 203   │ 100.0%   │ (copied)&lt;/span&gt;
&lt;span class="c"&gt;# └──────────────────┴───────┴──────────┘&lt;/span&gt;

&lt;span class="c"&gt;# Edit translations interactively&lt;/span&gt;
lrm edit
&lt;span class="c"&gt;# [TUI opens, side-by-side view of English/Greek]&lt;/span&gt;
&lt;span class="c"&gt;# Search, navigate, edit, save&lt;/span&gt;
&lt;span class="c"&gt;# All from the terminal&lt;/span&gt;

&lt;span class="c"&gt;# Validate everything is correct&lt;/span&gt;
lrm validate
&lt;span class="c"&gt;# ✓ All validations passed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the workflow I wanted from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Terminal UI: Visual Studio Experience in SSH
&lt;/h2&gt;

&lt;p&gt;One of my favorite features is the interactive TUI. Run &lt;code&gt;lrm edit&lt;/code&gt; and you get a full-featured editor right in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────┐
│ File  Edit  Languages  Help                                    │
├────────────────────────────────────────────────────────────────┤
│ Search: [save_______________]   Ctrl+N=Add  F2=Language  F6=Val│
├──────────────┬────────────────────────┬────────────────────────┤
│ Key          │ English (Default)      │ Ελληνικά (el)          │
├──────────────┼────────────────────────┼────────────────────────┤
│ SaveButton   │ Save                   │ Αποθήκευση             │
│ CancelButton │ Cancel                 │ Ακύρωση                │
│ DeleteButton │ Delete                 │ Διαγραφή               │
│ ...          │ ...                    │ ...                    │
└──────────────┴────────────────────────┴────────────────────────┘
 Keys: 203/203 | Languages: 2                           [MODIFIED]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Side-by-side view of all languages&lt;/li&gt;
&lt;li&gt;Real-time search and filtering&lt;/li&gt;
&lt;li&gt;Add/edit/delete keys with full validation&lt;/li&gt;
&lt;li&gt;Manage languages (add/remove with F2/F3)&lt;/li&gt;
&lt;li&gt;Keyboard shortcuts for everything&lt;/li&gt;
&lt;li&gt;Works perfectly over SSH&lt;/li&gt;
&lt;li&gt;Menu bar for discoverability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's everything I wanted from Visual Studio, but in the terminal. No GUI needed. No Windows needed. Just solid terminal tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Command-Line Experience
&lt;/h2&gt;

&lt;p&gt;For scripting and automation, the CLI is comprehensive:&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="c"&gt;# Validate all .resx files&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# Add a new key to both languages&lt;/span&gt;
lrm add WelcomeMessage &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--lang&lt;/span&gt; default:&lt;span class="s2"&gt;"Welcome to our app"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--lang&lt;/span&gt; el:&lt;span class="s2"&gt;"Καλώς ήρθατε στην εφαρμογή μας"&lt;/span&gt;

&lt;span class="c"&gt;# Update an existing key&lt;/span&gt;
lrm update SaveButton &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--lang&lt;/span&gt; default:&lt;span class="s2"&gt;"Save Changes"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--lang&lt;/span&gt; el:&lt;span class="s2"&gt;"Αποθήκευση Αλλαγών"&lt;/span&gt;

&lt;span class="c"&gt;# Delete a key from all languages&lt;/span&gt;
lrm delete OldFeature

&lt;span class="c"&gt;# View a key across all languages&lt;/span&gt;
lrm view SaveButton
&lt;span class="c"&gt;# Key: SaveButton&lt;/span&gt;
&lt;span class="c"&gt;# English (Default): Save&lt;/span&gt;
&lt;span class="c"&gt;# Ελληνικά (el): Αποθήκευση&lt;/span&gt;

&lt;span class="c"&gt;# Export for translators (future-proofing)&lt;/span&gt;
lrm &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; translations.csv

&lt;span class="c"&gt;# Import translated work&lt;/span&gt;
lrm import updated_translations.csv

&lt;span class="c"&gt;# List all languages&lt;/span&gt;
lrm list-languages

&lt;span class="c"&gt;# Add/remove languages&lt;/span&gt;
lrm add-language &lt;span class="nt"&gt;--culture&lt;/span&gt; fr
lrm remove-language &lt;span class="nt"&gt;--culture&lt;/span&gt; de
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every command is designed to work in scripts, CI/CD pipelines, or interactive use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future-Proofing: The Translator Workflow
&lt;/h2&gt;

&lt;p&gt;While I'm currently doing all translations myself, I built LRM with the future in mind. When I eventually work with translators, the workflow is ready:&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="c"&gt;# Export current state to CSV&lt;/span&gt;
lrm &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--include-status&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; for_translator.csv

&lt;span class="c"&gt;# Translator edits in Excel/Google Sheets&lt;/span&gt;
&lt;span class="c"&gt;# (Much easier than editing XML!)&lt;/span&gt;

&lt;span class="c"&gt;# Import completed translations&lt;/span&gt;
lrm import for_translator.csv &lt;span class="nt"&gt;--overwrite&lt;/span&gt;

&lt;span class="c"&gt;# Validate everything imported correctly&lt;/span&gt;
lrm validate

&lt;span class="c"&gt;# Check coverage&lt;/span&gt;
lrm stats
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is standard practice for professional localization workflows. LRM makes it trivial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why VS Code Extensions Aren't Enough
&lt;/h2&gt;

&lt;p&gt;Someone might ask: "Why not just use VS Code with an extension?"&lt;/p&gt;

&lt;p&gt;I tried. The available &lt;code&gt;.resx&lt;/code&gt; extensions are mediocre at best:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most just syntax highlight XML&lt;/li&gt;
&lt;li&gt;No side-by-side language comparison&lt;/li&gt;
&lt;li&gt;No validation&lt;/li&gt;
&lt;li&gt;No batch operations&lt;/li&gt;
&lt;li&gt;Still requires a GUI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More importantly: &lt;strong&gt;I work primarily in the terminal.&lt;/strong&gt; SSH sessions, tmux, vim. Opening VS Code just to edit a translation breaks my flow entirely.&lt;/p&gt;

&lt;p&gt;LRM fits naturally into a terminal-first workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Built with modern .NET:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET 9 for cross-platform support&lt;/li&gt;
&lt;li&gt;Spectre.Console.Cli for CLI framework&lt;/li&gt;
&lt;li&gt;Terminal.Gui for interactive TUI&lt;/li&gt;
&lt;li&gt;Self-contained binaries (no runtime needed)&lt;/li&gt;
&lt;li&gt;Works on x64 and ARM64 (Raspberry Pi included)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fully cross-platform:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux (x64, ARM64)&lt;/li&gt;
&lt;li&gt;Windows (x64, ARM64)&lt;/li&gt;
&lt;li&gt;Tested on GitHub Actions runners&lt;/li&gt;
&lt;li&gt;Works in Docker containers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The architecture:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LocalizationManager/
├── Core/                   # Parsing, validation, models
├── Commands/               # CLI commands (validate, add, update, etc.)
├── UI/                     # Terminal.Gui TUI editor
└── Tests/                  # Comprehensive test suite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is open source and well-documented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Linux .NET Developers
&lt;/h2&gt;

&lt;p&gt;.NET on Linux is real. I'm building production WASM apps with .NET API backends, entirely on Linux. Many developers are doing the same.&lt;/p&gt;

&lt;p&gt;But the tooling ecosystem still assumes Windows + Visual Studio for many tasks. Localization is just one example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building .NET apps on Linux&lt;/li&gt;
&lt;li&gt;Working primarily in the terminal&lt;/li&gt;
&lt;li&gt;Using SSH for development&lt;/li&gt;
&lt;li&gt;Running .NET in Docker/Kubernetes&lt;/li&gt;
&lt;li&gt;Tired of "just use Visual Studio" answers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...you've probably hit these same walls.&lt;/p&gt;

&lt;p&gt;LRM is proof that we can have first-class terminal tooling for .NET development on Linux. We don't need to compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source and Community-Driven
&lt;/h2&gt;

&lt;p&gt;LRM is completely open source under the MIT license:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔗 &lt;a href="https://github.com/nickprotop/LocalizationManager" rel="noopener noreferrer"&gt;github.com/nickprotop/LocalizationManager&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick start on Linux:&lt;/strong&gt;&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="c"&gt;# One-line install&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://raw.githubusercontent.com/nickprotop/LocalizationManager/main/install-lrm.sh | bash

&lt;span class="c"&gt;# Or download manually&lt;/span&gt;
wget https://github.com/nickprotop/LocalizationManager/releases/latest/download/lrm-linux-x64.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; lrm-linux-x64.tar.gz
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;linux-x64/lrm /usr/local/bin/

&lt;span class="c"&gt;# Start using it&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;YourProject/Resources
lrm validate
lrm edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Documentation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager/blob/main/INSTALLATION.md" rel="noopener noreferrer"&gt;Installation Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager/blob/main/COMMANDS.md" rel="noopener noreferrer"&gt;Command Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager/blob/main/EXAMPLES.md" rel="noopener noreferrer"&gt;Usage Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nickprotop/LocalizationManager/blob/main/CI-CD.md" rel="noopener noreferrer"&gt;CI/CD Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;If you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Develop .NET apps on Linux&lt;/li&gt;
&lt;li&gt;Work primarily in the terminal&lt;/li&gt;
&lt;li&gt;Need to manage localization&lt;/li&gt;
&lt;li&gt;Want proper CLI tooling instead of GUI-only solutions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Give LRM a try. It's the tool I wished existed when I started building my WASM app.&lt;/p&gt;

&lt;p&gt;Contributions, bug reports, and feature requests are welcome. This is a tool built to solve real problems for real developers working on Linux.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://github.com/nickprotop/LocalizationManager" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://github.com/nickprotop/LocalizationManager/blob/main/README.md" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐛 &lt;a href="https://github.com/nickprotop/LocalizationManager/issues" rel="noopener noreferrer"&gt;Report Issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;a href="https://github.com/nickprotop/LocalizationManager/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⬇️ &lt;a href="https://github.com/nickprotop/LocalizationManager/releases/latest" rel="noopener noreferrer"&gt;Download&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Do you develop .NET on Linux? What tooling gaps have you encountered? Let me know in the comments or open a discussion on GitHub. Let's make .NET on Linux a first-class experience.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>linux</category>
      <category>opensource</category>
      <category>localization</category>
    </item>
  </channel>
</rss>
