Skip to content

Commit 1790e42

Browse files
committed
Translate the setup checklist and the trunk update banners
The checklist, the trunk update card, the question an update asks over edits, and the banners for an old trunk, an incomplete update and a finished one are wrapped, with the sentences they take from setup-steps, update-plan, next-action and project-type's setup copy. Module-level strings become functions or getters so they are translated when shown, and sentences built from parts become whole ones: the step counters, the file count, the update summary and the link-ticket hint, which now takes the work item's kind instead of splicing in its label. Fixes #626.
1 parent 8dbed36 commit 1790e42

16 files changed

Lines changed: 330 additions & 145 deletions

‎src/project-type.cjs‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// to Core. A site created before this field existed has no `projectType`, so it
1818
// keeps Core behavior with no migration and no store write.
1919

20-
const { __ } = require('@wordpress/i18n');
20+
const { __, sprintf } = require('@wordpress/i18n');
2121

2222
const WORDPRESS_DEVELOP_GIT_URL = 'https://github.com/WordPress/wordpress-develop.git';
2323
const GUTENBERG_GIT_URL = 'https://github.com/WordPress/gutenberg.git';
@@ -70,12 +70,16 @@ const PROJECT_TYPES = {
7070
},
7171

7272
// What the setup checklist says about the steps that differ per target.
73+
// Getters, like `description`, so each is translated when it is read.
7374
setup: {
74-
cloneLabel: 'Download WordPress development version',
75-
cloneDescription: 'Clone the WordPress develop repository.',
76-
buildDescription: 'Compile WordPress Core to generate the dist files. Later updates rebuild automatically.',
77-
builtDescription: 'Built. Edited files in src/ since? Run npm run build in the Terminal so the site picks them up — updates and applied patches rebuild on their own.',
78-
serverDescription: 'Launch the development server once to complete the WordPress setup wizard.'
75+
get cloneLabel() { return __('Download WordPress development version'); },
76+
get cloneDescription() { return __('Clone the WordPress develop repository.'); },
77+
get buildDescription() { return __('Compile WordPress Core to generate the dist files. Later updates rebuild automatically.'); },
78+
get builtDescription() {
79+
// translators: 1: a folder, src/. 2: the build command, npm run build.
80+
return sprintf(__('Built. Edited files in %1$s since? Run %2$s in the Terminal so the site picks them up — updates and applied patches rebuild on their own.'), 'src/', 'npm run build');
81+
},
82+
get serverDescription() { return __('Launch the development server once to complete the WordPress setup wizard.'); }
7983
},
8084

8185
// What the site page's cards say where the two targets differ. The
@@ -207,11 +211,14 @@ const PROJECT_TYPES = {
207211
},
208212

209213
setup: {
210-
cloneLabel: 'Download Gutenberg',
211-
cloneDescription: 'Clone the Gutenberg repository.',
212-
buildDescription: 'Compile the Gutenberg packages. Later updates rebuild automatically.',
213-
builtDescription: 'Built. Edited a package since? Run npm run build in the Terminal so the site picks it up; updates rebuild on their own.',
214-
serverDescription: 'Launch a WordPress with this checkout as its Gutenberg plugin, once, to finish the setup.'
214+
get cloneLabel() { return __('Download Gutenberg'); },
215+
get cloneDescription() { return __('Clone the Gutenberg repository.'); },
216+
get buildDescription() { return __('Compile the Gutenberg packages. Later updates rebuild automatically.'); },
217+
get builtDescription() {
218+
// translators: %s: the build command, npm run build.
219+
return sprintf(__('Built. Edited a package since? Run %s in the Terminal so the site picks it up; updates rebuild on their own.'), 'npm run build');
220+
},
221+
get serverDescription() { return __('Launch a WordPress with this checkout as its Gutenberg plugin, once, to finish the setup.'); }
215222
},
216223

217224
// 'plugin-mount', Gutenberg is a plugin, so Playground boots a stock

‎src/renderer/components/dirty-tree-modal.jsx‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState } from 'react';
22
import { Button, Modal } from '@wordpress/components';
3+
import { __, _n, sprintf } from '@wordpress/i18n';
34
import { Notice, Stack, Text } from '@wordpress/ui';
45

56
// The failure is read by its role, and is not also spoken.
@@ -18,13 +19,21 @@ export function DirtyTreeModal({ files, saving, error, onSave, onDiscard, onClos
1819
const [dirtyChoice, setDirtyChoice] = useState('save'); // save | discard
1920
return (
2021
<Modal
21-
title="Update to latest trunk?"
22+
title={__('Update to latest trunk?')}
2223
onRequestClose={() => { if (!saving) onClose(); }}
2324
shouldCloseOnClickOutside={!saving}
2425
>
2526
<Stack direction="column" gap="md" className="dirty-tree">
2627
<Text variant="body-md" render={<p />}>
27-
You&apos;ve changed {files.length === 1 ? '1 file' : `${files.length} files`} in this site. Resetting to trunk would throw them away.
28+
{sprintf(
29+
// translators: %d: how many files the contributor has changed.
30+
_n(
31+
"You've changed %d file in this site. Resetting to trunk would throw them away.",
32+
"You've changed %d files in this site. Resetting to trunk would throw them away.",
33+
files.length
34+
),
35+
files.length
36+
)}
2837
</Text>
2938
{files.length ? (
3039
<div className="dirty-tree-files">
@@ -34,8 +43,11 @@ export function DirtyTreeModal({ files, saving, error, onSave, onDiscard, onClos
3443
</div>
3544
) : null}
3645
{[
37-
{ key: 'save', label: 'Save them as a patch first (as a local file)', detail: 'a .diff on your machine — nothing is sent to Trac' },
38-
{ key: 'discard', label: 'Discard them', detail: 'your changes are lost; this cannot be undone', destructive: true }
46+
// Each detail carries its own dash: punctuation is the translator's.
47+
// translators: Said after "Save them as a patch first (as a local file)", on the same line.
48+
{ key: 'save', label: __('Save them as a patch first (as a local file)'), detail: __('— a .diff on your machine — nothing is sent to Trac') },
49+
// translators: Said after "Discard them", on the same line.
50+
{ key: 'discard', label: __('Discard them'), detail: __('— your changes are lost; this cannot be undone'), destructive: true }
3951
].map((opt) => {
4052
const selected = dirtyChoice === opt.key;
4153
return (
@@ -47,8 +59,8 @@ export function DirtyTreeModal({ files, saving, error, onSave, onDiscard, onClos
4759
aria-pressed={selected}
4860
className={opt.destructive ? 'dirty-tree-choice is-destructive' : 'dirty-tree-choice'}
4961
>
50-
<strong>{opt.label}</strong>
51-
<span className="dirty-tree-choice-detail"> — {opt.detail}</span>
62+
<strong>{opt.label}</strong>{' '}
63+
<span className="dirty-tree-choice-detail">{opt.detail}</span>
5264
</button>
5365
);
5466
})}
@@ -58,14 +70,14 @@ export function DirtyTreeModal({ files, saving, error, onSave, onDiscard, onClos
5870
</Notice.Root>
5971
) : null}
6072
<Stack direction="row" justify="flex-end" gap="sm" wrap="wrap">
61-
<Button variant="secondary" onClick={onClose} disabled={saving}>Cancel</Button>
73+
<Button variant="secondary" onClick={onClose} disabled={saving}>{__('Cancel')}</Button>
6274
<Button
6375
variant="primary"
6476
isDestructive={dirtyChoice === 'discard'}
6577
isBusy={saving}
6678
disabled={saving}
6779
onClick={() => (dirtyChoice === 'discard' ? onDiscard() : onSave())}
68-
>{dirtyChoice === 'discard' ? 'Discard & update' : 'Save patch & update'}</Button>
80+
>{dirtyChoice === 'discard' ? __('Discard & update') : __('Save patch & update')}</Button>
6981
</Stack>
7082
</Stack>
7183
</Modal>

‎src/renderer/components/setup-checklist.jsx‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useId } from 'react';
2+
import { __ } from '@wordpress/i18n';
23
import { check, closeSmall } from '@wordpress/icons';
34
import { Badge, Button, Card, Icon, Notice, Spinner, Stack, Text } from '@wordpress/ui';
45
import { setupStepBadge, setupStepLabel } from '../setup-steps.cjs';
@@ -36,8 +37,8 @@ export function SetupChecklist({ steps, cueId, running, stopped, onSkip }) {
3637
return (
3738
<Card.Root className="setup-checklist" render={<section aria-labelledby={titleId} />}>
3839
<Card.Header render={<Stack direction="column" gap="xs" />}>
39-
<Card.Title id={titleId} render={<h2 />}>Initial setup checklist</Card.Title>
40-
<Text variant="body-md" className="muted-label">Complete each step to prepare this site for development.</Text>
40+
<Card.Title id={titleId} render={<h2 />}>{__('Initial setup checklist')}</Card.Title>
41+
<Text variant="body-md" className="muted-label">{__('Complete each step to prepare this site for development.')}</Text>
4142
</Card.Header>
4243
<Card.Content render={<Stack direction="column" gap="md" />}>
4344
{/* Nobody pressed a button to start this, so the notice has to say
@@ -48,14 +49,14 @@ export function SetupChecklist({ steps, cueId, running, stopped, onSkip }) {
4849
<Notice.Title>{running.title}</Notice.Title>
4950
<Notice.Description>{running.body}</Notice.Description>
5051
<Notice.Actions>
51-
<Button variant="outline" tone="neutral" size="compact" onClick={running.onStop}>Stop setup</Button>
52+
<Button variant="outline" tone="neutral" size="compact" onClick={running.onStop}>{__('Stop setup')}</Button>
5253
</Notice.Actions>
5354
</Notice.Root>
5455
) : null}
5556
{!running && stopped ? (
5657
<Notice.Root intent="warning" spokenMessage={SILENT}>
57-
<Notice.Title>Setup stopped.</Notice.Title>
58-
<Notice.Description>Nothing was lost — pick it back up with the buttons below whenever you want.</Notice.Description>
58+
<Notice.Title>{__('Setup stopped.')}</Notice.Title>
59+
<Notice.Description>{__('Nothing was lost — pick it back up with the buttons below whenever you want.')}</Notice.Description>
5960
</Notice.Root>
6061
) : null}
6162
<ol className="setup-steps">
@@ -81,7 +82,7 @@ export function SetupChecklist({ steps, cueId, running, stopped, onSkip }) {
8182
})}
8283
</ol>
8384
<Stack direction="row">
84-
<Button variant="minimal" tone="neutral" size="compact" onClick={onSkip}>Skip initialization wizard</Button>
85+
<Button variant="minimal" tone="neutral" size="compact" onClick={onSkip}>{__('Skip initialization wizard')}</Button>
8586
</Stack>
8687
</Card.Content>
8788
</Card.Root>

‎src/renderer/components/trunk-update-card.jsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useId } from 'react';
2+
import { __ } from '@wordpress/i18n';
23
import { Card, Stack, Text } from '@wordpress/ui';
34
import { StepList } from './step-list.jsx';
45

@@ -21,7 +22,7 @@ export function TrunkUpdateCard({ cue, rows, count, note = '' }) {
2122
<Card.Root {...cue} className={['trunk-update-card', cue.className].filter(Boolean).join(' ')} render={<section aria-labelledby={titleId} />}>
2223
<Card.Header>
2324
<Stack direction="row" align="baseline" justify="space-between" gap="md" wrap="wrap">
24-
<Card.Title id={titleId} render={<h2 />}>Updating to latest trunk</Card.Title>
25+
<Card.Title id={titleId} render={<h2 />}>{__('Updating to latest trunk')}</Card.Title>
2526
<Text variant="body-sm" className="muted-label">{count}</Text>
2627
</Stack>
2728
</Card.Header>

‎src/renderer/hooks/use-apply-patch.jsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { describeApplyFailure, otherPatchCount } from '../apply-conflict.cjs';
44
import { applyDoneMessage } from '../confirmations.cjs';
55
import { prCheckoutRefusal } from '../pr-checkout.cjs';
66
import { savedPrForSwitch } from '../ticket-branch-list.cjs';
7-
import { planApplySteps, updateStepStatuses, planWatchImpact, planTicketSwitchImpact, SKIP_INSTALL_MESSAGE, APPLY_STATE_TO_STEP } from '../update-plan.cjs';
7+
import { planApplySteps, updateStepStatuses, planWatchImpact, planTicketSwitchImpact, skipInstallMessage, APPLY_STATE_TO_STEP } from '../update-plan.cjs';
88
import { compilingMessage, applyFinishMessage, resumedWatchHandOff } from '../watch-activity.cjs';
99
import { watchOccupiesBuild } from '../watch-waiters.cjs';
1010

@@ -192,7 +192,7 @@ export function useApplyPatch({ sitePath, project, workItem, showTracCards, isAc
192192
}
193193
});
194194
} else {
195-
writeToTerminal(`\n${SKIP_INSTALL_MESSAGE}\n`);
195+
writeToTerminal(`\n${skipInstallMessage()}\n`);
196196
afterInstall();
197197
}
198198
};

‎src/renderer/hooks/use-dev-server.jsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useCallback, useEffect, useRef, useState } from 'react';
2+
import { __ } from '@wordpress/i18n';
23
import { serveWithoutWatch } from '../dev-server-command.cjs';
34

45
// The site's dev server (#554): whether it is starting or up, its address, and
@@ -166,7 +167,7 @@ export function useDevServer({ sitePath, projectBuild, hasBuilt, setHasBuilt, sk
166167
// click must not queue a second server start (#488).
167168
if (devServerActiveRef.current) return;
168169
// eslint-disable-next-line no-alert -- see the note above confirmAnd in index.jsx.
169-
if (!skipInit && !hasBuilt) { alert('Please complete the full build before starting the dev server. You can also skip the wizard.'); return; }
170+
if (!skipInit && !hasBuilt) { alert(__('Please complete the full build before starting the dev server. You can also skip the wizard.')); return; }
170171
serverStartRequestedRef.current = false;
171172
devServerActiveRef.current = true;
172173
setServerFailure('');

‎src/renderer/hooks/use-trunk-update.jsx‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useRef, useState } from 'react';
2-
import { __ } from '@wordpress/i18n';
2+
import { __, sprintf } from '@wordpress/i18n';
33
import { savedAndResetMessage } from '../confirmations.cjs';
4-
import { planUpdateSteps, updateStepStatuses, SKIP_INSTALL_MESSAGE, planWatchImpact } from '../update-plan.cjs';
4+
import { planUpdateSteps, updateStepStatuses, skipInstallMessage, planWatchImpact } from '../update-plan.cjs';
55
import { planUpdateHandOff } from '../update-handoff.cjs';
66
import { watchOccupiesBuild } from '../watch-waiters.cjs';
77
import { discardOutcome, DISCARD_CONFIRM_MESSAGE } from '../changes-note.cjs';
@@ -172,7 +172,7 @@ export function useTrunkUpdate({ sitePath, confirm, confirmAnd, installing, buil
172172
}
173173
});
174174
} else {
175-
writeToTerminal(`\n${SKIP_INSTALL_MESSAGE}\n`);
175+
writeToTerminal(`\n${skipInstallMessage()}\n`);
176176
afterInstall();
177177
}
178178
};
@@ -255,12 +255,14 @@ export function useTrunkUpdate({ sitePath, confirm, confirmAnd, installing, buil
255255
const res = await window.api.savePatch(sitePath);
256256
if (res && res.canceled) return; // stay in the modal
257257
if (!res || !res.ok || !res.filePath) {
258-
setDirtyError(`Error saving diff: ${res && res.error ? res.error : 'Unknown error'}`);
258+
// translators: %s: why the diff could not be saved.
259+
setDirtyError(sprintf(__('Error saving diff: %s'), res && res.error ? res.error : __('Unknown error')));
259260
return;
260261
}
261262
const d = await window.api.discardChanges(sitePath);
262263
if (!d || !d.ok) {
263-
setDirtyError(`Saved your changes to ${res.filePath}, but resetting the working tree failed: ${d && d.error ? d.error : 'Unknown error'}`);
264+
// translators: 1: the path of the saved patch file. 2: why the reset failed.
265+
setDirtyError(sprintf(__('Saved your changes to %1$s, but resetting the working tree failed: %2$s'), res.filePath, d && d.error ? d.error : __('Unknown error')));
264266
return;
265267
}
266268
savedPatchPathRef.current = res.filePath;
@@ -280,7 +282,8 @@ export function useTrunkUpdate({ sitePath, confirm, confirmAnd, installing, buil
280282
setDirtyError(null);
281283
const d = await window.api.discardChanges(sitePath);
282284
if (!d || !d.ok) {
283-
setDirtyError(`Failed to discard changes: ${d && d.error ? d.error : 'Unknown error'}`);
285+
// translators: %s: why the changes could not be discarded.
286+
setDirtyError(sprintf(__('Failed to discard changes: %s'), d && d.error ? d.error : __('Unknown error')));
284287
return;
285288
}
286289
applyDiscardToNote(discardOutcome(d));

0 commit comments

Comments
 (0)