Sort en.json keys alphabetically at every level (#4232)

**Add approved & assigned issue number here:**

N/A — maintainer change, groundwork for #4231.

## Description:

One-time recursive key sort of `resources/lang/en.json` (`jq -S` +
prettier), with a test (`tests/EnJsonSorted.test.ts`) that enforces the
invariant from now on.

Why: sorted keys make the file deterministic, give translation PRs
stable insertion points instead of everyone appending at section ends,
and let the map-generator (#4231) rewrite the en.json map section with a
plain JSON unmarshal/marshal round-trip — Go's `encoding/json` sorts
object keys on marshal, so under this invariant a full-file rewrite is a
no-op for everything it doesn't change.

Crowdin matches translation entries by key path, not file position, so
existing translations are unaffected. Only en.json is touched and
checked; other language files remain Crowdin-managed (they may get
reordered by Crowdin's next export, which is cosmetic).

The diff is 100% line moves — no key or value changes (JSON-equal before
and after).

## Please complete the following:

- [x] I have added screenshots for all UI updates (no UI changes)
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evanpelle

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Evan
2026-06-11 20:37:09 -07:00
committed by GitHub
parent b1e9955af3
commit be177f445a
2 changed files with 1402 additions and 1362 deletions
+1362 -1362
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
import fs from "fs";
import path from "path";
const EN_JSON = path.join(__dirname, "..", "resources", "lang", "en.json");
/** Collect every object whose keys are out of order, as dotted paths. */
function findUnsortedObjects(value: unknown, objectPath: string): string[] {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return [];
}
const errors: string[] = [];
const keys = Object.keys(value);
for (let i = 1; i < keys.length; i++) {
if (keys[i - 1] > keys[i]) {
errors.push(
`${objectPath}: "${keys[i]}" should come before "${keys[i - 1]}"`,
);
}
}
for (const [key, child] of Object.entries(value)) {
errors.push(...findUnsortedObjects(child, `${objectPath}.${key}`));
}
return errors;
}
// en.json keys are kept alphabetically sorted at every level. This keeps the
// file deterministic, gives translation PRs stable insertion points, and lets
// the map-generator rewrite it with a plain JSON round-trip (Go's
// encoding/json sorts object keys on marshal).
// Other language files are managed by Crowdin and are not checked.
test("en.json keys are alphabetically sorted at every level", () => {
const content = JSON.parse(fs.readFileSync(EN_JSON, "utf8"));
const errors = findUnsortedObjects(content, "en.json");
if (errors.length > 0) {
throw new Error(
"en.json keys are out of order (sort with `jq -S . resources/lang/en.json` + prettier):\n" +
errors.join("\n"),
);
}
});