Translation Preflight for VS Code
A DevSolal tool — devsolal.com
Offline localization QA for flat JSON locale files and exactly-two-column key,text CSV files.
It shows missing keys, empty target values and placeholder mismatches in the Problems panel.
It reads local files, writes only when you ask it to export, and makes no network calls.
This directory is the VS Code channel for the approved Translation Preflight engine. The
packaged engine.js is a byte-identical copy of ../src/engine.js, pinned in
engine.lock.json; the build refuses to package if either copy drifts.
Two editions, two extension ids
|
Free |
Pro |
| Extension id |
<publisher>.translation-preflight |
<publisher>.translation-preflight-pro |
| Pairs |
one source/target pair |
multiple configured pairs |
| Entry cap |
500 entries per side |
engine limit (10,000 entries, 2 MiB per file) |
| Problems diagnostics |
missing key, empty target, placeholder mismatch (plus the engine's other warnings) |
same, across every pair |
| Export |
no export module, no export command |
CSV and JSON export commands |
| Distribution |
VSIX now; Marketplace listing is an owner action; the Preflight: Upgrade to Pro command opens the paid listing only once the owner records a verified https itch.io URL |
paid VSIX through the merchant listing |
The ids are deliberately different. A Marketplace auto-update of Free can never overwrite a
sideloaded Pro build. Because both editions use the same command ids, uninstall Free before
installing Pro. Free also detects Pro's extension id and disables its own diagnostics while
Pro is running, so a user who has both installed does not get duplicate Problems entries. If
Pro is installed but disabled or never activates, Free keeps working instead of going dark.
Workspace trust (Restricted Mode)
Both manifests declare capabilities.untrustedWorkspaces.supported: "limited". Reading locale
files requires a trusted workspace, so in Restricted Mode:
- activation reads nothing and writes nothing (no diagnostics, no exports, no save watcher
output);
- the status bar shows
$(lock) Preflight Free/Pro: restricted mode — workspace not trusted
with a tooltip explaining how to proceed;
- the run/export commands stay registered but refuse to read: invoking one shows a warning and
leaves the blocked status in place, so a keybinding or command palette entry cannot bypass
the guard;
- granting trust resumes work through
workspace.onDidGrantWorkspaceTrust without reloading.
The publisher id in this build is the neutral placeholder localization-qa-placeholder.
Packaging works offline, but publishing is owner-gated: replace the placeholder in
src/product.js with the real Marketplace publisher id, rebuild, and re-run the gate. An
independent reviewer must also record VERDICT: APPROVE bound to each VSIX SHA-256 in
listing/approval.json; the build never creates that record. See listing/OWNER-GATE.md.
The funnel has a third owner gate: src/product.js ships proPurchaseUrl: '' until the
operator creates the Pro itch.io listing. Preflight: Upgrade to Pro refuses to open anything
while that value is empty or is not an absolute https URL on itch.io/*.itch.io; it shows
"not published yet" instead of guessing a link. npm run verify reports
purchaseUrlReady: false and publishReady: false until the owner records the real URL,
replaces the publisher and an independent approval is bound to the artifacts.
Install
Free VSIX (for local testing; no Marketplace listing exists yet):
code --install-extension dist/translation-preflight-free-0.1.0.vsix
Pro VSIX (buyers install the file they receive after purchase):
code --install-extension dist/translation-preflight-pro-0.1.0.vsix
Or in VS Code: Extensions view -> ... -> Install from VSIX....
Pro is sold separately on the operator's existing itch.io merchant account. The listing is
owner-created; this repository contains no itch URL and the build never invents one. The
Pro VSIX has no licence key, no entitlement check and no phone-home. Anyone who receives the
file can use it. The paid boundary is distribution plus the multi-pair/export wiring that the
Free archive does not contain. That is disclosed, not DRM.
{
"translationPreflight.source": "locales/en.json",
"translationPreflight.target": "locales/es.json",
"translationPreflight.format": "auto",
"translationPreflight.runOnSave": true
}
- Relative paths resolve against the first workspace folder; absolute paths also work only
inside that folder. A configured path outside the workspace is refused and surfaced in the
status bar instead of being read.
format is auto, json or csv. auto requires both files to end in the same
supported extension (.json or .csv); a .json source with a .csv target needs an
explicit format.
- Free accepts at most 500 entries per side. At 501 it refuses the pair, clears diagnostics
and reports the cap instead of comparing a truncated file.
runOnSave is read on every save, so toggling it takes effect immediately; no window reload
is needed. The same is true for Pro below.
{
"translationPreflight.pairs": [
{ "source": "locales/en.json", "target": "locales/es.json" },
{ "source": "locales/en.csv", "target": "locales/de.csv", "format": "csv" }
],
"translationPreflight.runOnSave": true
}
Pro has no 500-entry cap. The engine's own limits still apply: 10,000 entries and 2 MiB per
file, with string values only. Pro accepts at most 100 configured pairs. Every pair path must
stay inside the first workspace folder; paths outside it are refused. Settings changes and
saves of configured files re-run the comparison.
Commands
| Command |
Edition |
Behaviour |
Preflight: Re-run comparison |
Free |
re-read the configured pair |
Preflight: Upgrade to Pro |
Free |
opens the owner-verified itch.io listing; when no verified URL is configured it explains that Pro is not published yet and opens nothing |
Preflight Pro: Re-run all pairs |
Pro |
re-read every configured pair |
Preflight Pro: Export findings to CSV |
Pro |
save dialog, then engine toCSV output |
Preflight Pro: Export findings to JSON |
Pro |
save dialog, then per-pair reports with engine toJSON |
CSV has the engine's fixed columns (severity,code,key,message). When more than one pair is
exported, the message is prefixed with [pair N: source -> target] so a row keeps its pair
context. JSON keeps the original message and adds pair, source and target fields. Paths in
both formats are workspace-relative (basename when a pair is somehow outside the workspace);
absolute paths are never written into an export. An export is refused if any configured pair
fails to parse, so a partial file is never written.
Export targets are checked before writing: the save dialog cannot be used to overwrite a
configured source or target, including through a symbolic-link or hard-link alias of one, and a
failed write is reported in the status bar and an error message instead of disappearing. A
malformed translationPreflight.pairs value (not an array, or an entry that is not
{source, target}) is reported as a configuration error, never as a clean run.
Flat JSON object with string values:
{ "hello": "Hello", "bye": "Bye {name}" }
CSV with exactly the header key,text and two columns per row (RFC4180 quoting supported):
key,text
hello,Hello
bye,"Bye {name}"
Both files must be valid UTF-8. Invalid bytes are rejected with an encoding error instead of
being replaced with U+FFFD (a Windows-1252 export would otherwise look clean while its strings
were silently mangled).
Placeholders supported by the approved engine: {name} and simple %s/%d/%f
(including numbered forms). ICU plural/select and escaped braces are reported as
unsupported syntax rather than guessed at.
Privacy and offline behaviour
- No telemetry, no account, no update ping, no network module. The upgrade command uses
env.openExternal, which is the host's browser hand-off, and only for a validated itch.io
URL; the extension itself makes no network request.
- In Restricted Mode neither edition reads or writes workspace files until trust is granted.
- The packaged JavaScript is scanned by
scripts/verify-vsix.js for http/https/net/
dns/tls/dgram, fetch(, XMLHttpRequest and WebSocket.
- The engine is the same offline engine that ships with the standalone product.
Build and QA
cd vscode
npm install # installs @vscode/vsce
npm test # unit + mock-host + real archive tests
npm run build # writes dist/translation-preflight-{free,pro}-0.1.0.vsix
npm run verify # re-opens both archives and checks the gates
npm run gate # tests + build + verify
The build sets SOURCE_DATE_EPOCH when calling vsce package, so rebuilding the same source
produces byte-identical VSIX files (verified in test/packaging.test.js).
dist/verify-report.json carries publisherReady, purchaseUrlReady, approvalBound and
publishReady. publishReady is false until the real publisher id, a verified https itch.io
purchase URL and an independent approval bound to each artifact are all in place.
The mock-host tests exercise activation, Problems diagnostics, the Free cap, Pro multi-pair
behaviour and both export commands without needing a code binary. Archive tests reopen the
real VSIX zips and check that Free contains no pro-export.js, no export command and no
showSaveDialog reference, and that both archives carry the pinned engine.
Official API references used: Extension Manifest,
Publishing Extensions,
VS Code API, and the
@vscode/vsce package.
Licence
See LICENSE.txt. Free edition is free to use; Pro is governed by the buyer licence on the
purchase page. The Pro VSIX may not be redistributed as a competing product or public download.