DEV Community

Nikolaos Protopapas
Nikolaos Protopapas

Posted on

The $0 Localization Stack for Solo .NET Developers

How I Translated My .NET App to 5 Languages for $0 (As a Solo Dev)

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 $100-500/month.

For a side project? No way.

So I built my own solution. Here's how you can translate your .NET app for free too.

The Problem

I had 400+ strings in my app. I needed translations for at least Greek and English, ideally more. My options:

Option Cost Time
Lokalise $120/mo Fast
Phrase $25/mo Fast
Manual Google Translate $0 Hours of copy-paste hell
Hire translator $100+ Days

None of these worked for a solo dev with a side project.

The Solution: Local LLM Translation

What if you could run a translation model on your own machine? No API costs, no rate limits, no data leaving your computer.

That's exactly what I built into LRM (Localization Resource Manager).

Setup (5 minutes)

1. Install Ollama (the local LLM runtime):

# Linux
curl -fsSL https://ollama.ai/install.sh | sh

# Windows/Mac - download from https://ollama.ai
Enter fullscreen mode Exit fullscreen mode

2. Pull a model:

ollama pull llama3.2
# Or for better quality: ollama pull llama3.1:8b
Enter fullscreen mode Exit fullscreen mode

3. Install LRM:

# Linux (one-liner)
curl -sSL https://raw.githubusercontent.com/nickprotop/LocalizationManager/main/install-lrm.sh | bash

# Or via APT (Ubuntu/Debian)
sudo add-apt-repository ppa:nickprotop/lrm-tool
sudo apt install lrm-standalone

# Windows/macOS - download from GitHub releases
# https://github.com/nickprotop/LocalizationManager/releases
Enter fullscreen mode Exit fullscreen mode

That's it. No API keys, no accounts, no credit cards.

Translate Everything

# Navigate to your project
cd MyApp/Resources

# Translate all missing keys to Spanish, French, German
lrm translate --only-missing --provider ollama --target-languages es,fr,de
Enter fullscreen mode Exit fullscreen mode

That's it. LRM finds all your resource files, identifies missing translations, and fills them in using the local LLM.

400+ keys × 3 languages = 1,200+ translations. Zero dollars. Under 10 minutes.

No GPU? No Problem.

Don't have a beefy GPU? Use the free online providers - still $0:

MyMemory (No signup required)

lrm translate --only-missing --provider mymemory --target-languages es,fr,de
Enter fullscreen mode Exit fullscreen mode

5,000 characters/day free. Enough for most projects.

Lingva (Google Translate quality, free)

lrm translate --only-missing --provider lingva --target-languages es,fr,de
Enter fullscreen mode Exit fullscreen mode

Uses open-source proxy to Google Translate. No API key needed.

Real Example

Here's my actual workflow for adding Italian to my app:

# Add a new language file
lrm add-language --culture it

# Check what's missing
lrm stats
Enter fullscreen mode Exit fullscreen mode
                         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    │
└───────────────┴────────────┴───────────┴───────┴──────────┴───────────┘
Enter fullscreen mode Exit fullscreen mode

Italian at 0%! Let's fix that:

# Translate Italian with one command
lrm translate --only-missing --provider ollama --target-languages it
Enter fullscreen mode Exit fullscreen mode
╭────────────────────┬──────────┬─────────────────────────────────────┬────────╮
│ 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.
Enter fullscreen mode Exit fullscreen mode

12 keys translated in seconds. Done.

The CLI uses colorized output - missing translations show in red, complete in green.

Quality Comparison

"But is the quality good enough?"

I compared Ollama (llama3.2) vs Google Translate vs DeepL for my app strings:

Provider Quality Cost Speed
DeepL Excellent $20/mo Fast
Google Very Good Pay-per-use Fast
Ollama (llama3.2) Good $0 Medium
MyMemory Good $0 Fast

For UI strings like "Save", "Cancel", "Error occurred"? Ollama is more than good enough.

For marketing copy or legal text? Maybe pay for DeepL.

For 90% of app localization? Free works fine.

More Than Just Translation

Once I built the translation part, I kept going. LRM now does:

# Validate resource files (find issues)
lrm validate
Enter fullscreen mode Exit fullscreen mode
⚠ Validation found 12 issue(s)

                                  Empty Values
┌──────────┬───────────────────────────────────────────────────────────────────┐
│ Language │ Empty Keys                                                        │
├──────────┼───────────────────────────────────────────────────────────────────┤
│ de       │ CancelButton, DeleteConfirmation, ErrorOccurred, PatientName...   │
│ fr       │ ErrorOccurred, PatientName, AppointmentDate, NoResultsFound...    │
└──────────┴───────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Find Dead Code (.NET only)

This one's a game-changer. LRM scans your C#, Razor, and XAML files to find:

  • Missing keys - used in code but not defined in .resx (runtime errors waiting to happen!)
  • Unused keys - defined in .resx but never used in code (dead translations)
lrm scan --source-path ./src
Enter fullscreen mode Exit fullscreen mode
✓ 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
Enter fullscreen mode Exit fullscreen mode

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.

On a larger project (my actual app), the scan also found the opposite:

✓ 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
Enter fullscreen mode Exit fullscreen mode

56 dead keys I can clean up. Less clutter, smaller resource files.

More Tools

# Interactive editor (TUI)
lrm edit

# Convert between formats
lrm convert --from resx --to json

# Export for translators
lrm export --format csv
Enter fullscreen mode Exit fullscreen mode

Works With Your Stack

LRM supports what you're already using:

  • .resx (WPF, WinForms, ASP.NET)
  • JSON (ASP.NET Core, Blazor)
  • i18next (React, Vue, Angular)
  • Android strings.xml
  • iOS .strings

All formats, one tool.

The Workflow That Actually Works

Here's my complete localization workflow now:

# 1. Add new English strings normally in your app

# 2. Validate (catch issues early)
lrm validate

# 3. Translate missing keys
lrm translate --only-missing --provider ollama

# 4. Quick review in TUI
lrm edit

# 5. Commit
git add Resources/
git commit -m "Add translations"
Enter fullscreen mode Exit fullscreen mode

Total time: 5 minutes. Total cost: $0.

Why I Open-Sourced It

I built this for myself. Then I realized other solo devs have the same problem.

So I open-sourced it: github.com/nickprotop/LocalizationManager

  • MIT License (use it however you want)
  • Works 100% offline
  • No accounts, no tracking
  • Self-host everything

From Side Project to Product

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.

Now it's a full platform: lrm-cloud.com

The cloud adds what solo devs need when they grow:

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

But here's the thing: the CLI is still 100% free and open source. The cloud is optional - and self-hosted if you want!

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.

TL;DR

# Install (Linux)
curl -sSL https://raw.githubusercontent.com/nickprotop/LocalizationManager/main/install-lrm.sh | bash
ollama pull llama3.2

# Translate your app for free
cd YourProject/Resources
lrm translate --only-missing --provider ollama --target-languages es,fr,de,it,pt

# Done. $0. 5 minutes.
Enter fullscreen mode Exit fullscreen mode

Got questions? Open an issue on GitHub or find me on Reddit.

Found it useful? A GitHub star helps others discover it.


Tags: dotnet, localization, i18n, translation, ollama, llm, open-source, csharp, blazor, aspnetcore

Top comments (0)