Skip to content

Commit 70a0af5

Browse files
ryanwelcherzaerl
andauthored
Translate the Terminal and Logs output (#646)
## Why The Terminal and the Logs were plain English in a translated window: the terminal's help and refusals, every line the setup, update and apply chains write there, the build watch and dev server lines, the Logs' tabs and empty-pane notes, and main's own lines from the trunk update, the pull request switch, the npm runner and the server start. This is the #627 batch of #622. ## What changes - **Every line written to the Terminal or a Logs pane** goes through `__`/`_n`/`sprintf`, with commands, paths, keys and file names passed in as `%s`. Each line is one or more whole sentences, so the terminal can show a translated line on its own. - **Sentences that were spliced together are written whole.** The apply chain built "The ${noun} is ${verb}" and "${verb} — …" from five verb and noun pairs; it is now a table of whole sentences per pair (`applyLines` in `watch-activity.cjs`), the way `applyDoneMessage` in `confirmations.cjs` works. `applyFinishMessage` used to regex the English "— open the site to try it out." out of a line; the caller now passes the line without it. The switch progress (`switch-progress.cjs`), the setup's `Status: ${phase}`, main's `Update failed during ${stage}` and the npm "letting go" line each get one sentence per case. - **Inline markup** (the hints under the terminal, the empty Logs panes) uses `createInterpolateElement`, with `npm run build`, `help`, `Ctrl+C`, `package.json`, `src/`, `build/` and `error_log()` passed in as elements. - **The dev server's "see Help → Open App Log"** takes the menu item as `%s`, from the same `__('Open App Log')` the menu uses (left over from #634). - **Module-level constants became functions:** `COPY_BUTTON_LABELS`, `SETUP_END_MESSAGES` and the setup status line move from `index.jsx` to `confirmations.cjs` as `copyButtonLabel`, `setupEndMessage` and `setupStatusLine`, where the suite can reach them; `STALE_ASSETS` and main's `ENGINE_RETRY_NOTICE` become functions too. 110 new strings. Not wrapped, per #622 or because the text is not ours: Git's own progress phases (`Updating files 3/10`), npm's and the server's output, the stub `appendNpm` buffer nothing shows, the `42%` progress figure, and the lines owned by sibling batches (`SKIP_INSTALL_MESSAGE` and the dirty-tree errors are #626's in #636; the patch apply lines in main are #628's; the blocked-site errors are #629's). ## How to test this The new journeys cover both trays in the pseudo-locale. Run them from the repository root, on either platform: ``` npx playwright test --project=journeys tests/e2e/journeys/i18n.spec.js -g "Terminal is fully|Logs are fully" ``` Both fail without this change (checked by stashing `src/` and rebuilding). The Terminal journey opens the tray on a built site, reads the help printed at start, types `help` and an unknown command, joins the terminal's rows into whole lines and checks every printed line starts with `[`, then scans the hints under it. The Logs journey scans the empty Build watch and Debug.log panes, starts the dev server with stubbed handlers so the watch starts and the server refuses, and scans the server's failure line, the watch's start and exit lines and the tab titles as they change. What the journeys cannot reach: they stub IPC, so main's lines (trunk update, PR switch, npm runner, server start) never reach the screen in them, and neither do the apply, update and setup chains, which need a real install and build. Unit tests cover those in the pseudo-locale instead: `watch-activity`, `update-handoff`, `dev-server-command`, `switch-progress`, `confirmations`, `trunk-update-fetch.integration` and three in `ipc-wiring` (the server's start failure, the trunk update's failure lines and the engine retry notice). To see the chains on screen by hand (either platform, current head): 1. From the repository root, run `npx electron . --lang=en-XA`. 2. Open a built Core site, open the **Terminal** tray and type `help`. - Expected: every line except the `$` prompt is accented and in brackets, and the help's descriptions still start in one column. 3. Click **Update to latest trunk** (in its pseudo-localised form) and let it run. - Expected: the lines the app writes ("Fetching latest trunk…", "Now on trunk as of …", "Running npm run build…", "Update complete — …") are bracketed; Git's and npm's own output is not. **What must not have happened:** no English an English speaker sees has changed, apart from the one line break and the one plural named in Risks. All 132 journeys pass with their exact English strings, including `terminal.spec.js`, `tray.spec.js`, `logs.spec.js`, `build-watch.spec.js` and `dev-server.spec.js`. ## Risks and limitations - Two changes to the English, both forced by the rules: the help's last paragraph was one sentence broken over two lines with a `\n`, which a translated string cannot hold, so it is now two sentences on two lines ("…npm run build once." / "Run them here whenever…"); `LONGEST_HELP_LINE` in `terminal.spec.js` follows it. And the switch progress said "1 files"; with `_n` it says "1 file". - Merge conflicts with the other open batches: #643, #636 and the parallel #628 and #629 all append tests to the end of `i18n.spec.js` (#635 did too; this branch is rebased on it). #636 also adds `import { __ }` to `use-dev-server.jsx` and `sprintf` to `index.jsx`'s import, where this branch adds `sprintf` too; those lines will need a one-line resolution, whichever merges second. - Main's PR-switch lines and `npmLetGoLine` in `main.js` are covered only by `npm run i18n:pot` extracting them and lint checking their comments; the trunk update, server start and engine retry lines have `ipc-wiring` tests. - Still English in the terminal during an apply: the lines owned by #628 (main's "Applying …" and the `patch-apply.js` lines) and #626's `SKIP_INSTALL_MESSAGE` (in #636). They land with those batches. - The diff is about 870 lines, over the ~800 guideline. Most of it is the per-case sentence table and tests; it is one tray's worth of strings and did not split cleanly. - Review: 3 [fix here], all fixed; 3 [follow-up], deferred (see the review outcome). ## Related Fixes #627. Part of #622. Follows #634 and #635. --- <details> <summary>Design decisions and alternatives considered</summary> - **The help's columns.** Each help line is one string, `%s Show this help text`, with the command padded into `%s`, so every printed line opens with the translated string's bracket and the descriptions still line up after the command column. The alternative, a translated description after an untranslated command, would leave each line starting in English. - **The apply sentences as a table**, not a frame like "%1$s but the build failed": a translation cannot rely on "The patch is applied" fitting in front of "but". The two `Restored` lines share their English but have a `_x` context per noun, so a language with gendered participles can say them differently. - **"Start the build watch, or run %s in the Terminal."** is its own sentence after the one saying the site still runs the old assets, instead of being copied into ten strings. - **Setup status per phase:** main sends only `cloning` and `done`; each gets its own line and anything else says "Status update", where it used to print the code. - **`Debug.log` and `Debug.log (%d)`** are wrapped so a translator can name the tab in their language; the file name in sentences is left as is. </details> <details> <summary>Review outcome (required — see AGENTS.md)</summary> 3 [fix here] · 3 [follow-up]. All 3 [fix here] findings are fixed. - 🟡 `copyButtonLabel`, `setupStatusLine` and `setupEndMessage` were branches choosing user-facing sentences inside `index.jsx`, which the suite cannot load. Fixed: moved to `confirmations.cjs` with a test in English and in the pseudo-locale. - 🔵 `engineRetryNotice` was made a function so it is translated when shown, and nothing checked it. Fixed: an `ipc-wiring` test drives the engine retry in the pseudo-locale. - 🔵 The Terminal journey dropped any line starting with `[`, so translated text followed by English would pass. Fixed: each line must be one bracketed string from its first character to its last. - Follow-up: the apply flow in the terminal is still partly English (main's "Applying …" line, `patch-apply.js`, `SKIP_INSTALL_MESSAGE`). Those belong to #628 and #626 (#636), not this batch. - Follow-up: the five `compiling` sentences in `applyLines` repeat `compilingMessage()`'s text, on purpose, so each can be translated whole; the test now pins all five against `compilingMessage()`. - Follow-up: `npmLetGoLine` reads `process.platform`, so only one platform's branch runs in a test. The split was already inline before this branch; injecting the platform is a separate change. - One review note was checked and not taken: it said the renderer no longer imports `switch-progress.cjs`, but `index.jsx` still does, so that module's header comment stays. - **Review:** completed. Fresh agent context (Explore subagent given the diff and the review instructions), head `c7a1ba8` / base `9fd167e`. Lint clean, `npm test` passing, read-only, so it did not run the journeys. - **Since review:** `c7a1ba8 → 274bb2d`. The review fixes above, then a rebase onto trunk `e94f507` after #635 merged (only `i18n.spec.js` conflicted: both appended tests). Checked by the authoring session, not re-reviewed by a fresh context: `npm run lint` clean, `npm test` 2044 pass / 0 fail, all 132 journeys pass, `npm run i18n:pot` clean. - **After CI:** `274bb2d → 6179bd9`. The Terminal journey failed on CI's macOS runner: it waited for the help's first line, which on that window had scrolled out of the rows xterm draws, because pseudo-locale lines are longer and wrap. It now waits for the help's last line. Reproduced locally at 1024×640 (failed before, passes after). Test-only change, checked by the authoring session, not re-reviewed: the i18n and terminal journeys pass (20). </details> <details> <summary>Screenshots or recording</summary> None. Nothing changes on screen in English beyond the help's line break, and the pseudo-locale journeys are the check for the translated state. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Francesco Bigiarini <francesco.bigiarini@gmail.com>
1 parent fbb96c6 commit 70a0af5

24 files changed

Lines changed: 875 additions & 173 deletions

‎src/main.js‎

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const { LEGACY_SITE_ERROR } = require('./renderer/legacy-site.cjs');
8888
const { resolveCatalog, languageChoices } = require('./i18n.cjs');
8989
const { isPseudoLocale } = require('./renderer/pseudo-locale.cjs');
9090
const { applyLocale } = require('./renderer/locale-setup.cjs');
91-
const { __, setLocaleData } = require('@wordpress/i18n');
91+
const { __, _n, sprintf, setLocaleData } = require('@wordpress/i18n');
9292
const { addFilter } = require('@wordpress/hooks');
9393
const { mergeInProgressError, mergeCheckFailedError } = require('./renderer/merge-in-progress.cjs');
9494
const { parseHandle } = require('./wporg-handle.cjs');
@@ -2075,7 +2075,8 @@ ipcMain.handle('git:update-trunk', async (event, sitePath) => {
20752075
branchBefore = active.ref;
20762076
ticketBefore = branchBefore === TRUNK ? null : ticketIdFromRef(branchBefore);
20772077
if (branchBefore !== TRUNK) {
2078-
sendLog(`Parking your work on ${branchBefore} before updating…\n`);
2078+
// translators: %s: the branch the contributor's work is on, such as ticket/59234.
2079+
sendLog(`${sprintf(__('Parking your work on %s before updating…'), branchBefore)}\n`);
20792080
// The same progress the ticket panel shows (#173), but into the
20802081
// terminal this flow already streams to rather than onto the
20812082
// switch channel: one operation with two progress surfaces is
@@ -2160,7 +2161,8 @@ ipcMain.handle('git:update-trunk', async (event, sitePath) => {
21602161
// onto the new trunk is `branches:rebase`, offered by the ticket
21612162
// card's notice — the app never silently rebases anyone.
21622163
if (branchBefore !== TRUNK) {
2163-
sendLog(`\nReturning to your work on ${branchBefore}…\n`);
2164+
// translators: %s: the branch the contributor's work is on, such as ticket/59234.
2165+
sendLog(`\n${sprintf(__('Returning to your work on %s…'), branchBefore)}\n`);
21642166
const returnLog = updateSwitchLogger(sendLog);
21652167
try {
21662168
await withSwitchMarker(sitePath, () => switchToBranch(sitePath, branchBefore, { onProgress: returnLog.emit, onChild: trackGitChild(sitePath) }));
@@ -2199,7 +2201,18 @@ ipcMain.handle('git:update-trunk', async (event, sitePath) => {
21992201
// so. Neither is a case of writing where nobody will read.
22002202
try { await writeWorkMeta(sitePath, patch); } catch {}
22012203
}
2202-
sendLog(`\nUpdate failed during ${stage}: ${String(e && e.message ? e.message : e)}\n`);
2204+
// One sentence per stage, which is a code: the fetch, or the
2205+
// checkout that follows it.
2206+
const why = String(e && e.message ? e.message : e);
2207+
let failed;
2208+
if (stage === 'checkout') {
2209+
// translators: %s: the error the checkout failed with.
2210+
failed = sprintf(__('Update failed during checkout: %s'), why);
2211+
} else {
2212+
// translators: %s: the error the fetch failed with.
2213+
failed = sprintf(__('Update failed during fetch: %s'), why);
2214+
}
2215+
sendLog(`\n${failed}\n`);
22032216
// The ticket was parked and the site left on trunk before this went
22042217
// wrong. Saying so is the whole difference between "my work is gone"
22052218
// and "my work is over there": the registry must not keep naming a
@@ -2209,9 +2222,15 @@ ipcMain.handle('git:update-trunk', async (event, sitePath) => {
22092222
const { ref: nowOn } = await activeBranch(sitePath);
22102223
if (nowOn === TRUNK && branchBefore !== TRUNK) {
22112224
await mergeSiteMeta(sitePath, { currentBranch: TRUNK, tracTicket: null });
2212-
sendLog(ticketBefore !== null
2213-
? `Your work on #${ticketBefore} is safe — it is committed on ${branchBefore}. Link that ticket again to return to it.\n`
2214-
: `Your work is safe — it is committed on ${branchBefore}. Apply that pull request again to return to it.\n`);
2225+
let safe;
2226+
if (ticketBefore !== null) {
2227+
// translators: 1: the ticket's number. 2: the branch the work is committed on, such as ticket/59234.
2228+
safe = sprintf(__('Your work on #%1$s is safe — it is committed on %2$s. Link that ticket again to return to it.'), ticketBefore, branchBefore);
2229+
} else {
2230+
// translators: %s: the branch the work is committed on, such as pr/7701.
2231+
safe = sprintf(__('Your work is safe — it is committed on %s. Apply that pull request again to return to it.'), branchBefore);
2232+
}
2233+
sendLog(`${safe}\n`);
22152234
}
22162235
} catch {}
22172236
sendDone({ ok: false, upToDate: false, error: String(e), stage, parkedOn: branchBefore === TRUNK ? null : branchBefore });
@@ -2327,7 +2346,7 @@ ipcMain.handle('git:checkout-pr', (event, sitePath, value) => streamPrOperation(
23272346
else if (localCopy) destination = branchState.tip;
23282347
else if (recorded.headOid === headOid) destination = await resolveRef(sitePath, ref) || headOid;
23292348
const needsInstall = await prNeedsInstall(sitePath, destination);
2330-
sendLog(localCopy ? 'Returning to your saved copy of the pull request…\n' : "Downloading the pull request's files and switching to its branch…\n");
2349+
sendLog(`${localCopy ? __('Returning to your saved copy of the pull request…') : __('Downloading the pull request\'s files and switching to its branch…')}\n`);
23312350
let switchOperation = () => checkoutPullRequest(sitePath, number, { headOid, recordedHeadOid: recorded.headOid, fromBaseOid: active.meta?.baseOid, onProgress, onChild });
23322351
if (resume) switchOperation = () => resumeSwitch(sitePath, ref, { onProgress, onChild });
23332352
else if (localCopy) switchOperation = () => switchToBranch(sitePath, ref, { baseOid: active.meta?.baseOid, onProgress, onChild });
@@ -2340,7 +2359,10 @@ ipcMain.handle('git:checkout-pr', (event, sitePath, value) => streamPrOperation(
23402359
// it now is what makes the next attempt ours rather than a foreign PR.
23412360
if (e.created || e.moved) await recordPrHead(sitePath, ref, number, e.headOid, returnTo);
23422361
logError('git:checkout-pr', String(e.stack || e));
2343-
if (e.cleanupError) sendLog(`Could not remove the unused branch: ${e.cleanupError.message}\n`);
2362+
if (e.cleanupError) {
2363+
// translators: %s: the error Git gave.
2364+
sendLog(`${sprintf(__('Could not remove the unused branch: %s'), e.cleanupError.message)}\n`);
2365+
}
23442366
return { ok: false, number, code: e.code, error: e.message, ...(e.code === 'dirty-trunk' ? { files: await countChangesAgainst(sitePath) } : {}) };
23452367
}
23462368
}));
@@ -2360,7 +2382,7 @@ ipcMain.handle('git:leave-pr', (event, sitePath) => streamPrOperation(event, sit
23602382
if (blocked) return blocked;
23612383
if (!recorded.headOid) return { ok: false, code: 'no-pr-head' };
23622384
const needsInstall = await prNeedsInstall(sitePath, returnTo);
2363-
sendLog('Restoring the files of your previous branch…\n');
2385+
sendLog(`${__('Restoring the files of your previous branch…')}\n`);
23642386
const result = await withSwitchMarker(sitePath, () => resume
23652387
? resumeSwitch(sitePath, returnTo, { onProgress, onChild })
23662388
: leavePullRequest(sitePath, { returnTo, headOid: recorded.headOid, onProgress, onChild }));
@@ -2602,7 +2624,7 @@ ipcMain.handle('git:apply-patch', async (event, sitePath, options = {}) => {
26022624
} else {
26032625
const revertable = patchText.length <= REVERTABLE_PATCH_LIMIT;
26042626
if (!revertable) {
2605-
sendLog('This patch is too large to keep for an undo, so Revert will not be offered.\n');
2627+
sendLog(`${__('This patch is too large to keep for an undo, so Revert will not be offered.')}\n`);
26062628
}
26072629
try {
26082630
await writeWorkMeta(sitePath, {
@@ -3609,7 +3631,28 @@ ipcMain.handle('dir:show', async (_e, sitePath) => {
36093631
});
36103632
});
36113633

3612-
const ENGINE_RETRY_NOTICE = '\n⚠ This site requires a newer Node.js than this app bundles.\n Retrying with engine checks relaxed…\n\n';
3634+
// What the terminal says when npm has exited but something it started still
3635+
// holds its output: one sentence per platform, and per whether a signal ended
3636+
// it, since Windows lets that process run on and elsewhere it is ended.
3637+
function npmLetGoLine(code, signal) {
3638+
if (process.platform === 'win32') {
3639+
if (signal) {
3640+
// translators: 1: the code npm exited with. 2: the signal that ended it, such as SIGTERM.
3641+
return sprintf(__('npm exited with code %1$s (signal %2$s), but something it started is still running and holding its output. Letting go; that process runs on until it finishes on its own.'), code, signal);
3642+
}
3643+
// translators: %s: the code npm exited with.
3644+
return sprintf(__('npm exited with code %s, but something it started is still running and holding its output. Letting go; that process runs on until it finishes on its own.'), code);
3645+
}
3646+
if (signal) {
3647+
// translators: 1: the code npm exited with. 2: the signal that ended it, such as SIGTERM.
3648+
return sprintf(__('npm exited with code %1$s (signal %2$s), but something it started is still running and holding its output. Letting go and ending it.'), code, signal);
3649+
}
3650+
// translators: %s: the code npm exited with.
3651+
return sprintf(__('npm exited with code %s, but something it started is still running and holding its output. Letting go and ending it.'), code);
3652+
}
3653+
3654+
// A function, so that it is said in the locale applied at startup.
3655+
const engineRetryNotice = () => `\n⚠ ${__('This site requires a newer Node.js than this app bundles.')}\n ${__('Retrying with engine checks relaxed…')}\n\n`;
36133656

36143657
// Spawns an npm runner, and if it fails specifically because a dependency
36153658
// demands a newer Node than Electron bundles, retries once with engine checks
@@ -3654,7 +3697,8 @@ function runNpmWithEngineRetry({ runnerPath, args, cwd, onLog, onDone, register,
36543697
logError(logScope, `could not start: ${String(err)}`);
36553698
if (initial) throw err;
36563699
logEvent(logScope, 'never started; shims not written');
3657-
onLog('stderr', `\nFailed to start: ${err && err.message ? err.message : String(err)}\n`);
3700+
// translators: %s: why npm could not be started.
3701+
onLog('stderr', `\n${sprintf(__('Failed to start: %s'), err && err.message ? err.message : String(err))}\n`);
36583702
setTimeout(() => onDone(null), 0);
36593703
return;
36603704
}
@@ -3693,7 +3737,11 @@ function runNpmWithEngineRetry({ runnerPath, args, cwd, onLog, onDone, register,
36933737
// Also surfaced in the app's terminal: the log file explains a failure
36943738
// after the fact, but the person who just clicked the button needs to
36953739
// see that the run never started.
3696-
onLog('stderr', `\nFailed to start: ${err && err.message ? err.message : String(err)}\n`);
3740+
onLog('stderr', `\n${sprintf(
3741+
// translators: %s: why npm could not be started.
3742+
__('Failed to start: %s'),
3743+
err && err.message ? err.message : String(err)
3744+
)}\n`);
36973745
// Deferred by a turn so that close — which knows about the engines
36983746
// retry — wins whenever it does arrive. A spawn failure is the very
36993747
// case this logging exists to expose, so it must not also become a
@@ -3727,7 +3775,7 @@ function runNpmWithEngineRetry({ runnerPath, args, cwd, onLog, onDone, register,
37273775
if (settled) return;
37283776
const exit = `code ${code}${signal ? ` (signal ${signal})` : ''}`;
37293777
logEvent(logScope, `exited with ${exit} but a descendant still holds its output; letting go`);
3730-
onLog('stderr', `\nnpm exited with ${exit}, but something it started is still running and holding its output. Letting go${process.platform === 'win32' ? '; that process runs on until it finishes on its own' : ' and ending it'}.\n`);
3778+
onLog('stderr', `\n${npmLetGoLine(code, signal)}\n`);
37313779
// Group only: the leader is dead (this is its exit), so a group that
37323780
// is gone too leaves nothing to kill and a pid that may be reissued.
37333781
if (process.platform !== 'win32') killTreeByPid(child.pid, 'SIGKILL', { groupOnly: true });
@@ -3753,7 +3801,7 @@ function runNpmWithEngineRetry({ runnerPath, args, cwd, onLog, onDone, register,
37533801
cancelled: cancelledChildren.has(child)
37543802
});
37553803
if (retry) {
3756-
onLog('stdout', ENGINE_RETRY_NOTICE);
3804+
onLog('stdout', engineRetryNotice());
37573805
start(true);
37583806
return;
37593807
}
@@ -3999,7 +4047,15 @@ ipcMain.handle('playground:start', async (event, sitePath) => {
39994047
// settles on close the same way.
40004048
if (typeof pendingResolve === 'function') {
40014049
clearTimeout(timeoutId);
4002-
pendingResolve({ ok: false, error: `Server exited with code ${code}${signal ? ` (signal ${signal})` : ''} before reporting a URL` });
4050+
let error;
4051+
if (signal) {
4052+
// translators: 1: the code the server exited with. 2: the signal that ended it, such as SIGTERM.
4053+
error = sprintf(__('Server exited with code %1$s (signal %2$s) before reporting a URL'), code, signal);
4054+
} else {
4055+
// translators: %s: the code the server exited with.
4056+
error = sprintf(__('Server exited with code %s before reporting a URL'), code);
4057+
}
4058+
pendingResolve({ ok: false, error });
40034059
pendingResolve = null;
40044060
}
40054061
event.sender.send('playground:stopped', { sitePath, code });
@@ -4025,7 +4081,9 @@ ipcMain.handle('playground:start', async (event, sitePath) => {
40254081
// The tree, not the runner alone: a server that hung on the way up
40264082
// still has its worker underneath it.
40274083
killChildTree(child);
4028-
pendingResolve({ ok: false, error: `Server did not start within ${START_TIMEOUT_MS / 1000} seconds` });
4084+
const seconds = START_TIMEOUT_MS / 1000;
4085+
// translators: %d: how many seconds the server was given to start.
4086+
pendingResolve({ ok: false, error: sprintf(_n('Server did not start within %d second', 'Server did not start within %d seconds', seconds), seconds) });
40294087
pendingResolve = null;
40304088
}
40314089
}, START_TIMEOUT_MS);

‎src/renderer/components/logs-panel.jsx‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createInterpolateElement } from '@wordpress/element';
2+
import { __ } from '@wordpress/i18n';
13
import { Button, Stack, Tabs } from '@wordpress/ui';
24
import { LogText } from './log-text.jsx';
35

@@ -33,7 +35,11 @@ export function LogsPanel({ hidden, tabs, logs, copyLabel }) {
3335
<Tabs.Panel value="watch" tabIndex={-1} className="tray-log-panel">
3436
<div ref={watchPane} onScroll={logs.makeOnScroll('watch')} className="log-pane">
3537
{logs.watchLogs ? <LogText text={logs.watchLogs} /> : (
36-
<span className="log-pane-note">The build watch compiles <code>src/</code> edits into <code>build/</code>. It runs independently of the dev server — its output, and whether it is watching, paused, or stopped, appears here.</span>
38+
<span className="log-pane-note">{createInterpolateElement(
39+
// translators: <source /> is the folder edits are made in, src/. <build /> is the folder they are compiled into, build/.
40+
__('The build watch compiles <code><source /></code> edits into <code><build /></code>. It runs independently of the dev server — its output, and whether it is watching, paused, or stopped, appears here.'),
41+
{ code: <code />, source: <>src/</>, build: <>build/</> }
42+
)}</span>
3743
)}
3844
</div>
3945
</Tabs.Panel>
@@ -45,7 +51,11 @@ export function LogsPanel({ hidden, tabs, logs, copyLabel }) {
4551
// it instead. In the app's own font, not the terminal's:
4652
// this is interface copy rather than log output, and it is
4753
// what keeps the `<code>` bits in it distinguishable.
48-
<span className="log-pane-note">No PHP notices or errors yet. Anything WordPress or your code writes — <code>error_log()</code>, notices, deprecations, fatals — appears here while the dev server runs.</span>
54+
<span className="log-pane-note">{createInterpolateElement(
55+
// translators: <function /> is the PHP function that writes to the log, error_log().
56+
__('No PHP notices or errors yet. Anything WordPress or your code writes — <code><function /></code>, notices, deprecations, fatals — appears here while the dev server runs.'),
57+
{ code: <code />, function: <>error_log()</> }
58+
)}</span>
4959
)}
5060
</div>
5161
<Stack direction="row" align="center" justify="space-between" gap="sm" wrap="wrap" className="tray-notes">
@@ -54,11 +64,11 @@ export function LogsPanel({ hidden, tabs, logs, copyLabel }) {
5464
it is what someone needs to tail it in a terminal or attach
5565
it to a ticket. Selectable rather than truncated with an
5666
ellipsis: a path you cannot copy is decoration. */}
57-
<code className="log-path">{logs.debugLogPath || 'The log file appears once the dev server has run.'}</code>
67+
<code className="log-path">{logs.debugLogPath || __('The log file appears once the dev server has run.')}</code>
5868
<Stack direction="row" gap="sm">
59-
<Button variant="outline" tone="neutral" size="compact" onClick={logs.revealDebugLog} disabled={!logs.debugLogPath}>Show in folder</Button>
69+
<Button variant="outline" tone="neutral" size="compact" onClick={logs.revealDebugLog} disabled={!logs.debugLogPath}>{__('Show in folder')}</Button>
6070
<Button variant="outline" tone="neutral" size="compact" onClick={logs.copyDebugLog} disabled={!logs.debugLogs}>{copyLabel}</Button>
61-
<Button variant="outline" tone="neutral" size="compact" onClick={logs.clearDebugLog} disabled={!logs.debugLogs}>Clear</Button>
71+
<Button variant="outline" tone="neutral" size="compact" onClick={logs.clearDebugLog} disabled={!logs.debugLogs}>{__('Clear')}</Button>
6272
</Stack>
6373
</Stack>
6474
</Tabs.Panel>

0 commit comments

Comments
 (0)