Archived
deploy.
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Internationalization | HiAPI-C# 2025 </title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="title" content="Internationalization | HiAPI-C# 2025 ">
|
||||
|
||||
|
||||
<link rel="icon" href="../../img/HiAPI.favicon.ico">
|
||||
<link rel="stylesheet" href="../../public/docfx.min.css">
|
||||
<link rel="stylesheet" href="../../public/main.css">
|
||||
<meta name="docfx:navrel" content="../../toc.html">
|
||||
<meta name="docfx:tocrel" content="toc.html">
|
||||
|
||||
<meta name="docfx:rel" content="../../">
|
||||
|
||||
|
||||
|
||||
<meta name="loc:inThisArticle" content="In this article">
|
||||
<meta name="loc:searchResultsCount" content="{count} results for "{query}"">
|
||||
<meta name="loc:searchNoResults" content="No results for "{query}"">
|
||||
<meta name="loc:tocFilter" content="Filter by title">
|
||||
<meta name="loc:nextArticle" content="Next">
|
||||
<meta name="loc:prevArticle" content="Previous">
|
||||
<meta name="loc:themeLight" content="Light">
|
||||
<meta name="loc:themeDark" content="Dark">
|
||||
<meta name="loc:themeAuto" content="Auto">
|
||||
<meta name="loc:changeTheme" content="Change theme">
|
||||
<meta name="loc:copy" content="Copy">
|
||||
<meta name="loc:downloadPdf" content="Download PDF">
|
||||
|
||||
<script type="module" src="./../../public/docfx.min.js"></script>
|
||||
|
||||
<script>
|
||||
const theme = localStorage.getItem('theme') || 'auto'
|
||||
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
|
||||
<header class="bg-body border-bottom">
|
||||
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
|
||||
<div class="container-xxl flex-nowrap">
|
||||
<a class="navbar-brand" href="../../index.html">
|
||||
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
|
||||
|
||||
</a>
|
||||
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<i class="bi bi-three-dots"></i>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navpanel">
|
||||
<div id="navbar">
|
||||
<form class="search" role="search" id="search">
|
||||
<i class="bi bi-search"></i>
|
||||
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="container-xxl">
|
||||
<div class="toc-offcanvas">
|
||||
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<nav class="toc" id="toc"></nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="actionbar">
|
||||
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
|
||||
<nav id="breadcrumb"></nav>
|
||||
</div>
|
||||
|
||||
<article data-uid="Anatomy-I18n">
|
||||
<h1 id="internationalization">Internationalization</h1>
|
||||
|
||||
<p>The web client ships three locales — <code>en</code>, <code>zh-Hant</code> and <code>zh-Hans</code> — over one message shape, and it
|
||||
owns no route: the mechanism is a vue-i18n instance created in the <code>i18n</code> module, a boot file that
|
||||
picks the locale before the application mounts, a single switch function every locale change goes
|
||||
through, and two Node scripts that gate the production build. English is not one locale among three;
|
||||
it is the schema the other two are written against and the text every unlocalized path falls back to.
|
||||
The user-facing gesture that triggers a switch is documented under
|
||||
<a class="xref" href="../shell/language-selection-submenu.html">Language Selection SubMenu</a>, and the vocabulary the
|
||||
translations are held to under <a class="xref" href="../conventions/translation-remarks.html">Translation Remarks</a>.</p>
|
||||
<h2 id="the-three-bundles">The Three Bundles</h2>
|
||||
<p>Each locale is a folder under the i18n directory holding one file per namespace plus an index file
|
||||
that re-exports them as a single object. The three folders carry identical file lists — the same
|
||||
twenty-three namespaces, from <code>apiErrors</code> and <code>auth</code> through <code>tree</code> and <code>widgets</code> — because a
|
||||
namespace's keys are added to all three locales in the same change. Keys are named
|
||||
<code>namespace.area.element</code> in lowerCamelCase and are split by the UI region they serve rather than by
|
||||
the source file that reads them.</p>
|
||||
<p>English is the schema, mechanically and not by convention. <code>wwwroot-src/src/i18n/schema.ts</code> exports
|
||||
<code>MessageSchema = typeof en</code>, and each Chinese locale's index file annotates its own message object
|
||||
with that type, so a key missing from a Chinese namespace is a type error at the annotation rather
|
||||
than a silent English fallback at runtime. <code>schema.d.ts</code> augments vue-i18n's <code>DefineLocaleMessage</code> with the
|
||||
same shape, which registers it as the global message schema for call sites. Type checking is not part
|
||||
of the production build, however: <code>wwwroot-src/package.json</code> runs <code>vue-tsc</code> under a separate <code>lint</code>
|
||||
script, and <code>build</code> runs the two i18n scripts described below instead.</p>
|
||||
<p>The instance is created with <code>legacy: false</code>, an initial <code>locale</code> of <code>en</code> and <code>fallbackLocale: 'en'</code>.
|
||||
The resolution chain is therefore current locale → English → the key string itself, so a key missing
|
||||
from a Chinese bundle renders English with no runtime signal, and a key missing everywhere renders as
|
||||
its own dotted name.</p>
|
||||
<p>Quasar's own component texts are a parallel bundle with different code names, mapped in the same
|
||||
module: <code>en</code> to Quasar's <code>en-US</code> pack, <code>zh-Hant</code> to <code>zh-TW</code>, <code>zh-Hans</code> to <code>zh-CN</code>. All three packs are
|
||||
static imports rather than a dynamic import built from the locale code, because a template-literal
|
||||
import on a bare package specifier is not statically analysable by the bundler.</p>
|
||||
<h2 id="applying-a-locale">Applying a Locale</h2>
|
||||
<p><code>applyLocale(code)</code> in the i18n module is the only place a locale change happens. It first maps its
|
||||
argument through <code>normalizeLocale</code>, falling back to <code>en</code>, and then does six things in one call:</p>
|
||||
<ul>
|
||||
<li>sets the vue-i18n instance's <code>locale</code> ref;</li>
|
||||
<li>calls Quasar's <code>Lang.set()</code> with the mapped language pack;</li>
|
||||
<li>writes <code>document.documentElement.lang</code>, which is what selects the CJK glyph variant;</li>
|
||||
<li>rebuilds the exported <code>collator</code>, an <code>Intl.Collator</code> for the new locale with base sensitivity and
|
||||
numeric ordering;</li>
|
||||
<li>writes the locale into <code>localStorage</code> under <code>hinc.lang</code>, inside a <code>try</code>/<code>catch</code> because storage can
|
||||
be unavailable;</li>
|
||||
<li>calls the re-title hook the router registered, so a browser tab parked on a page without navigating
|
||||
still gets its title in the new language.</li>
|
||||
</ul>
|
||||
<p><code>normalizeLocale</code> maps any language tag — the server's, the browser's, or a stored one — onto a
|
||||
supported locale or <code>null</code>. An exact match wins; otherwise a tag beginning <code>en</code> becomes <code>en</code>, and a
|
||||
tag beginning <code>zh</code> becomes <code>zh-Hant</code> when it carries the <code>hant</code> script subtag or a <code>TW</code>, <code>HK</code> or <code>MO</code>
|
||||
region, and <code>zh-Hans</code> otherwise. Anything else yields <code>null</code>. <code>applyLocale</code> and the boot file's
|
||||
browser-language fallback each turn that <code>null</code> into <code>en</code>; the boot file's read of the stored value
|
||||
instead treats it as a cache miss and asks the server, so an unsupported stored code takes the
|
||||
cold-start path rather than pinning English.</p>
|
||||
<p>Only two modules call <code>applyLocale</code>: the boot file, and the app-state store's language action.
|
||||
The store updates its own <code>languageCode</code> optimistically, POSTs the new value, adopts the <code>current</code> and
|
||||
<code>available</code> the server echoes back, and only then applies the locale — so the UI text flips after the
|
||||
server has accepted the value, and a failed POST rolls the store reference back without any visible
|
||||
language change.</p>
|
||||
<h2 id="boot">Boot</h2>
|
||||
<p><code>wwwroot-src/quasar.config.ts</code> lists three boot files in the order <code>auth</code>, <code>i18n</code>, <code>routine-toast</code>.
|
||||
The i18n file is placed after <code>auth</code> because <code>auth</code> patches the global <code>fetch</code> for the 401 login gate,
|
||||
and the language request the i18n file makes must go through that patch on builds where login is
|
||||
enabled.</p>
|
||||
<p>The boot file installs the plugin and then reads the <code>hinc.lang</code> value:</p>
|
||||
<ul>
|
||||
<li><strong>Cache hit.</strong> The cached locale is applied immediately, so the first painted frame is already
|
||||
translated, and a reconcile against the server is fired without being awaited. The server value is
|
||||
the source of truth, so a disagreement — the language was changed from another browser — repaints
|
||||
shortly after mount.</li>
|
||||
<li><strong>Cache miss.</strong> The file returns a promise racing the language request against a four-second
|
||||
timeout, and mount waits on it. A dead backend rejects quickly; a hung one is capped by the timeout
|
||||
rather than blanking the application. Either failure falls back to the normalized browser language,
|
||||
and then to English.</li>
|
||||
</ul>
|
||||
<p>Every locale step is individually guarded, because a boot file that throws does not degrade to
|
||||
English — the client entry logs the error and never mounts. The one unguarded step is installing the
|
||||
plugin itself, without which nothing in the application can render a translated string at all. The
|
||||
worst case the guards preserve is an application that mounts in English.</p>
|
||||
<p>The shipped <code>wwwroot-src/index.html</code> is static and carries <code>lang="en"</code>, so the very first painted
|
||||
frame always reports English on the root element; <code>applyLocale</code> corrects it in the same tick as mount.</p>
|
||||
<h2 id="what-re-renders">What Re-Renders</h2>
|
||||
<p>A locale change is a write to one reactive ref, so everything that reads a translation inside a
|
||||
render or a computed re-evaluates on its own. Five things follow it in practice:</p>
|
||||
<ul>
|
||||
<li><strong>Component text.</strong> Every <code>t()</code> call in a template or computed.</li>
|
||||
<li><strong>Quasar's built-in texts</strong>, through the language pack the same call sets.</li>
|
||||
<li><strong>The tab title.</strong> Route records carry an i18n <em>key</em> in <code>meta.title</code>; the router resolves it against
|
||||
the active locale and composes the title from it. It re-resolves both after a navigation and from
|
||||
the hook <code>applyLocale</code> calls.</li>
|
||||
<li><strong>Control-Tree node labels</strong>, described below.</li>
|
||||
<li><strong>Engine session messages</strong>, which are re-resolved because the localizing helper reads the locale
|
||||
ref, so rows built inside a computed rebuild on a switch.</li>
|
||||
</ul>
|
||||
<p>File Explorer sorting is the one thing that does <em>not</em> follow on its own. The shared <code>collator</code> is a
|
||||
live <code>let</code> binding rather than a reactive one: <code>applyLocale</code> rebuilds it for the new locale, but no
|
||||
render tracks it and <code>FileExplorer.vue</code> watches no locale, so a listing already on screen keeps its
|
||||
pre-switch order until the next folder load or sort-spec change re-runs the comparison. Because the
|
||||
binding is live, a caller must invoke <code>collator.compare(...)</code> in place; capturing the instance or
|
||||
extracting its <code>compare</code> would pin the pre-switch locale for good. <code>explorerSort.ts</code> is its only
|
||||
consumer, and no bare <code>localeCompare</code> — whose <code>undefined</code> locale argument means the <em>browser</em> locale
|
||||
— is called anywhere in the client.</p>
|
||||
<p>Numbers deliberately do not follow the locale. <code>Intl.NumberFormat</code> is used nowhere in the client, and
|
||||
the only <code>toLocale*</code> calls are two triangle counts and two timestamps, each passed the app locale
|
||||
explicitly rather than defaulting to the browser's. NC and machine-tool quantities go through no
|
||||
locale-aware number formatting at all, so a coordinate never picks up a comma decimal separator from
|
||||
a locale that uses one.</p>
|
||||
<h2 id="control-tree-node-labels">Control-Tree Node Labels</h2>
|
||||
<p>A Control-Tree node carries both a <code>label</code> and an optional <code>labelKey</code>, and <code>nodeDisplayLabel(n)</code> in
|
||||
<code>itemTypes.ts</code> returns the translation of <code>labelKey</code> — with the node's <code>labelParams</code> interpolated —
|
||||
whenever one is set, and the verbatim <code>label</code> otherwise. <code>nodeDisplayInfo</code> applies the same rule to a
|
||||
group stem's intro text through <code>infoKey</code> over <code>info</code>. The translator function is read inside the call
|
||||
rather than captured at build time, so the computed that maps nodes onto the rendered tree tracks the
|
||||
locale ref and the whole tree's role labels change on a switch.</p>
|
||||
<p>The two fields overlap on purpose. <code>label</code> is required on the node type and <code>labelKey</code> is optional,
|
||||
so a fixed role label is written both ways: the key that actually renders, and the verbatim English
|
||||
sitting beside it as the fallback <code>nodeDisplayLabel</code> returns when no key is set. <code>label</code> is also the
|
||||
only storage for text that can never be a key at all — server-composed mission titles, file-backed
|
||||
geometry paths, and engine type names. It is the first of those two jobs, not the second, that makes
|
||||
the verbatim English on node labels the largest single class in the census allowlist: fixed role
|
||||
labels the census can see but the screen never shows, classified rather than removed because the
|
||||
node type requires the field.</p>
|
||||
<p>Mission command nodes are the one family that never takes the key path. A node's label is the title
|
||||
the server composes for the command — the kind name in the request language, with the command's own
|
||||
text in brackets when there is one — and <code>Missions/MissionController.cs</code> fills that title in for every
|
||||
entry: from the command's own composition where it implements the engine's title contract — whose rule
|
||||
keeps the kind name at the front and never drops it — and from the kind's display name, resolved in the
|
||||
request culture, where it does not. Because a title is therefore always present, the node's
|
||||
<code>labelKey</code> is always dropped and the client's kind-name fallback never fires. Since the composition
|
||||
happens on the server per request, the tree host watches the locale and re-reads the command entries,
|
||||
rewriting those labels in place; it deliberately does not rebuild the branch, which would remount an
|
||||
open command editor mid-edit. The kind keys are still built as <code>tree.mission.kind.${commandType}</code>, and
|
||||
they do render — <code>AddCommandDialog.vue</code> prefers one over the server's label for every kind that has
|
||||
one — while the key lint resolves the template shape by wildcard rather than reporting an orphan.</p>
|
||||
<h2 id="the-server-half">The Server Half</h2>
|
||||
<p>The language preference is server-held. <code>GET /api/preference/language</code> answers
|
||||
<code>{ success, current, available }</code>, where <code>current</code> is the persisted <code>UserConfig.LanguageCode</code> —
|
||||
default <code>en</code> — and <code>available</code> is the server's own supported list, <code>en</code>, <code>zh-Hans</code>, <code>zh-Hant</code>. <code>POST</code>
|
||||
rejects an unlisted code with 400 and otherwise writes the value and saves the user configuration,
|
||||
which round-trips through the XML user config file. The <code>localStorage</code> key holds only a paint-time
|
||||
hint and loses every disagreement with this value.</p>
|
||||
<p>Some response text is composed on the server and never passes through a bundle.
|
||||
<code>PresentCatalogService</code> resolves the effective language per request in this order: an explicit <code>?lang=</code>
|
||||
query, then the request's <code>Accept-Language</code> header, then the persisted preference, then English.
|
||||
Because the header sits <em>above</em> the saved preference, a browser set to a different language than the
|
||||
application would win by default — so the client sends <code>?lang=</code> explicitly, from a <code>currentLang()</code>
|
||||
helper returning the app locale, on the endpoints whose text is localized server-side: the
|
||||
selected-step info, the execution strip chart, its step-property picker and its colour guide, the
|
||||
mission command entries, catalog and field descriptors, and the step-present key list. The mission
|
||||
controller resolves the same
|
||||
chain into a per-request UI culture and injects it into the command text source, so the culture rides
|
||||
on the request rather than on the thread.</p>
|
||||
<p>Two further server-originated string classes are localized on the client instead:</p>
|
||||
<ul>
|
||||
<li><strong>Coded API errors.</strong> Ten error codes are mapped onto <code>apiErrors.*</code> keys; a failed response carrying
|
||||
one of them renders the bundle value with the server's interpolation arguments. English is passed
|
||||
straight through to the server's own message on purpose, because that message <em>is</em> the English
|
||||
rendering and keeps per-site nuance the generic bundle value flattens.</li>
|
||||
<li><strong>Engine notifications.</strong> Session messages arrive with a structured id and English text. The client
|
||||
swaps in an <code>engineMessages.<id></code> bundle entry, but only when the wire text agrees with the English
|
||||
bundle entry for that id: a templated message must match the canonical English template exactly
|
||||
before its arguments are re-interpolated, and an untemplated one is swapped only when the bundle
|
||||
entry has no interpolation holes. Any mismatch — an id emitted with different templates at different
|
||||
sites, or an engine built against a different bundle — falls back to the wire English rather than
|
||||
rendering wrong text. The English <code>engineMessages</code> values are therefore load-bearing code, not a
|
||||
description of the Chinese ones.</li>
|
||||
</ul>
|
||||
<p>The step-present labels have a third home again: they ship beside the executable as
|
||||
<code>catalog.{lang}.json</code> files that overlay translations on top of the live English attribute data.
|
||||
English never comes from those files, and a missing file, a missing key or a parse failure simply
|
||||
means English.</p>
|
||||
<h2 id="the-glossary-and-the-build-gate">The Glossary and the Build Gate</h2>
|
||||
<p>Two Node scripts stand between the bundles and a shipped release. <code>wwwroot-src/package.json</code> defines
|
||||
<code>lint:i18n</code> as <code>census.mjs</code> followed by <code>lint-glossary.mjs</code>, and <code>build</code> as <code>lint:i18n</code> followed by
|
||||
<code>quasar build</code> — so either script failing stops the production build. The development server runs
|
||||
neither.</p>
|
||||
<p><code>lint-glossary.mjs</code> bundles each locale's index through esbuild, flattens the three message trees into
|
||||
key/value maps, and applies four checks, every one of them a hard failure:</p>
|
||||
<ol>
|
||||
<li><strong>Key isomorphism.</strong> The three flattened key sets must be identical, compared in both directions,
|
||||
so an extra key in a Chinese bundle fails as loudly as a missing one.</li>
|
||||
<li><strong>Bidirectional key integrity.</strong> Every bundle-shaped string literal under the source tree —
|
||||
excluding the bundles themselves — must resolve to an English key, and every English key must be
|
||||
referenced from somewhere. A literal naming a namespace prefix counts as a reference to everything
|
||||
beneath it; a template literal with an interpolation hole is expanded into a wildcard, which is how
|
||||
<code>tree.mission.kind.${kind}</code> is matched; and <code>@:linked.key</code> references inside bundle values count as
|
||||
well. A double-quoted literal in a Vue template is treated as a key only when its attribute is one
|
||||
of the four the check knows to be key-valued, so an expression attribute whose local variable
|
||||
happens to share a namespace name is not mistaken for one. The orphan half is what makes the check
|
||||
bidirectional, and its allow-list is currently empty.</li>
|
||||
<li><strong>Shape preservation.</strong> Never-translate tokens present in an English value must survive verbatim in
|
||||
both Chinese values — product and brand names, file formats and unit symbols by literal match, and
|
||||
NC codes, controller parameter names and function-key shortcuts by pattern. Placeholder sets must
|
||||
match in both directions, so a Chinese value can neither lose a placeholder nor invent one.</li>
|
||||
<li><strong>Per-locale vocabulary.</strong> An adjudicated forbidden-term table per locale, each entry naming its
|
||||
replacement and its reason, plus a check for a Chinese value identical to its English source that
|
||||
contains no CJK at all. Both carry narrow exemptions: named keys where a term is used in a
|
||||
different sense, and a small identity set for values that legitimately are the identifier.</li>
|
||||
</ol>
|
||||
<p>The tables the third and fourth checks read are written at the top of the script itself.
|
||||
<code>glossary.yaml</code>, the large harvested term list that sits beside the bundles, is generated data that
|
||||
the application never imports and the lint never reads: it is where a reading is settled before it is
|
||||
written into a bundle, not an input to the gate.</p>
|
||||
<p><code>census.mjs</code> guards the opposite direction — text that never became a key. It scans the same tree for
|
||||
user-visible English string literals reached through display-named template attributes, display-named
|
||||
object properties, notification and dialog shorthands, and prose text nodes; subtracts a classified
|
||||
allowlist of concrete text-and-file pairs; and compares what is left, per file, against a baseline.
|
||||
The gate fails only when a file's residual count <em>rises</em>, so cleanup can land incrementally — but the
|
||||
baseline is currently empty, which allows zero residual strings in any file and makes the effective
|
||||
rule that a newly hard-coded display string fails the build unless it is added to the allowlist with
|
||||
a reason.
|
||||
Allowlist entries whose file field is a glob or a brace list are classification notes rather than
|
||||
matchers; only concrete paths participate.</p>
|
||||
<h2 id="strings-outside-the-bundles">Strings Outside the Bundles</h2>
|
||||
<p>Three classes of user-visible English render in every locale — two because an interpolation hole
|
||||
hides them from the census, which discards any literal containing one, and one because the allowlist
|
||||
deliberately keeps it:</p>
|
||||
<ul>
|
||||
<li><strong>The shared numeric field's validation text.</strong> <code>NumericInput.vue</code> imports nothing from vue-i18n and
|
||||
builds three messages as template literals directly: an invalid-number message quoting what was
|
||||
typed, a “must be ≥” message naming the field's minimum, and a “must be ≤” message naming its
|
||||
maximum. All three are produced on blur — the field commits on blur or on Enter — and every panel
|
||||
that embeds the widget shows them in English. A <code>widgets</code> namespace exists for exactly this kind of
|
||||
shared control text; this component does not use it.</li>
|
||||
<li><strong>The geometry and topology editors' error prefixes.</strong> Those editors emit their failure context as a
|
||||
template literal that leads with the type name and the operation, so the leading phrase is English
|
||||
whatever the locale. The allowlist records the family as a ledger note rather than as matched
|
||||
entries.</li>
|
||||
<li><strong>The API-error default.</strong> The generic fallback message in the HTTP helper is allowlisted
|
||||
deliberately, on the grounds that it sits in the same stream as the server's own English messages.</li>
|
||||
</ul>
|
||||
<p>A fourth class is untranslated by design rather than by escape: the dynamic <code>label</code> text on a
|
||||
Control-Tree node — a file-backed path, an engine type name, and the user's own words inside a
|
||||
server-composed mission title — and the identity strings the allowlist classifies alongside it, which
|
||||
are backend type names rendered inside a translated sentence, axis and unit column headers, and brand
|
||||
names. The role-label half of that same allowlist class is a different case again: stored English that
|
||||
never reaches the screen at all, because a <code>labelKey</code> always resolves ahead of it.</p>
|
||||
<h2 id="source-code-path">Source Code Path</h2>
|
||||
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
|
||||
<h3 id="web-application">Web Application</h3>
|
||||
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
|
||||
<ul>
|
||||
<li><code>wwwroot-src/src/i18n/index.ts</code> — the vue-i18n instance and its fallback chain, <code>SUPPORTED_LOCALES</code>,
|
||||
<code>normalizeLocale</code>, <code>applyLocale</code>, the Quasar language-pack map, the shared collator, the storage key
|
||||
and the re-title registration.</li>
|
||||
<li><code>wwwroot-src/src/i18n/schema.ts</code> — the English bundle as <code>MessageSchema</code>.</li>
|
||||
<li><code>wwwroot-src/src/i18n/schema.d.ts</code> — the ambient augmentation that registers that shape as
|
||||
vue-i18n's global message schema.</li>
|
||||
<li><code>wwwroot-src/src/i18n/en/index.ts</code> — the English bundle: the twenty-three namespaces assembled into
|
||||
one object.</li>
|
||||
<li><code>wwwroot-src/src/i18n/zh-Hant/index.ts</code> and <code>wwwroot-src/src/i18n/zh-Hans/index.ts</code> — the same
|
||||
assembly, each annotated with <code>MessageSchema</code>.</li>
|
||||
<li><code>wwwroot-src/src/i18n/en/engineMessages.ts</code> — the engine-notification renderings keyed by structured
|
||||
id; its English values are the eligibility gate for every swap.</li>
|
||||
<li><code>wwwroot-src/src/i18n/en/routes.ts</code> — the strings the routes' <code>meta.title</code> keys resolve to.</li>
|
||||
<li><code>wwwroot-src/src/i18n/en/widgets.ts</code> — the shared-widget namespace.</li>
|
||||
<li><code>wwwroot-src/src/i18n/README.md</code> — the invariants written beside the bundles: the fallback rule, the
|
||||
never-translate list, the number and sorting rules and the vocabulary table.</li>
|
||||
<li><code>wwwroot-src/src/i18n/glossary.yaml</code> — generated term data, imported by nothing.</li>
|
||||
<li><code>wwwroot-src/src/boot/i18n.ts</code> — the plugin installation, the cached-locale fast path with its
|
||||
unawaited reconcile, the awaited race on a cache miss, and the guards around every locale step.</li>
|
||||
<li><code>wwwroot-src/quasar.config.ts</code> — the boot list whose order places i18n after the authentication
|
||||
fetch patch.</li>
|
||||
<li><code>wwwroot-src/index.html</code> — the static shell whose <code>lang</code> attribute is corrected at mount.</li>
|
||||
<li><code>wwwroot-src/package.json</code> — <code>build</code> as the i18n scripts followed by <code>quasar build</code>, and <code>vue-tsc</code>
|
||||
as a separate script.</li>
|
||||
<li><code>scripts/lint-glossary.mjs</code> — the four bundle checks and the adjudication tables they read.</li>
|
||||
<li><code>scripts/census.mjs</code> — the raw display-string extraction, the allowlist subtraction and the per-file
|
||||
ratchet.</li>
|
||||
<li><code>scripts/i18n-allowlist.json</code> — the classified exemptions, each with its reason.</li>
|
||||
<li><code>scripts/i18n-baseline.json</code> — the per-file residual ceiling the census compares against.</li>
|
||||
<li><code>wwwroot-src/src/stores/appState.ts</code> — the language reference, the available-code list, and the
|
||||
action that POSTs before applying the locale and rolls back on failure.</li>
|
||||
<li><code>wwwroot-src/src/api/preference.ts</code> — the typed wrappers over the language endpoints and the
|
||||
<code>?lang=</code>-bearing step-present key request.</li>
|
||||
<li><code>wwwroot-src/src/api/http.ts</code> — <code>currentLang()</code>, the coded-error map and the app-locale rendering
|
||||
that skips English.</li>
|
||||
<li><code>wwwroot-src/src/api/sessionMessages.ts</code> — the engine-notification localizer, its template-equality
|
||||
gate and the repeat-fold wrapper.</li>
|
||||
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the node's <code>label</code>, <code>labelKey</code> and
|
||||
<code>labelParams</code> fields and the <code>nodeDisplayLabel</code> and <code>nodeDisplayInfo</code> resolvers.</li>
|
||||
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the rendered-tree computed that
|
||||
calls the resolver, and the locale watcher that re-pulls the server-composed mission command titles
|
||||
without rebuilding the branch.</li>
|
||||
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the kind display names and their
|
||||
<code>tree.mission.kind.*</code> key twin, and the rule that drops the key whenever the entry carries a title.</li>
|
||||
<li><code>wwwroot-src/src/components/widgets/NumericInput.vue</code> — the shared numeric field: blur-and-Enter
|
||||
commit, and the three hard-coded validation messages.</li>
|
||||
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the language submenu, its hard-coded self-name map and
|
||||
the action it calls.</li>
|
||||
<li><code>wwwroot-src/src/components/explorerSort.ts</code> — the only consumer of the shared collator, calling it
|
||||
in place so a switch is picked up.</li>
|
||||
<li><code>wwwroot-src/src/router/index.ts</code> — the key-valued <code>meta.title</code> resolution and the hook the locale
|
||||
switch calls to re-title a parked tab.</li>
|
||||
<li><code>Environments/PreferenceController.cs</code> — the language endpoints, the supported-code list and the
|
||||
400 on an unlisted code.</li>
|
||||
<li><code>Environments/UserConfig.cs</code> — the persisted language code, its English default and its XML
|
||||
round-trip.</li>
|
||||
<li><code>Environments/PresentCatalogService.cs</code> — the per-request language resolution chain, the tag
|
||||
normalizer and the shipped catalog overlay.</li>
|
||||
<li><code>Missions/MissionController.cs</code> — the per-request UI culture taken from the same chain and injected
|
||||
into command title composition.</li>
|
||||
</ul>
|
||||
<h2 id="see-also">See Also</h2>
|
||||
<ul>
|
||||
<li><a class="xref" href="../shell/language-selection-submenu.html">Language Selection SubMenu</a> — the menu gesture that drives this mechanism, and the only
|
||||
place a user changes the locale</li>
|
||||
<li><a class="xref" href="../conventions/translation-remarks.html">Translation Remarks</a> — the terminology contract the bundles are written to, and the
|
||||
readings the vocabulary lint enforces</li>
|
||||
</ul>
|
||||
|
||||
</article>
|
||||
|
||||
<div class="contribution d-print-none">
|
||||
</div>
|
||||
|
||||
<div class="next-article d-print-none border-top" id="nextArticle"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="affix">
|
||||
<nav id="affix"></nav>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="container-xxl search-results" id="search-results"></div>
|
||||
|
||||
<footer class="border-top text-secondary">
|
||||
<div class="container-xxl">
|
||||
<div class="flex-fill">
|
||||
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user