H073.Local.Tool 2.0.0-alpha.2

Prefix Reserved
This is a prerelease version of H073.Local.Tool.
dotnet tool install --global H073.Local.Tool --version 2.0.0-alpha.2
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local H073.Local.Tool --version 2.0.0-alpha.2
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=H073.Local.Tool&version=2.0.0-alpha.2&prerelease
                    
nuke :add-package H073.Local.Tool --version 2.0.0-alpha.2
                    

H073.Local.Tool

Command-line tools for H073.Local, the .NET localization library.

This is not a library you reference from code. It installs a program called hxloc onto your machine, which you run from a terminal or a CI job. It never ends up inside your application.

dotnet tool install -g H073.Local.Tool --prerelease

Three commands:

hxloc check Validate a source tree. Fails a build on real problems.
hxloc compile Turn JSON catalogues into the compact .loc3 binary format.
hxloc import Turn XLIFF back into a source tree.

check is the one you want first, and it works whether or not you ever compile.


hxloc check — find broken translations before you ship

hxloc check ./Localization
Schema: 412 keys in 3 bank(s)

  de/ui
    error  'welcome' uses undeclared placeholder(s) {player}
    error  missing key 'menu.settings'
    warn   'menu.play' is 21 characters, over its limit of 12

  pl/items
    warn   'stones' has no "few", "many" form(s); pl needs "one", "few", "many", "other"

2 error(s), 2 warning(s).

Why you need this

The library reads a folder of files, one folder per language. Unless you write a schema, nothing declares which keys are supposed to exist — each file simply lists what it happens to contain. If English is missing a key that German has, no error is raised anywhere: at runtime the fallback quietly covers for it, and the missing-key log only ever sees keys that something actually asked for. A key on a screen nobody opened during testing stays invisible until a user finds it.

hxloc check is the only thing that catches that before release.

With a schema

A _schema directory declares what exists, one file per bank:

// Localization/_schema/ui.json
{
  "bank": { "tags": ["always", "interface"] },
  "keys": {
    "menu.play": { "context": "Main menu button", "maxLength": 12 },
    "welcome":   { "variables": { "name": "the player's display name" } },
    "files":     { "forms": "plural" }
  }
}

The schema is then the authority, and every language is checked against it — including your source language. Without a schema the source language is the one thing never checked, because it defines the answer.

Severity
A declared key missing from a language error
A key the schema does not declare error
A bank the schema does not declare error
A placeholder the schema does not declare error
A declared placeholder the text never uses warning
Tagged forms on a key declared single error
A plain string where plural was declared warning
A plural category the language needs but the file lacks warning (error if other is missing)
A translation over its maxLength warning
A bank missing from a language entirely warning
Malformed JSON, or two keys that collide error

Without a schema

Everything still works; there is simply nothing authoritative to compare against, so the language with the most keys stands in as a reference. Override it with --reference. That catches most drift, but it cannot tell a key missing everywhere from a key that was never meant to exist.

On character limits

maxLength is reported as a warning, deliberately. A character count stands in for what actually overflows a button and does it badly: MMMMMMMMMM and iiiiiiiiii are the same length and roughly three times apart in width. It is the only approximation a build can make without knowing your font. Measure real width in your UI layer, where the font is known.

Options

--reference <code> Compare against this language. Only used when there is no schema.
--strict Fail on warnings too, not just errors.
--quiet Print only the summary line.

In CI

Non-zero exit on errors, so it fails a build:

- run: dotnet tool install -g H073.Local.Tool --prerelease
- run: hxloc check ./Localization

hxloc compile — build the binary format

hxloc compile ./Localization ./Localization.compiled
  de/ui                          412 keys      18,204 →     14,880 bytes
  en/ui                          412 keys      17,993 →     14,612 bytes

2 catalogue(s) → ./Localization.compiled   29,492 bytes, 82% of the JSON

It walks every language folder and turns each .json into a .loc3:

Localization/                     Localization.compiled/
├── _schema/ui.json                (not copied — it describes sources)
├── de/ui.json           →        ├── de/ui.loc3
└── en/ui.json           →        └── en/ui.loc3

Ship the compiled folder and point Loc3Source at it. The source tree stays in version control, and the schema never travels — it describes sources, and a compiled catalogue is the result of applying it.

Compiling is optional. JsonSource reads the source tree directly and supports every feature. Compiling buys startup time and memory; skipping it costs you nothing else.

Run it as part of the build

<Target Name="CompileLocalization" BeforeTargets="Build"
        Condition="'$(Configuration)' == 'Release'">
  <Exec Command="hxloc compile ./Localization ./Localization.compiled" />
</Target>

This belongs in the build, not in your application. Compiling at startup would ship the JSON as well and pay the conversion cost on every launch — strictly worse than reading the JSON directly.

Options

--keys Keep key names in the output. Larger files, but missing-key reports name the key instead of a hash. Useful for internal test builds.
--language <code> Compile only this language. Repeatable.
--clean Delete the output folder first.
--quiet Print only errors.

What the compiler refuses to emit

Anything a reader could misinterpret fails the build instead: two keys whose hashes collide, a duplicate key, duplicate labels within one entry, a value over the format's limits, or a labelled entry with no category. All of them name the offending key.


hxloc import — XLIFF into a source tree

hxloc import ./from-translators ./Localization

XLIFF is the standard format for sending text out for translation and getting it back. One file carries a language pair, so an import writes both catalogues — plus the schema, since XLIFF also carries maxwidth and <note>:

de.xliff  →  _schema/ui.json     ← from maxwidth and note
             en/ui.json          ← from <source>
             de/ui.json          ← from <target>

It lands in the source tree rather than compiling directly, so the result passes through check before it becomes a catalogue. Translations that arrive from outside are the ones most worth checking.

Three things worth knowing

Plural forms travel as key[form] unit ids. XLIFF 1.2 has no element for plural forms — the specification does not model them — so every toolchain invents a convention, and this is ours. Units named stones[one] and stones[few] are regrouped into one key. Without that, a round trip silently turns one key with four forms into four unrelated keys.

Inline markup is flattened. A placeholder may arrive wrapped in <ph>, and formatting in <g> or <bpt>. Reading only an element's own text would drop what they contain; reading the raw XML would leave tags in your strings. Both are reduced to the text they wrap.

Only XLIFF 1.2 is read. Version 2.0 restructured the format — different namespace, different elements, different nesting. It is refused with a clear message rather than parsed as 1.2, which would find no units and report an empty but successful import.

Options

--state <name> Only import units whose target has this state, e.g. translated or final.
--quiet Print only errors.

A unit with an empty target is left out of the target catalogue entirely, so the key falls through to your fallback language rather than rendering as blank.


Exit codes

0 Success. For check, no problems — or warnings only, without --strict.
1 check found errors; compile or import failed on one or more files.
2 Bad usage — missing arguments, unknown option, path does not exist, unreadable schema.

Help

hxloc --help
hxloc --version

For the file format, the JSON layout and the library itself, see the H073.Local documentation.

Licence

Apache-2.0

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
2.0.0-alpha.2 67 8/18/2026
2.0.0-alpha.1 59 8/18/2026