273 lines
10 KiB
HTML
273 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Locales — Roject</title>
|
|
<link rel="stylesheet" href="../../_assets_/styles.css">
|
|
<link rel="stylesheet" href="../../_assets_/nav.css">
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
|
|
<header>
|
|
<h1>Locales</h1>
|
|
<p class="subtitle">How user-visible strings are stored, generated, and used across the project.</p>
|
|
</header>
|
|
|
|
<section>
|
|
<h2>Summary</h2>
|
|
|
|
<div class="card">
|
|
<p>
|
|
Roject uses a file-based locale system. Every user-visible string lives as a
|
|
plain file under <code>locales/en/</code>. On server start the generator walks
|
|
that tree and produces typed TypeScript classes into
|
|
<code>src/locales/generated/</code>. In code, strings are fetched at runtime
|
|
through <code>LocaleManager</code> using the generated path constants — for
|
|
example <code>LocaleManager.$.get( Locales.FileTree.renameButton_txt )</code>.
|
|
</p>
|
|
<p style="margin-top:0.75rem">
|
|
<strong>Rule:</strong> 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.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Locale Source Files</h2>
|
|
|
|
<div class="decision">
|
|
<strong>Location</strong>
|
|
<p>
|
|
All locale source files live under <code>locales/en/</code>. The <code>en</code>
|
|
directory is the English source of truth. One file = one string. The directory
|
|
hierarchy is free-form — organise by feature area.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Supported formats</strong>
|
|
<p>
|
|
<code>.txt</code> — raw UTF-8 text, used for plain labels and messages.<br>
|
|
<code>.html</code> — HTML markup, used when the string contains tags.<br>
|
|
<code>.json</code> — flexible format; must contain at least <code>{ "value": "…" }</code>.
|
|
Extra fields are ignored by the loader but can carry metadata for tools.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Companion context files (<code>.md</code>)</strong>
|
|
<p>
|
|
Any locale file may have a sibling whose name is the full filename plus
|
|
<code>.md</code>. For example <code>rename-button.txt</code> may have a companion
|
|
<code>rename-button.txt.md</code>. These files are human-readable notes for
|
|
translators and contributors. The generator and the app ignore them completely —
|
|
they are never served or compiled.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>Code Generation</h2>
|
|
|
|
<div class="decision">
|
|
<strong>When it runs</strong>
|
|
<p>
|
|
The generator (<code>server/localeGenerator.ts</code>) runs automatically every
|
|
time the server starts via <code>npm start</code>. After adding, renaming, or
|
|
removing any locale file, restart the server to regenerate
|
|
<code>src/locales/generated/</code>. The generated files are checked into the
|
|
repo so the frontend TypeScript compiler can see them without a running server.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Output structure</strong>
|
|
<p>
|
|
The generator mirrors the <code>locales/en/</code> directory tree into
|
|
<code>src/locales/generated/</code>. Each directory produces exactly one
|
|
<code>.ts</code> file. The root directory produces
|
|
<code>src/locales/generated/Locales.ts</code>. Never edit generated files
|
|
manually — changes will be overwritten on the next server start.
|
|
</p>
|
|
<pre><code>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)</code></pre>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Naming — class names (directories)</strong>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<pre><code>commands → Commands
|
|
file-tree-commands → FileTreeCommands
|
|
my_panel → MyPanel</code></pre>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Naming — member names (files)</strong>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<pre><code>add-file.txt → addFile_txt
|
|
greeting.txt → greeting_txt
|
|
MyFile.html → MyFile_html
|
|
config.min.json → config_min_json</code></pre>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Generated class shape</strong>
|
|
<p>
|
|
Each generated class has <code>static readonly</code> members. File members hold
|
|
the locale path string (relative to <code>locales/en/</code>, extension included).
|
|
Subdirectory members import and re-expose the child class so the entire tree is
|
|
reachable from <code>Locales</code>.
|
|
</p>
|
|
<pre><code>// 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;
|
|
}</code></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>LocaleManager API</h2>
|
|
|
|
<div class="decision">
|
|
<strong>Singleton access</strong>
|
|
<p>
|
|
<code>LocaleManager.$</code> is a static getter that returns the single instance.
|
|
No parentheses needed. Import from
|
|
<code>src/locales/LocaleManager.ts</code> (compiled to
|
|
<code>public/locales/LocaleManager.js</code>).
|
|
</p>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Fetching a string</strong>
|
|
<p>
|
|
<code>LocaleManager.$.get( path )</code> is async and returns
|
|
<code>Promise<string></code>. 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.
|
|
</p>
|
|
<pre><code>const label = await LocaleManager.$.get( Locales.Commands.FileTreeCommands.addFile_txt );
|
|
// → "Add file"</code></pre>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Format handling</strong>
|
|
<p>
|
|
The manager detects the format from the file extension in the path.
|
|
<code>.json</code> files are parsed and <code>data.value</code> is returned.
|
|
All other extensions are returned as raw text.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="decision">
|
|
<strong>Changing the active locale</strong>
|
|
<p>
|
|
The current locale defaults to <code>'en'</code>. Switch with
|
|
<code>LocaleManager.$.setLocale( 'de' )</code>. After switching, call
|
|
<code>LocaleManager.$.invalidateCache()</code> to clear cached strings so
|
|
the next <code>get</code> call fetches from the new locale.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h2>How To — Add and Use a Locale String</h2>
|
|
|
|
<div class="card">
|
|
<h3>Step 1 — Create the locale file</h3>
|
|
<p>
|
|
Choose a location under <code>locales/en/</code> that matches the feature.
|
|
Create a <code>.txt</code> file whose name describes the string.
|
|
</p>
|
|
<pre><code>locales/en/file-tree/rename-button.txt
|
|
content: Rename</code></pre>
|
|
<p style="margin-top:0.75rem">
|
|
Optionally add a companion context file:
|
|
</p>
|
|
<pre><code>locales/en/file-tree/rename-button.txt.md
|
|
content: Label on the Rename entry in the file tree right-click context menu.</code></pre>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Step 2 — Restart the server to regenerate</h3>
|
|
<p>
|
|
The generator runs on every <code>npm start</code> and creates or updates the
|
|
generated TypeScript files. After this step the following files exist:
|
|
</p>
|
|
<pre><code>npm start
|
|
|
|
src/locales/generated/file-tree/FileTree.ts ← created
|
|
src/locales/generated/Locales.ts ← updated (FileTree member added)</code></pre>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Step 3 — Build</h3>
|
|
<p>
|
|
Compile the generated TypeScript so the browser can import the new constants.
|
|
</p>
|
|
<pre><code>npm run build</code></pre>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Step 4 — Use in TypeScript</h3>
|
|
<p>
|
|
Import <code>LocaleManager</code> and <code>Locales</code>, then await the string.
|
|
Always import <code>Locales</code> (not individual child classes) so the full
|
|
path is visible at the call site and easy to trace back to the source file.
|
|
</p>
|
|
<pre><code>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;</code></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<footer>
|
|
Roject — locales
|
|
</footer>
|
|
|
|
</div>
|
|
<script>var NAV_ROOT = '../../';</script>
|
|
<script src="../../_assets_/nav-data.js"></script>
|
|
<script src="../../_assets_/nav.js"></script>
|
|
</body>
|
|
</html>
|