Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions BlazorLocalization.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorLocalization.Extensio
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorLocalization.TranslationProvider.Crowdin.Tests", "tests\BlazorLocalization.TranslationProvider.Crowdin.Tests\BlazorLocalization.TranslationProvider.Crowdin.Tests.csproj", "{DAB8B6E9-1B23-40A8-9E68-E04ED1565417}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleBlazorApp", "tests\SampleBlazorApp\SampleBlazorApp.csproj", "{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -101,16 +103,29 @@ Global
{DAB8B6E9-1B23-40A8-9E68-E04ED1565417}.Release|x64.Build.0 = Release|Any CPU
{DAB8B6E9-1B23-40A8-9E68-E04ED1565417}.Release|x86.ActiveCfg = Release|Any CPU
{DAB8B6E9-1B23-40A8-9E68-E04ED1565417}.Release|x86.Build.0 = Release|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Debug|x64.ActiveCfg = Debug|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Debug|x64.Build.0 = Debug|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Debug|x86.ActiveCfg = Debug|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Debug|x86.Build.0 = Debug|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Release|Any CPU.Build.0 = Release|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Release|x64.ActiveCfg = Release|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Release|x64.Build.0 = Release|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Release|x86.ActiveCfg = Release|Any CPU
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{70DCB212-441A-4D50-82E8-026D52A368C8} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{8F4BE7F1-CC56-4383-8C1D-9F3CAA050B67} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{DAB8B6E9-1B23-40A8-9E68-E04ED1565417} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{3CE76414-FCC9-4F89-8DCD-283FEB0A57A7} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{510A06EC-13E0-41F4-BF30-B5A27589FC78} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{6069D306-E1C1-40CF-8188-F11F19FAC9BA} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{70DCB212-441A-4D50-82E8-026D52A368C8} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{8F4BE7F1-CC56-4383-8C1D-9F3CAA050B67} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{DAB8B6E9-1B23-40A8-9E68-E04ED1565417} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{EB1814F7-5FA8-4827-A7E4-575FE44F8A2F} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
EndGlobal
43 changes: 31 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ See [Examples](docs/Examples.md) for plurals, ordinals, enum display names, and
| Package | Version | Install |
|---------|:-------:|--------:|
| [**BlazorLocalization.Extensions**](https://www.nuget.org/packages/BlazorLocalization.Extensions) <br/> Caches translations, supports plurals and inline translations, pluggable translation providers | [![NuGet](https://img.shields.io/nuget/v/BlazorLocalization.Extensions.svg)](https://www.nuget.org/packages/BlazorLocalization.Extensions) | `dotnet add package BlazorLocalization.Extensions` |
| [**BlazorLocalization.Extractor**](https://www.nuget.org/packages/BlazorLocalization.Extractor) <br/> CLI tool (`blazor-loc`) — Roslyn-based scanner that extracts source strings from `.razor`, `.cs`, and `.resx` files | [![NuGet](https://img.shields.io/nuget/v/BlazorLocalization.Extractor.svg)](https://www.nuget.org/packages/BlazorLocalization.Extractor) | `dotnet tool install -g BlazorLocalization.Extractor` |
| [**BlazorLocalization.Extractor**](https://www.nuget.org/packages/BlazorLocalization.Extractor) <br/> CLI tool (`blazor-loc`) — inspect translation health and extract source strings from `.razor`, `.cs`, and `.resx` files | [![NuGet](https://img.shields.io/nuget/v/BlazorLocalization.Extractor.svg)](https://www.nuget.org/packages/BlazorLocalization.Extractor) | `dotnet tool install -g BlazorLocalization.Extractor` |

Translation providers:

Expand Down Expand Up @@ -109,24 +109,43 @@ Built on [Microsoft's `IStringLocalizer`](https://learn.microsoft.com/en-us/aspn

---

## 🎬 String Extraction
## 🎬 CLI Tool — Inspect & Extract

Already using `IStringLocalizer`? The Extractor scans your `.razor`, `.cs`, and `.resx` files and exports every translation string — no matter which localization backend you use.

![blazor-loc interactive wizard demo](https://raw.githubusercontent.com/linckez/BlazorLocalization/main/docs/assets/blazor-loc.svg)
A Roslyn-based CLI that understands both BlazorLocalization's `Translation()` API and Microsoft's built-in `IStringLocalizer["key"]` + `.resx` — no code changes or adoption required. Point it at your project and go.

```bash
dotnet tool install -g BlazorLocalization.Extractor
```

# Interactive wizard — run with no arguments
blazor-loc
### Inspect: Translation Health Audit

```bash
blazor-loc inspect ./src
```

You get a table of every translation key, where it's used, its source text, which locales have it, and whether anything is wrong. Spot duplicate keys with conflicting values, `IStringLocalizer` calls the scanner couldn't resolve, and `.resx` entries that no code references anymore — across hundreds of files in seconds.

### Extract: Keep Translations in Sync

Without tooling, keeping your translation platform in sync with your code means manually copying strings — every key, every language, every time something changes. There's no built-in way to get translation strings out of a .NET project and into Crowdin, Lokalise, a database, or anywhere else.

# Or go direct
```bash
blazor-loc extract ./src -f po -o ./translations
```

Upload the generated files to Crowdin, Lokalise, or any translation management system.
See [Extractor CLI](docs/Extractor.md) for recipes, CI integration, and export formats.
One command scans your entire codebase and exports every source string to PO, i18next JSON, or generic JSON. Run it in CI on every merge and your translation platform always has the latest strings.

### Interactive Wizard

Run with no arguments for a guided walkthrough:

![blazor-loc interactive wizard demo](https://raw.githubusercontent.com/linckez/BlazorLocalization/main/docs/assets/blazor-loc.svg)

```bash
blazor-loc
```

See [Extractor CLI](docs/Extractor.md) for all recipes, CI integration, and export formats.

---

Expand All @@ -141,7 +160,7 @@ See [Extractor CLI](docs/Extractor.md) for recipes, CI integration, and export f
| Named placeholders | ✗ | ✗ | ✓ — via SmartFormat |
| External provider support | ✗ | ✗ | ✓ — pluggable |
| Merge-conflict-free | ✗ — XML | ✗ — PO files | ✓ — with OTA providers. File-based providers are opt-in |
| Automated string extraction | Manual | Manual | Roslyn-based CLI |
| Translation audit + extraction | Manual | Manual | Roslyn-based CLI — inspect and export |
| Reusable definitions | ✗ | ✗ | ✓ — define once, use anywhere |
| Standard `IStringLocalizer` | ✓ | ✓ | ✓ |
| Battle-tested | ✓ — 20+ years | ✓ | Production use, actively maintained |
Expand All @@ -155,7 +174,7 @@ See [Extractor CLI](docs/Extractor.md) for recipes, CI integration, and export f
| Topic | Description |
|-------|-------------|
| [Examples](docs/Examples.md) | `Translation()` usage — simple, placeholders, plurals, ordinals, select, inline translations, reusable definitions |
| [Extractor CLI](docs/Extractor.md) | Install, interactive wizard, common recipes, CI integration, export formats |
| [Extractor CLI](docs/Extractor.md) | Install, inspect & extract commands, interactive wizard, CI integration, export formats |
| [Configuration](docs/Configuration.md) | Cache settings, `appsettings.json` binding, multiple providers, code-only config |
| [Crowdin Provider](docs/Providers/Crowdin.md) | Over-the-air translations from Crowdin — distribution hash, export formats, error handling |
| [JSON File Provider](docs/Providers/JsonFile.md) | Load translations from flat JSON files on disk |
Expand Down
44 changes: 37 additions & 7 deletions docs/Extractor.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

# Extractor CLI

`blazor-loc` scans your `.razor`, `.cs`, and `.resx` files and exports source strings to translation files. Upload the output to Crowdin, Lokalise, or any translation management system.
`blazor-loc` is a Roslyn-based CLI that understands both BlazorLocalization's `Translation()` API and Microsoft's built-in `IStringLocalizer["key"]` + `.resx`. No code changes or adoption required — point it at any `IStringLocalizer` project and go.

It works with any `IStringLocalizer` codebase — regardless of which localization backend you use.
- **`inspect`** — Translation health audit. See every translation key, where it's used, what's missing, what conflicts, and how complete each locale is.
- **`extract`** — Scan your codebase and export every source string. Run it in CI on every merge to keep your translation platform in sync.

## Install

Expand All @@ -28,7 +29,9 @@ Run with no arguments to launch the interactive wizard. It walks you through pro
blazor-loc
```

## Common Recipes
## Extract

Without tooling, keeping translations in sync means manually copying strings between your code and your translation platform — every key, every language, every time something changes. `extract` scans your entire codebase and exports every source string in one command. Run it locally or in CI on every merge.

Extract to Crowdin i18next JSON (the default format):

Expand Down Expand Up @@ -84,19 +87,46 @@ Narrow to specific locales:
blazor-loc extract ./src -f i18next -o ./translations -l da -l es-MX
```

Debug what the scanner detects (raw calls + merged entries):
## Inspect

Point `inspect` at your project and get a translation health audit — the full picture across every file, locale, and pattern in seconds.

```bash
blazor-loc inspect ./src
```

Output as JSON (pipeable to other tools):
### What you see

**Translation entries** — one row per unique key, showing where it's used in your code, its source text, which form it takes (simple, plural, select...), and which locales have a translation. Spot a key that's missing `de` when every other row has it.

**Conflicts** — same key used with different source texts in different places. Almost always a bug. The table shows exactly which files disagree and what each one says.

**Extraction warnings** — the handful of calls the scanner couldn't confidently resolve. Things like `Loc[someVar ? Loc["..."] : Loc["..."]]` or mangled expressions that somehow made it through code review. By default, only these problem cases surface — not the hundreds of healthy calls.

**Locale coverage** — per-language summary: how many keys each locale has, what percentage that covers, and any keys that only exist in one locale but not the source. At a glance you see that `es-MX` is at 97.6% but `vi` is at 85.7%.

**Cross-reference summary** — one line bridging code and data: how many keys resolved, how many are missing, how many `.resx` entries have no matching code reference.

### Options

See full key/value tables per language (instead of the default summary):

```bash
blazor-loc inspect ./src --show-resx-locales
```

See every line of code where a translation was found (including all healthy ones):

```bash
blazor-loc inspect ./src --json
blazor-loc inspect ./src --show-extracted-calls
```

`inspect` dumps every detected `IStringLocalizer` call with its key, source text, plural forms, and file location — useful for verifying the scanner found what you expected.
Output as JSON (auto-enabled when stdout is piped):

```bash
blazor-loc inspect ./src --json
blazor-loc inspect ./src | jq '.translationEntries[] | select(.status == "Missing")'
```

## Export Formats

Expand Down
Loading