Audience: translators / contributors. Sucrose.Localizer is a standalone console application that contributors use to translate the Sucrose app. It is not part of the main application — it is a tooling project that converts translation data between three formats: the runtime XAML resources, an intermediate CSV working format, and POEditor CSV (for the POEditor translation service). This page documents the tool's nine-option menu, the conversion pipeline, the file formats and their separator characters, and how to add a new language. For the current set of translated languages, see Localization Coverage.
Sucrose.Localizer lives in src/Localizer/Sucrose.Localizer/ and has its own solution, src/Sucrose.Localizer.slnx (one of the project's three solutions — see Repository Layout). Project facts:
net10.0-windows but with UseWPF=false; OutputType=Exe; defines the LOCALIZER preprocessor symbol.Skylark, Skylark.Standard, CsvHelper.…/Localizer/{x86|x64|ARM64}.It is purely a contributor tool: it reads and writes files on disk and is driven by an interactive numbered menu.
| Format | Where it lives | Shape |
|---|---|---|
| XAML (runtime) | src/Library/Sucrose.Resources/Locales/Locale.<CODE>.xaml and grouped files like Portal.<area>.<CODE>.xaml |
ResourceDictionary of <system:String x:Key="…">value</system:String> entries; the app loads one per language at runtime. |
| CSV (working) | .localize/ (repo root) |
Columns Hash,File,Key,Value — used for diffing/checking and as the working format. |
| POEditor CSV | .localize/POEditor/ |
Columns Term,Translate,Context,Reference,Comment — the import/export format for the POEditor service. |
Do not conflate the two CSV schemas: the working CSV is
Hash,File,Key,Value; the POEditor CSV isTerm,Translate,Context,Reference,Comment.
The entry point is App.cs → Main, which sets the console encoding to UTF-8, sets the culture, and calls Helper.Program.Start(). Helper/Program.cs presents the interactive menu. Each menu option prompts for directory paths on stdin via Console.ReadLine.
Because the tool is interactive-only (there are no command-line flags), running it means launching the built Sucrose.Localizer.exe and answering its prompts. Build it from its own solution:
dotnet build src/Sucrose.Localizer.slnx -c Release -p:PlatformTarget=x64
Helper/Program.cs exposes nine operations:
| # | Operation | Helper call | Notes |
|---|---|---|---|
| 1 | Convert XAML → CSV | XamlToCsv.Convert(xamlDir, csvDir) |
Extracts system:String entries plus merged-dictionary blocks. |
| 2 | Convert CSV → XAML | CsvToXaml.Convert(csvDir, xamlDir) |
Rebuilds the ResourceDictionary XAML files. |
| 3 | Convert CSV → POEditor | CsvToPoe.Convert(csvDir, poeDir) |
|
| 4 | Convert POEditor → CSV | PoeToCsv.Convert(poeDir, csvDir) |
|
| 5 | Check CSV files against each other | Against.CheckCsv(csvDir) |
Row-by-row consistency across all two-letter CSVs. |
| 6 | Check POEditor files against each other | Against.CheckPoe(poeDir) |
|
| 7 | CSV reindexer from a reference CSV | Against.ReindexCsv(csvDir, lang) |
Re-orders all CSVs to match a reference language's line order by Hash. |
| 8 | Alphabetic key indexer for a CSV | Against.AlphabeticIndexer(csvDir, lang) |
Sorts keys (shortest-first, then natural alpha-numeric) per file group; excludes Base64/empty keys. |
| 9 | Create a new language file from CSVs | Creating.Create(csvDir, srcLang, outLang, outLangName) |
Clones a source CSV to a new language and registers it in Locale.csv. |
The typical contributor loop is: export the current strings to POEditor (1 → 3), translate in POEditor, import back (4 → 2), then check consistency (5, 6) and optionally reindex (7) before committing.
XamlToCsv.cs)*.xaml; the language code is the last dot-segment of the filename (e.g. Portal.Enum.TR.xaml → TR).<ResourceDictionary.MergedDictionaries>…</…>) are captured whole, Base64-encoded (CryptologyExtension.TextToBase), and stored under the key Base64.system:String becomes a CSV row Hash,File,Key,Value; & is escaped to &; blank separator rows preserve the XAML spacing.CsvToXaml.cs)ResourceDictionary with xmlns:system="clr-namespace:System;assembly=mscorlib".Base64 keys are decoded back to raw XAML (BaseToText); empty key+value rows become blank lines.system:String entries, the header is simplified (drops the system namespace, prepends a BOM).CsvToPoe.cs)Term,Translate,Context,Reference,Comment.PASS⦙n; Base64 keys become Base64⦙n.Don't Touch and the comment "Please don't touch the translation in this line." The Reference column carries the File.PoeToCsv.cs)PASS⦙ terms become empty, ⦙-marked translations are cleared, and the Hash is recomputed.Internal separator characters used by the helpers: ⁞ (entry split) and ⦙ (special-marker split). These are literal Unicode characters baked into the file format — preserve them exactly.
The Hash column is the cross-language join key that ties the same string together across all languages. It is computed as the SHA256 (TextToSHA256) of "{file-without-langcode}.{key-or-counter}". Because reindexing and checking (menu options 5–7) depend on the Hash being stable, do not hand-edit it — let the tool compute it.
| Path | Contents |
|---|---|
src/Library/Sucrose.Resources/Locales/Locale.<CODE>.xaml |
Runtime XAML resource per language. |
src/Library/Sucrose.Resources/Locales/Locale.xaml |
The runtime locale registry (code → display name + version), e.g. Locale.EN = "English (v1.4)". |
.localize/ (repo root) |
Working/translation CSVs (Portal.csv, Launcher.csv, Discord.csv, and per-language <CODE>.csv). |
.localize/POEditor/ |
POEditor-format CSVs. |
Localized XAML resources under Sucrose.Resources/Locales/ are grouped by app area — e.g. Locales/Portal/… (Portal.<area>.<CODE>.xaml, including Portal.Enum.<CODE>.xaml, which localizes enum display names such as StoreServerType and ReportThemeType (the Category names live in a separate Portal.Category.<CODE>.xaml group)), plus Launcher and Discord groups. The runtime looks strings up with segment keys via Sucrose.Resources.Extension.Resources.GetValue("Portal","StoreCard","MenuInstall")-style calls.
Use menu option 9 (Creating.Create(csvDir, srcLang, outLang, outLangName)):
<src>.csv → <out>.csv.File column's language tokens (.SRC. → .OUT.) — including inside the Base64-encoded merged-dictionary blocks.Locale.csv of the form:Locale.xaml, Locale.<OUT>, <Language Name> (v0.1)
and re-sorts the registry by Key.
After creating the language CSV, translate it (export to POEditor with option 3, translate, import back with option 4), then convert CSV → XAML (option 2) to produce the runtime Locale.<CODE>.xaml that the app loads.
The locale registry maps each code to a human display name and version, e.g. Locale.EN = "English (v1.4)". The version is a maturity indicator for the translation.
Hash column must stay stable — it is the cross-language join key; reindexing/checking depends on it.