DeusaldLocalizerCommon 2.0.10

dotnet add package DeusaldLocalizerCommon --version 2.0.10
                    
NuGet\Install-Package DeusaldLocalizerCommon -Version 2.0.10
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DeusaldLocalizerCommon" Version="2.0.10" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DeusaldLocalizerCommon" Version="2.0.10" />
                    
Directory.Packages.props
<PackageReference Include="DeusaldLocalizerCommon" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DeusaldLocalizerCommon --version 2.0.10
                    
#r "nuget: DeusaldLocalizerCommon, 2.0.10"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DeusaldLocalizerCommon@2.0.10
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DeusaldLocalizerCommon&version=2.0.10
                    
Install as a Cake Addin
#tool nuget:?package=DeusaldLocalizerCommon&version=2.0.10
                    
Install as a Cake Tool

Deusald Localizer

A community localization tool for games. Translators edit strings, propose alternative translations, and vote on the best one per key — working offline against local files or online against a Git-backed backend, with no database and no lock-in: a project is just a folder of JSON files in a Git repository.

The same editor ships as two clients that share one UI: a .NET MAUI desktop app (Windows / macOS) and a Blazor WebAssembly web app that runs entirely in the browser.

<p align="center"> <img src="docs/screenshots/editor.png" alt="Translation editor" width="800"> </p>

Try it in your browser: the web version runs at https://deusald.github.io/DeusaldLocalizer/ — no install required. The same editor also ships as a desktop app (see Getting started).


Table of contents


What it does

Deusald Localizer is built for translating game text collaboratively. The whole UI is a three-column editor: languages on the left, the key list in the middle, and the key detail on the right. The middle column resizes by dragging, and keys are browsed through a collapsible category tree.

  • Per-key translation editing — every localization key holds one translation per language, each with a status (untranslated / suggested / approved) and a character-length limit that the editor enforces.
  • Suggestions & voting — translators propose alternative wordings for a key/language pair. Each suggestion collects votes, so the community can converge on the best translation instead of one person overwriting another's work.
  • Comments & @mentions — free-form comment threads attach to a key, a single key+language translation, or a suggestion, and members can @mention each other; a notification bell surfaces mentions and recent changes.
  • Source-drift detection — a translation stores a SHA-256 hash of the source text it was written against. When the source language string changes, dependent translations are flagged as "source changed" so they can be revisited.
  • Categories — keys are organized into a category tree (folders) and can be filtered by category, including collapse-all and single-select.
  • Languages & sub-languages — any BCP-47 language, plus sub-languages (regional variants stored as {base}_{tag}, e.g. en-US_simple) that reuse the base culture's formatting.
  • Tags & flags — keys carry free-form tags (e.g. ui, button) for search/filter, plus structured workflow flags (with notes and authorship) to mark keys that need attention. The key list can be filtered by both.
  • Variables & live preview — keys declare typed variables (including project-level enums) and the editor renders a live SmartFormat preview so translators see plural/gender/conditional output (and basic HTML tags) as they type.
  • Project enums — define named integer→string enums once and reference them from variables, enabling SmartFormat choose/conditional formatting across translations.
  • Excel & C# export/import — round-trip translations through .xlsx for external translators (async, with a progress bar), or export a ready-to-use C# script — a nested static-class tree of key GUIDs plus a Get(language, keyId, values) SmartFormat lookup — to drop straight into a game project. Exports filter by language subset, tags, flags, and a "modified after" date, and remember their settings per project.
  • Undo/redo & deletion — a session undo/redo stack; keys and members can be deleted (removing a member reassigns its work to the offline user).
  • Members & access tokens — projects have members with per-language review permissions and an admin role; online access is authenticated with hashed access tokens.
  • Localized UI — the app's own interface ships in 11 languages (English plus Czech, German, Spanish, French, Italian, Japanese, Korean, Polish, Brazilian Portuguese, Russian), selectable from the Home screen. The UI strings are themselves authored as a Localizer project (deusald-localizer-ui/) and C#-exported into the app — the tool localizes itself.
  • Works offline or online — a project with no API URL is purely local files; add an API URL and the app syncs against the backend Git bot (see below). The distinction is derived from the project, not a manual toggle.

Screenshots

Home / project picker Translation editor Suggestions & voting
Home Editor Voting
Excel export Variables & preview Members & tokens
Export Variables Members

Two clients, one UI

The entire editor is one shared Blazor UI (WebCommon), hosted two ways:

  • Desktop (App) — a .NET MAUI Blazor Hybrid shell for Windows and macOS, with native folder pickers and Velopack auto-update. Projects are plain folders on disc.
  • Web (WebApp) — a Blazor WebAssembly PWA that runs entirely in the browser and stores projects in IndexedDB (with navigator.storage.persist() so offline work isn't evicted). From the Home screen you can create, open, import (.zip), export (.zip), and delete local projects; the zip round-trips the exact desktop project-folder format, so a project moves between web and desktop as a file. Online projects can also be connected and downloaded into the browser over the sync protocol.

Because both hosts render the same components, a feature lands in both clients at once. The platform-specific bits (file pickers, secure storage, IndexedDB vs. disc) sit behind interfaces implemented per host.

Tech stack

Everything targets .NET 10.

Project Type Key libraries
App .NET MAUI Blazor Hybrid desktop app (Windows / MacCatalyst) — the UI is Blazor in a BlazorWebView; MAUI is just the shell. CommunityToolkit.Maui (native file/folder pickers), Velopack (auto-update)
WebApp Blazor WebAssembly PWA (DeusaldLocalizerWeb), deployable to GitHub Pages — browser shell + IndexedDB storage. Microsoft.AspNetCore.Components.WebAssembly
WebCommon Razor class library — the whole shared editor UI, session state, and the platform-abstraction interfaces. Consumed by both App and WebApp. —
Common Shared class library (DeusaldLocalizerCommon): all domain models, file persistence, and business services. Multi-targets netstandard2.1;net10.0, pinned to C# 9 with implicit usings disabled. SmartFormat (previews), ClosedXML (.xlsx), BCrypt.Net-Next (token hashing), Newtonsoft.Json (project files, enums as strings)
Backend ASP.NET Core Web API (DeusaldLocalizerBackend) — the Git "bot" that mediates online sync/push. Microsoft.AspNetCore.OpenApi

Reference graph: App → WebCommon → Common, WebApp → WebCommon → Common, Backend → Common.

Solution layout

DeusaldLocalizer.sln
├── App/         .NET MAUI Blazor Hybrid desktop app  → WebCommon → Common
├── WebApp/      Blazor WebAssembly PWA (web client)  → WebCommon → Common
├── WebCommon/   Shared Blazor editor UI + interfaces → Common
├── Common/      Shared domain models + services      (netstandard2.1 / net10.0, C# 9)
└── Backend/     ASP.NET Core Git-sync bot            → Common

LocProject is the aggregate root for one project, holding Metadata, ProjectMembers, Categories, Enums, UncommitedChanges, and Keys. Each key owns its Translations (per language), plus tags, flags, and variables.

How a project is stored

A project is a folder of JSON files, one file per entity — no database:

my-project/
├── metadata.json               project name, languages, main language, SyncId, API URL
├── Members/{guid}.json
├── Categories/{guid}.json
├── Enums/{guid}.json
├── Keys/{guid}.json            one key per file (translations, suggestions, tags, flags…)
└── UncommittedChanges/0000.json…   pending changes queue (online mode)

Writes go to a .tmp sibling and are then renamed, so a project survives a mid-write crash. Because a project is plain files, it lives naturally in a Git repository — which is exactly what the backend uses.

Getting started

The quickest way to try the editor is the hosted web version at https://deusald.github.io/DeusaldLocalizer/ — nothing to install. To run it yourself:

Desktop app

Prerequisites

  • .NET 10 SDK
  • The MAUI workload: dotnet workload install maui
  • Windows 10.0.17763.0+ (or macOS 15+ for MacCatalyst)

Run the app (Windows)

dotnet build App/App.csproj -t:Run -f net10.0-windows10.0.19041.0

From the home screen you can create a new local project, open an existing project folder, or log in to an online project by pointing it at a backend API URL.

Try the sample project: open the ExampleLoc/ folder from the home screen — a small offline "Example RPG" project (English / German / French / Polish) that exercises most features: categories, tags, workflow flags, SmartFormat variables and enums, competing suggestions with votes, and a key whose source text has drifted out of sync.

Web app

Prerequisites: just the .NET 10 SDK — no MAUI workload needed.

# Dev server (F5-able from the IDE too)
dotnet run --project WebApp

# Publish to static files for hosting
dotnet publish WebApp -c Release -o publish

Projects live in the browser's IndexedDB. Create a new one, open an existing local project, or import a project .zip exported from the desktop app; you can export any project back to a .zip to move it elsewhere. The web app deploys to GitHub Pages via .github/workflows/deploy-pages.yml — see docs/WebApp-Plan.md.

Online sync protocol

Online mode never merges and needs no database — the backend is a Git bot and SyncId (a GUID in metadata.json) is the version token:

  • Every commit of a push batch carries SyncId: <guid> in its message. The bot finds "the version a client last saw" with git log --grep.
  • Sync — the bot fetches and resets to origin/<branch>, diffs from the client's SyncId commit to HEAD, and returns the changed/deleted files (or a full resync if the id is unknown, or "up to date" if already at HEAD).
  • Push — the bot fetches/resets, validates each change against the freshly-pulled state, applies each change as one commit (author = the member, committer = the bot), bumps the SyncId in a final commit, and does a plain git push. If the remote moved during processing the push is rejected (git reset --hard, no merge) and the client re-syncs.
  • Work is serialized per project (parallel across projects) for Git work-tree safety.

Clients (the App) only download files over HTTP — they never run Git. The backend is the only component that touches Git.

Backend setup

The backend holds one working-tree clone per managed project under a repos root, and exposes:

Method & route Purpose
POST /projects/{projectId}/sync Pull changes since the client's SyncId
POST /projects/{projectId}/push Apply a batch of changes and push
GET /health Liveness check → { "status": "ok" }

Auth travels in headers on sync/push:

Authorization: Bearer <raw-access-token>
X-User-Id: <member-guid>

The token is verified with BCrypt against the member's HashedAccessToken stored in the project's Members/.

Run the backend locally

Prerequisites: .NET 10 SDK and git on your PATH.

dotnet run --project Backend

By default it listens on http://localhost:5114 (and https://localhost:7088 with the https profile — see Backend/Properties/launchSettings.json). OpenAPI is mapped in the Development environment.

Configure the managed projects before syncing — either edit Backend/appsettings.json or, preferably for secrets, use appsettings.Development.json / environment variables / user secrets. A minimal config:

{
  "Bot": {
    "ReposRoot": "repos",
    "CommitterName": "Deusald Localizer Bot",
    "CommitterEmail": "bot@localizer",
    "Projects": [
      {
        "ProjectId": "00000000-0000-0000-0000-000000000000",
        "Slug": "my-game",
        "RemoteUrl": "https://<user>:<github-pat>@github.com/you/my-game-loc.git",
        "Branch": "main"
      }
    ]
  }
}

The GitHub push credential lives in RemoteUrl — use a Personal Access Token over HTTPS (https://<user>:<pat>@github.com/...). Keep it out of source control; prefer environment variables or user secrets in real deployments.

Any config key can be supplied as an environment variable using the __ separator, e.g.:

export Bot__ReposRoot=/data/repos
export Bot__Projects__0__ProjectId=00000000-0000-0000-0000-000000000000
export Bot__Projects__0__Slug=my-game
export Bot__Projects__0__RemoteUrl="https://user:pat@github.com/you/my-game-loc.git"
export Bot__Projects__0__Branch=main
dotnet run --project Backend

Run the backend in Docker

The Backend/Dockerfile builds only Common + Backend (no MAUI workload needed), installs git in the runtime image, listens on port 8080, and defaults Bot__ReposRoot to /data/repos — mount a persistent volume there so the per-project clones survive restarts.

Build (from the repo root, so the Docker context includes both Common/ and Backend/):

docker build -f Backend/Dockerfile -t deusald-localizer-backend .

Run:

docker run -d --name localizer-backend \
  -p 8080:8080 \
  -v localizer-repos:/data/repos \
  -e Bot__CommitterName="Deusald Localizer Bot" \
  -e Bot__CommitterEmail="bot@localizer" \
  -e Bot__Projects__0__ProjectId="00000000-0000-0000-0000-000000000000" \
  -e Bot__Projects__0__Slug="my-game" \
  -e Bot__Projects__0__RemoteUrl="https://user:pat@github.com/you/my-game-loc.git" \
  -e Bot__Projects__0__Branch="main" \
  deusald-localizer-backend

Or use the bundled docker-compose.yml (repo root). It builds from Backend/Dockerfile, publishes on port 8080, persists clones in the bot-repos named volume, and reads project config + GitHub credentials from a .env file. Copy the template and fill it in:

cp .env.example .env
#   PROJECT_0_ID=00000000-0000-0000-0000-000000000000
#   PROJECT_0_SLUG=my-game
#   PROJECT_0_REMOTE_URL=https://<github-user>:<github-pat>@github.com/<owner>/<repo>.git
#   PROJECT_0_BRANCH=main

docker compose up -d --build

The compose file runs with ASPNETCORE_ENVIRONMENT=Production and restart: unless-stopped. The target deployment is a DigitalOcean Droplet with the bot-repos volume mounted at the repos root so clones survive restarts and redeploys.

Configuration reference

All settings live under the top-level Bot section.

Key Default Description
Bot:ReposRoot repos (Docker: /data/repos) Root directory holding one working-tree clone per project.
Bot:CommitterName Deusald Localizer Bot Git committer name on every bot commit (the author is the member).
Bot:CommitterEmail bot@localizer Git committer email.
Bot:Projects[] [] The managed projects (below).
Bot:Projects[].ProjectId — GUID identifying the project (matches the client's project id).
Bot:Projects[].Slug "" Human-readable slug.
Bot:Projects[].RemoteUrl "" Git remote to clone/push. Embed the GitHub PAT here for HTTPS push.
Bot:Projects[].Branch main Branch the bot tracks.

Using translations in your game (DeusaldLocalizerCommon on NuGet)

The Common project is published to NuGet as DeusaldLocalizerCommon so external readers — for example a Unity importer — can load a project folder and read translations with the same domain models the editor uses. It multi-targets netstandard2.1;net10.0 (so it works in Unity), and publishes automatically from .github/workflows/deploy-nuget.yml on a version tag.

For games that just want the strings baked in, prefer the editor's C# script export: it emits a self-contained file with a nested static-class tree of key GUIDs and a Get(language, keyId, values) SmartFormat lookup — no runtime dependency required.

Building from source

# Build the whole solution (needs the MAUI + WASM workloads)
dotnet build DeusaldLocalizer.sln

# Build just the shared library (fast — no MAUI/WASM workload needed)
dotnet build Common/Common.csproj

# Run the desktop app (Windows)
dotnet build App/App.csproj -t:Run -f net10.0-windows10.0.19041.0

# Run the web app (dev server)
dotnet run --project WebApp

# Run the backend API
dotnet run --project Backend

There are no test projects, so there is no test command.

When contributing to Common, remember it is pinned to C# 9 with implicit usings disabled — files there need explicit using directives and cannot use newer C# syntax.

License

Released under the MIT License — © 2026 Deusald.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 is compatible.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.10 120 7/16/2026
1.8.9 109 7/14/2026
1.7.8 113 7/14/2026
1.6.7 114 7/12/2026
1.5.6 108 7/11/2026
1.4.5 126 7/10/2026