Locales

How user-visible strings are stored, generated, and used across the project.

Summary

Roject uses a file-based locale system. Every user-visible string lives as a plain file under locales/en/. On server start the generator walks that tree and produces typed TypeScript classes into src/locales/generated/. In code, strings are fetched at runtime through LocaleManager using the generated path constants — for example LocaleManager.$.get( Locales.FileTree.renameButton_txt ).

Rule: whenever you add a new user-visible string, add a locale file first, regenerate, then reference the constant — never hardcode the string in TypeScript directly.

Locale Source Files

Location

All locale source files live under locales/en/. The en directory is the English source of truth. One file = one string. The directory hierarchy is free-form — organise by feature area.

Supported formats

.txt — raw UTF-8 text, used for plain labels and messages.
.html — HTML markup, used when the string contains tags.
.json — flexible format; must contain at least { "value": "…" }. Extra fields are ignored by the loader but can carry metadata for tools.

Companion context files (.md)

Any locale file may have a sibling whose name is the full filename plus .md. For example rename-button.txt may have a companion rename-button.txt.md. These files are human-readable notes for translators and contributors. The generator and the app ignore them completely — they are never served or compiled.

Code Generation

When it runs

The generator (server/localeGenerator.ts) runs automatically every time the server starts via npm start. After adding, renaming, or removing any locale file, restart the server to regenerate src/locales/generated/. The generated files are checked into the repo so the frontend TypeScript compiler can see them without a running server.

Output structure

The generator mirrors the locales/en/ directory tree into src/locales/generated/. Each directory produces exactly one .ts file. The root directory produces src/locales/generated/Locales.ts. Never edit generated files manually — changes will be overwritten on the next server start.

locales/en/                          →  src/locales/generated/
  greeting.txt                        →    Locales.ts  (member: greeting_txt)
  commands/                           →    commands/
    file-tree-commands/               →      Commands.ts  (member: FileTreeCommands)
      add-file.txt                    →      file-tree-commands/
                                      →        FileTreeCommands.ts  (member: addFile_txt)
Naming — class names (directories)

Directory names are converted to PascalCase: dashes and underscores split words, each word is capitalised, the first letter is always uppercase regardless of the original name.

commands              →  Commands
file-tree-commands    →  FileTreeCommands
my_panel              →  MyPanel
Naming — member names (files)

File names keep the case of their first character. Dashes convert subsequent words to camelCase. The file extension is appended after an underscore, with dots in the extension also replaced by underscores.

add-file.txt          →  addFile_txt
greeting.txt          →  greeting_txt
MyFile.html           →  MyFile_html
config.min.json       →  config_min_json
Generated class shape

Each generated class has static readonly members. File members hold the locale path string (relative to locales/en/, extension included). Subdirectory members import and re-expose the child class so the entire tree is reachable from Locales.

// src/locales/generated/commands/file-tree-commands/FileTreeCommands.ts
// Auto-generated — do not edit manually.

export class FileTreeCommands
{
  static readonly addFile_txt = 'commands/file-tree-commands/add-file.txt';
}

// src/locales/generated/commands/Commands.ts
// Auto-generated — do not edit manually.
import { FileTreeCommands } from './file-tree-commands/FileTreeCommands.js';

export class Commands
{
  static readonly FileTreeCommands = FileTreeCommands;
}

// src/locales/generated/Locales.ts
// Auto-generated — do not edit manually.
import { Commands } from './commands/Commands.js';

export class Locales
{
  static readonly Commands = Commands;
}

LocaleManager API

Singleton access

LocaleManager.$ is a static getter that returns the single instance. No parentheses needed. Import from src/locales/LocaleManager.ts (compiled to public/locales/LocaleManager.js).

Fetching a string

LocaleManager.$.get( path ) is async and returns Promise<string>. Pass a generated path constant — never a raw string literal. The result is cached by key after the first fetch; subsequent calls return immediately from cache. If the server returns an error the method falls back to returning the path string itself so the UI never breaks silently.

const label = await LocaleManager.$.get( Locales.Commands.FileTreeCommands.addFile_txt );
// → "Add file"
Format handling

The manager detects the format from the file extension in the path. .json files are parsed and data.value is returned. All other extensions are returned as raw text.

Changing the active locale

The current locale defaults to 'en'. Switch with LocaleManager.$.setLocale( 'de' ). After switching, call LocaleManager.$.invalidateCache() to clear cached strings so the next get call fetches from the new locale.

How To — Add and Use a Locale String

Step 1 — Create the locale file

Choose a location under locales/en/ that matches the feature. Create a .txt file whose name describes the string.

locales/en/file-tree/rename-button.txt
content: Rename

Optionally add a companion context file:

locales/en/file-tree/rename-button.txt.md
content: Label on the Rename entry in the file tree right-click context menu.

Step 2 — Restart the server to regenerate

The generator runs on every npm start and creates or updates the generated TypeScript files. After this step the following files exist:

npm start

src/locales/generated/file-tree/FileTree.ts    ← created
src/locales/generated/Locales.ts               ← updated (FileTree member added)

Step 3 — Build

Compile the generated TypeScript so the browser can import the new constants.

npm run build

Step 4 — Use in TypeScript

Import LocaleManager and Locales, then await the string. Always import Locales (not individual child classes) so the full path is visible at the call site and easy to trace back to the source file.

import { LocaleManager } from '../../locales/LocaleManager.js';
import { Locales } from '../../locales/generated/Locales.js';

const label = await LocaleManager.$.get( Locales.FileTree.renameButton_txt );
button.textContent = label;