Skip to content

Localization

Valkerran edited this page Sep 29, 2026 · 2 revisions

Localization

PCEdit ships in 15 languages. English (US) is written by hand; the other 14 are generated from per-locale JSON.

The pieces

File / type Role
PCEdit.App.Core/Resources/Strings.resx Source of truth — keys, their order, and the canonical en-US text
PCEdit.App.Core/Resources/Strings.<culture>.resx (×14) Generated satellites — never hand-edit
tools/i18n/<culture>.json (×14) The translations themselves; one flat { "Key": "text" } map
tools/i18n/gen_satellites.py JSON → satellite .resx
tools/i18n/gen_metainfo.py Catalog → the Linux AppStream metainfo (store listing)
Localization/LocKeys.cs Constants for every key referenced from C#
Localization/ILocalizer.cs, Localizer.cs this[key], Format(key, args), SetCulture, CultureChanged
Localization/LocaleOption.cs One selectable language (culture name, native name, English name)

Cultures: en-GB, fr, de, es-ES, zh-Hans, ru, pl, pt-PT, ko, ja, pt-BR, it, zh-Hant, tr — plus neutral en-US.

Adding or changing a UI string

  1. Add / change the key in Strings.resx.

  2. Add / change the same key in every tools/i18n/<culture>.json — all 14.

  3. Regenerate the satellites:

    python tools/i18n/gen_satellites.py
  4. If the key feeds the Linux store listing (Shell_Title, About_Tagline, OpenFile_Intro, Disclaimer_Body), regenerate the AppStream metadata too:

    python tools/i18n/gen_metainfo.py
  5. Run the tests:

    dotnet test PCEdit.App.Core.Tests/PCEdit.App.Core.Tests.csproj
  6. Commit the generated files. CI regenerates them and fails the build on any drift.

LocalizationCatalogTests enforce that every satellite has exactly the neutral key set, that no value is empty, that {0} / {1} placeholders match the neutral string, and that every LocKeys constant exists.

Using a string

From C# — inject ILocalizer and use a LocKeys constant:

_localizer[LocKeys.Save_Ok];
_localizer.Format(LocKeys.Teleport_Done, playerName, planetId, position);

From XAML — the markup extensions:

<Button Content="{m:Loc Common_Save}" />

<!-- one argument, bound from the data context -->
<TextBlock Text="{m:LocFormat Key=Inv_Fallback, Path=InventoryId}" />

<!-- two arguments: {0} from Path, {1} from Path2 (v1.5.0) -->
<Button AutomationProperties.Name="{m:LocFormat Key=Inventories_MoveA11y, Path=DisplayName, Path2=WorldObjectId}" />

<!-- FallbackKey supplies the text when the first argument is null or empty -->

LocFormat only takes named properties (Key, Path, Path2, FallbackKey); a positional form does not exist.

Important

The markup extensions bind ILocalizer.Current through a converter, not the string indexer. Avalonia's reflection binding does not re-read an [key] path on an Item[] notification, so a binding written the obvious way would go stale the moment the user switches language.

The same rule applies in ViewModels: a status message is stored as a key plus its arguments and re-formatted on CultureChanged, never as a finished string.

How the language is chosen

On first run the OS language is used if it is one of the 15; otherwise en-US. The user's choice from the nav-pane picker is stored via ILanguageStore (see Languages & Settings) and applied at startup by LanguageStartup.

SetCulture raises CultureChanged, and the whole UI re-reads — no restart.

Adding a language

  1. Add the culture to CULTURES in tools/i18n/gen_satellites.py.
  2. Add tools/i18n/<culture>.json with every key translated.
  3. Add a LocaleOption for the picker (culture name, native name, English name).
  4. Add the culture to <SatelliteResourceLanguages> in PCEdit.Desktop.csproj — a satellite not listed there is not published, and the language silently falls back to English.
  5. Regenerate, test, commit the generated files.

Consider whether the language needs fonts the app does not carry — CJK relies on the OS fallback, which is why the Linux notes ask for a Noto CJK package.

Translation quality

Everything except English started as a machine-translation pass. Resources/TRANSLATIONS.md tracks the review status per locale and lists the batches of keys added since. A native-speaker review is very welcome: correct tools/i18n/<culture>.json, regenerate, and mark the locale reviewed.

The disclaimer

Its wording has one source — DISCLAIMER.md at the repo root — mirrored by the Disclaimer_Body key in the catalog and by the AppStream <description>. Change one, change all three.

Clone this wiki locally