@conduction/docusaurus-preset 3.16.1 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/docusaurus-preset",
3
- "version": "3.16.1",
3
+ "version": "3.18.0",
4
4
  "scripts": {
5
5
  "prepack": "node scripts/prepack-bundle-css.js"
6
6
  },
@@ -37,6 +37,7 @@
37
37
  */
38
38
 
39
39
  import React from 'react';
40
+ import {translate} from '@docusaurus/Translate';
40
41
  import HexThumbnail from '../primitives/HexThumbnail';
41
42
  import Pill from '../primitives/Pill';
42
43
  import {CONTENT_TYPE_LABELS} from '../ContentTypeFilter/contentTypes';
@@ -48,7 +49,14 @@ function readOrWatch(_contentType, minutes) {
48
49
  /* No verb appended — the type line below the date (BLOG / TUTORIAL /
49
50
  WEBINAR) already signals read vs. watch, and the extra word kept
50
51
  wrapping to a second line on narrow card columns. */
51
- return `${minutes} min`;
52
+ return translate(
53
+ {
54
+ id: 'preset.contentCard.minLabel',
55
+ message: '{minutes} min',
56
+ description: 'Duration label under a content card (just minutes, no verb).',
57
+ },
58
+ {minutes},
59
+ );
52
60
  }
53
61
 
54
62
  function formatDate(date, locale = 'nl') {
@@ -168,7 +176,24 @@ export default function ContentCard({
168
176
 
169
177
  {audience.length > 0 && (
170
178
  <div className={styles.audienceLine}>
171
- For: {audience.map((a) => AUDIENCE_SHORT_LABELS[a] || a).join(', ')}
179
+ {translate(
180
+ {
181
+ id: 'preset.contentCard.audienceFor',
182
+ message: 'For: {list}',
183
+ description: 'Audience line under a content card. {list} is a comma-separated audience list.',
184
+ },
185
+ {
186
+ list: audience
187
+ .map((a) => translate(
188
+ {
189
+ id: `preset.audience.short.${a}`,
190
+ message: AUDIENCE_SHORT_LABELS[a] || a,
191
+ description: `Short audience label used in the "For: ..." line. Slug: ${a}.`,
192
+ },
193
+ ))
194
+ .join(', '),
195
+ },
196
+ )}
172
197
  </div>
173
198
  )}
174
199
 
@@ -51,6 +51,7 @@
51
51
  */
52
52
 
53
53
  import React from 'react';
54
+ import {translate} from '@docusaurus/Translate';
54
55
  import {
55
56
  CONTENT_TYPES,
56
57
  CONTENT_TYPE_PLURAL_LABELS,
@@ -64,12 +65,17 @@ export default function ContentTypeFilter({
64
65
  onChange,
65
66
  hrefForType,
66
67
  counts,
67
- allLabel = 'Everything',
68
+ allLabel,
68
69
  allCount,
69
70
  types = CONTENT_TYPES,
70
71
  labels,
71
72
  className,
72
73
  }) {
74
+ const resolvedAllLabel = allLabel ?? translate({
75
+ id: 'preset.contentTypeFilter.all',
76
+ message: 'Everything',
77
+ description: 'Default label on the "all" chip of the content type filter when no allLabel is passed',
78
+ });
73
79
  const isLinkMode = typeof hrefForType === 'function';
74
80
  const labelFor = (key) => {
75
81
  if (labels && labels[key]) return labels[key];
@@ -116,7 +122,7 @@ export default function ContentTypeFilter({
116
122
 
117
123
  return (
118
124
  <div className={[styles.row, className].filter(Boolean).join(' ')}>
119
- {renderChip(ALL, allLabel, allCount)}
125
+ {renderChip(ALL, resolvedAllLabel, allCount)}
120
126
  {types.map((t) =>
121
127
  renderChip(t, labelFor(t), counts && counts[t])
122
128
  )}
@@ -27,16 +27,18 @@
27
27
  */
28
28
 
29
29
  import React from 'react';
30
+ import {translate} from '@docusaurus/Translate';
30
31
  import HexThumbnail from '../primitives/HexThumbnail';
31
32
  import HexBullet from '../primitives/HexBullet';
32
33
  import AuthorByline from '../primitives/AuthorByline';
33
- import {AUDIENCE_LABELS} from '../../data/audience';
34
+ import {AUDIENCE_SHORT_LABELS} from '../../data/audience';
34
35
  import styles from './FeaturedCard.module.css';
35
36
 
36
37
  function readOrWatch(contentType, minutes) {
37
38
  if (!minutes) return null;
38
- const verb = contentType === 'webinar' ? 'watch' : 'read';
39
- return `${minutes} min ${verb}`;
39
+ return contentType === 'webinar'
40
+ ? translate({id: 'preset.featuredCard.minWatch', message: '{minutes} min watch', description: 'Duration label for video content'}, {minutes})
41
+ : translate({id: 'preset.featuredCard.minRead', message: '{minutes} min read', description: 'Duration label for text content'}, {minutes});
40
42
  }
41
43
 
42
44
  export default function FeaturedCard({
@@ -44,7 +46,7 @@ export default function FeaturedCard({
44
46
  eyebrow,
45
47
  title,
46
48
  lede,
47
- ctaLabel = 'Read more',
49
+ ctaLabel,
48
50
  author,
49
51
  date,
50
52
  dateLabel,
@@ -61,15 +63,39 @@ export default function FeaturedCard({
61
63
  className,
62
64
  }) {
63
65
  const readWatch = readOrWatch(contentType, durationMinutes);
66
+ const resolvedCtaLabel = ctaLabel ?? translate({
67
+ id: 'preset.featuredCard.cta',
68
+ message: 'Read more',
69
+ description: 'Default CTA label on FeaturedCard when the consuming MDX does not pass ctaLabel',
70
+ });
64
71
  const moduleLabel = moduleSlug
65
72
  ? (modulePosition && moduleTotalParts
66
- ? `Part ${modulePosition} of ${moduleTotalParts}`
67
- : (modulePosition ? `Part ${modulePosition}` : null))
73
+ ? translate({id: 'preset.featuredCard.modulePartOf', message: 'Part {position} of {total}', description: 'Module-position label, e.g. "Part 2 of 4"'}, {position: modulePosition, total: moduleTotalParts})
74
+ : (modulePosition ? translate({id: 'preset.featuredCard.modulePart', message: 'Part {position}', description: 'Module-position label when total parts is unknown'}, {position: modulePosition}) : null))
68
75
  : null;
69
- const audienceLabel = audience.length > 0
70
- ? audience.map((a) => AUDIENCE_LABELS[a] || a).join(' · ')
76
+ /* Audience renders on its own "For: A, B, C" line below the meta bits,
77
+ matching ContentCard and ModuleCard. Each short label gets its own
78
+ translate() so consuming sites can localise the audience names
79
+ without forking the data file. */
80
+ const audienceLabels = audience
81
+ .map((a) => translate(
82
+ {
83
+ id: `preset.audience.short.${a}`,
84
+ message: AUDIENCE_SHORT_LABELS[a] || a,
85
+ description: `Short audience label used in the "For: ..." line. Slug: ${a}.`,
86
+ },
87
+ ));
88
+ const audienceLine = audienceLabels.length > 0
89
+ ? translate(
90
+ {
91
+ id: 'preset.featuredCard.audienceFor',
92
+ message: 'For: {list}',
93
+ description: 'Audience line under the FeaturedCard meta row. {list} is a comma-separated audience list.',
94
+ },
95
+ {list: audienceLabels.join(', ')},
96
+ )
71
97
  : null;
72
- const metaBits = [readWatch, moduleLabel, moduleSlug ? (moduleTitle || moduleSlug) : null, audienceLabel]
98
+ const metaBits = [readWatch, moduleLabel, moduleSlug ? (moduleTitle || moduleSlug) : null]
73
99
  .filter(Boolean);
74
100
  const Tag = href ? 'a' : 'div';
75
101
  const composed = [styles.card, className].filter(Boolean).join(' ');
@@ -98,6 +124,10 @@ export default function FeaturedCard({
98
124
  </div>
99
125
  )}
100
126
 
127
+ {audienceLine && (
128
+ <div className={styles.audienceLine}>{audienceLine}</div>
129
+ )}
130
+
101
131
  {(author || date) && (
102
132
  <div className={styles.meta}>
103
133
  <AuthorByline
@@ -112,9 +142,9 @@ export default function FeaturedCard({
112
142
  </div>
113
143
  )}
114
144
 
115
- {ctaLabel && (
145
+ {resolvedCtaLabel && (
116
146
  <span className={styles.cta}>
117
- <span>{ctaLabel}</span>
147
+ <span>{resolvedCtaLabel}</span>
118
148
  <span aria-hidden="true">→</span>
119
149
  </span>
120
150
  )}
@@ -70,6 +70,20 @@
70
70
  .metaBits .metaBit { white-space: nowrap; }
71
71
  .metaBits .metaSep { opacity: 0.6; }
72
72
 
73
+ /* "For: MKB, Overheid, Developers" — same line treatment as the meta
74
+ bits but its own row so the audience set never wraps into the
75
+ duration / module line. On-dark variant of the ContentCard
76
+ audienceLine. */
77
+ .audienceLine {
78
+ font-family: var(--conduction-typography-font-family-code);
79
+ font-size: 12px;
80
+ letter-spacing: 0.04em;
81
+ color: var(--c-cobalt-200);
82
+ line-height: 1.4;
83
+ margin-top: calc(var(--space-3) * -1);
84
+ margin-bottom: var(--space-4);
85
+ }
86
+
73
87
  .meta { margin-bottom: var(--space-5); }
74
88
 
75
89
  .cta {
@@ -42,6 +42,7 @@
42
42
 
43
43
  import React, {useEffect, useState, useCallback, useMemo} from 'react';
44
44
  import useIsBrowser from '@docusaurus/useIsBrowser';
45
+ import Translate, {translate} from '@docusaurus/Translate';
45
46
  import styles from './GameModal.module.css';
46
47
 
47
48
  const STORAGE_KEY = 'conduction:minigames';
@@ -142,29 +143,43 @@ export default function GameModal({games = DEFAULT_GAMES, className}) {
142
143
 
143
144
  if (!isBrowser || !open || !event) return null;
144
145
 
145
- const eyebrow = event.won ? 'Mini-game complete' : 'Game over';
146
- const title = event.title || (event.won ? 'Nice run.' : "That's all of them.");
146
+ const eyebrow = event.won
147
+ ? translate({id: 'preset.gameModal.eyebrow.won', message: 'Mini-game complete', description: 'Eyebrow above the game-over heading when the player won'})
148
+ : translate({id: 'preset.gameModal.eyebrow.lost', message: 'Game over', description: 'Eyebrow above the game-over heading when the player lost'});
149
+ const title = event.title || (event.won
150
+ ? translate({id: 'preset.gameModal.title.won', message: 'Nice run.', description: 'Default headline on the game-over modal when the player won'})
151
+ : translate({id: 'preset.gameModal.title.lost', message: "That's all of them.", description: 'Default headline on the game-over modal when the player lost'}));
147
152
  const subtitle = event.subtitle ||
148
153
  (event.won
149
- ? "You've cleared a hidden Conduction mini-game."
150
- : "Try again any time, the rain doesn't stop.");
154
+ ? translate({id: 'preset.gameModal.subtitle.won', message: "You've cleared a hidden Conduction mini-game.", description: 'Default subtitle on the game-over modal when the player won'})
155
+ : translate({id: 'preset.gameModal.subtitle.lost', message: "Try again any time, the rain doesn't stop.", description: 'Default subtitle on the game-over modal when the player lost'}));
151
156
 
152
157
  return (
153
158
  <div className={[styles.modal, className].filter(Boolean).join(' ')} role="dialog" aria-modal="true" aria-labelledby="gm-title">
154
159
  <div className={styles.overlay} onClick={close} />
155
160
  <div className={styles.panel}>
156
- <button type="button" className={styles.close} onClick={close} aria-label="Close">×</button>
161
+ <button type="button" className={styles.close} onClick={close} aria-label={translate({id: 'preset.gameModal.close', message: 'Close', description: 'Accessible label for the close (×) button on the game-over modal'})}>×</button>
157
162
  <p className={styles.eyebrow}>{eyebrow}</p>
158
163
  <h2 className={styles.title} id="gm-title">{title}</h2>
159
164
  <p className={styles.subtitle}>{subtitle}</p>
160
165
 
161
166
  {typeof event.score !== 'undefined' && (
162
- <span className={styles.scorePill}>{event.summary || `score: ${event.score}`}</span>
167
+ <span className={styles.scorePill}>{event.summary || translate({id: 'preset.gameModal.scorePill', message: 'score: {score}', description: 'Default score pill text. {score} is the numeric score.'}, {score: event.score})}</span>
163
168
  )}
164
169
 
165
170
  <div className={styles.progress}>
166
171
  <div className={styles.progressLabel}>
167
- <span><strong>{foundCount}</strong> / {total} mini-games found</span>
172
+ <span>
173
+ <Translate
174
+ id="preset.gameModal.progress.found"
175
+ description="Progress label below the game-over copy. {found} bolded count of games discovered; {total} is the total."
176
+ values={{
177
+ found: <strong>{foundCount}</strong>,
178
+ total: total,
179
+ }}>
180
+ {'{found} / {total} mini-games found'}
181
+ </Translate>
182
+ </span>
168
183
  <span>{percent}%</span>
169
184
  </div>
170
185
  <div className={styles.progressBar}>
@@ -183,13 +198,24 @@ export default function GameModal({games = DEFAULT_GAMES, className}) {
183
198
 
184
199
  <p className={styles.cta}>
185
200
  {foundCount < total
186
- ? `${total - foundCount} more game${total - foundCount === 1 ? '' : 's'} hidden somewhere. Keep clicking.`
187
- : "All five found. You read the kit."}
201
+ ? translate(
202
+ {
203
+ id: 'preset.gameModal.cta.remaining',
204
+ message: '{remaining, plural, one {# more game hidden somewhere. Keep clicking.} other {# more games hidden somewhere. Keep clicking.}}',
205
+ description: 'CTA text on the game-over modal when at least one game is still hidden. {remaining} is the count of games still hidden.',
206
+ },
207
+ {remaining: total - foundCount},
208
+ )
209
+ : translate({id: 'preset.gameModal.cta.allFound', message: 'All five found. You read the kit.', description: 'CTA text on the game-over modal when the player has discovered every mini-game.'})}
188
210
  </p>
189
211
 
190
212
  <div className={styles.actions}>
191
- <button type="button" className={styles.btnSecondary} onClick={close}>Close</button>
192
- <button type="button" className={styles.btnPrimary} onClick={replay}>Play again</button>
213
+ <button type="button" className={styles.btnSecondary} onClick={close}>
214
+ <Translate id="preset.gameModal.action.close" description="Close button label on the game-over modal">Close</Translate>
215
+ </button>
216
+ <button type="button" className={styles.btnPrimary} onClick={replay}>
217
+ <Translate id="preset.gameModal.action.replay" description="Replay button label on the game-over modal">Play again</Translate>
218
+ </button>
193
219
  </div>
194
220
  </div>
195
221
  </div>
@@ -24,6 +24,7 @@
24
24
  */
25
25
 
26
26
  import React from 'react';
27
+ import {translate} from '@docusaurus/Translate';
27
28
  import HexThumbnail from '../primitives/HexThumbnail';
28
29
  import {AUDIENCE_SHORT_LABELS} from '../../data/audience';
29
30
  import {CONTENT_TYPE_PLURAL_LABELS} from '../ContentTypeFilter/contentTypes';
@@ -86,14 +87,41 @@ export default function ModuleCard({
86
87
  the composite type summary below already signal whether this is
87
88
  something the visitor reads or watches. Keeps the date line on a
88
89
  single row in narrow columns. */
89
- const totalLabel = typeof totalMinutes === 'number' ? `${totalMinutes} min` : null;
90
+ const totalLabel = typeof totalMinutes === 'number'
91
+ ? translate(
92
+ {
93
+ id: 'preset.moduleCard.minLabel',
94
+ message: '{minutes} min',
95
+ description: 'Total duration label under a module card (just minutes, no verb).',
96
+ },
97
+ {minutes: totalMinutes},
98
+ )
99
+ : null;
90
100
  const dateLabel = formatDate(latestDate, locale);
91
101
  const dateReadLine = [dateLabel, totalLabel].filter(Boolean).join(' · ');
92
102
 
103
+ const moduleEyebrow = translate({
104
+ id: 'preset.moduleCard.moduleLabel',
105
+ message: 'MODULE',
106
+ description: 'All-caps eyebrow word in the module card type line.',
107
+ });
93
108
  const partsLabel = typeof parts === 'number'
94
- ? (parts === 1 ? '1 PART' : `${parts} PARTS`)
109
+ ? (parts === 1
110
+ ? translate({
111
+ id: 'preset.moduleCard.singlePart',
112
+ message: '1 PART',
113
+ description: 'Module-parts count when the module has exactly one part.',
114
+ })
115
+ : translate(
116
+ {
117
+ id: 'preset.moduleCard.multipleParts',
118
+ message: '{count} PARTS',
119
+ description: 'Module-parts count when the module has 2+ parts.',
120
+ },
121
+ {count: parts},
122
+ ))
95
123
  : null;
96
- const typeLine = ['MODULE', partsLabel].filter(Boolean).join(' · ');
124
+ const typeLine = [moduleEyebrow, partsLabel].filter(Boolean).join(' · ');
97
125
 
98
126
  /* Composite content-type summary on line 4. For a single-type
99
127
  module ("Tutorials") this is one word; mixed modules read
@@ -141,7 +169,24 @@ export default function ModuleCard({
141
169
 
142
170
  {audience.length > 0 && (
143
171
  <div className={styles.audienceLine}>
144
- For: {audience.map((a) => AUDIENCE_SHORT_LABELS[a] || a).join(', ')}
172
+ {translate(
173
+ {
174
+ id: 'preset.moduleCard.audienceFor',
175
+ message: 'For: {list}',
176
+ description: 'Audience line under a module card. {list} is a comma-separated audience list.',
177
+ },
178
+ {
179
+ list: audience
180
+ .map((a) => translate(
181
+ {
182
+ id: `preset.audience.short.${a}`,
183
+ message: AUDIENCE_SHORT_LABELS[a] || a,
184
+ description: `Short audience label used in the "For: ..." line. Slug: ${a}.`,
185
+ },
186
+ ))
187
+ .join(', '),
188
+ },
189
+ )}
145
190
  </div>
146
191
  )}
147
192
  </div>
@@ -34,19 +34,25 @@
34
34
  */
35
35
 
36
36
  import React from 'react';
37
+ import {translate} from '@docusaurus/Translate';
37
38
  import styles from './NextSteps.module.css';
38
39
 
39
- export function NextStep({href, title, cta = 'Read more', children, className}) {
40
+ export function NextStep({href, title, cta, children, className}) {
40
41
  const composed = [styles.card, className].filter(Boolean).join(' ');
41
42
  const Tag = href ? 'a' : 'div';
42
43
  const props = href ? {href} : {};
44
+ const resolvedCta = cta ?? translate({
45
+ id: 'preset.nextSteps.step.cta',
46
+ message: 'Read more',
47
+ description: 'Default CTA label on a NextStep card when the consuming MDX does not pass cta',
48
+ });
43
49
  return (
44
50
  <Tag className={composed} {...props}>
45
51
  {title && <div className={styles.title}>{title}</div>}
46
52
  {children && <div className={styles.body}>{children}</div>}
47
53
  {href && (
48
54
  <div className={styles.cta}>
49
- {cta}
55
+ {resolvedCta}
50
56
  <svg viewBox="0 0 24 24" aria-hidden="true" width="14" height="14"
51
57
  fill="none" stroke="currentColor" strokeWidth="2"
52
58
  strokeLinecap="round" strokeLinejoin="round">
@@ -58,12 +64,17 @@ export function NextStep({href, title, cta = 'Read more', children, className})
58
64
  );
59
65
  }
60
66
 
61
- export default function NextSteps({title = 'Next steps', lede, children, columns = 3, className}) {
67
+ export default function NextSteps({title, lede, children, columns = 3, className}) {
68
+ const resolvedTitle = title ?? translate({
69
+ id: 'preset.nextSteps.title',
70
+ message: 'Next steps',
71
+ description: 'Default section heading on NextSteps when the consuming MDX does not pass title',
72
+ });
62
73
  const composed = [styles.section, className].filter(Boolean).join(' ');
63
74
  const gridClass = [styles.grid, styles['cols-' + columns]].join(' ');
64
75
  return (
65
76
  <section className={composed}>
66
- {title && <h2 className={styles.title}>{title}</h2>}
77
+ {resolvedTitle && <h2 className={styles.title}>{resolvedTitle}</h2>}
67
78
  {lede && <p className={styles.lede}>{lede}</p>}
68
79
  <div className={gridClass}>{children}</div>
69
80
  </section>
@@ -0,0 +1,55 @@
1
+ /**
2
+ * <PartnersFor />
3
+ *
4
+ * Section that lists the partners shipping a given app or solution.
5
+ * Reuses <PartnerCard/> (full variant) for each partner, with the
6
+ * <BecomePartner/> CTA as the trailing grid cell.
7
+ *
8
+ * Pure presentation: the consuming site resolves which partners ship
9
+ * the subject (via usePartnersByApp / usePartnersBySolution) and
10
+ * passes the locale-resolved copy in. When `partners` is empty, the
11
+ * grid contains only the BecomePartner cell so the section still
12
+ * recruits without looking broken.
13
+ *
14
+ * Usage in MDX:
15
+ *
16
+ * import {PartnersFor} from '@conduction/docusaurus-preset/components';
17
+ * import {usePartnersByApp, useBecomePartner} from '@site/src/data/partners-catalog';
18
+ *
19
+ * <PartnersFor
20
+ * eyebrow="Partners"
21
+ * title="Partners shipping OpenRegister"
22
+ * lede="Implementation, hosting, and integration partners that deliver OpenRegister to their customers."
23
+ * partners={usePartnersByApp('OpenRegister')}
24
+ * becomePartner={useBecomePartner()}
25
+ * />
26
+ */
27
+
28
+ import React from 'react';
29
+ import Section from '../primitives/Section';
30
+ import SectionHead from '../primitives/SectionHead';
31
+ import PartnerCard, {PartnerGrid, BecomePartner} from '../PartnerCard/PartnerCard';
32
+
33
+ export default function PartnersFor({
34
+ eyebrow = 'Partners',
35
+ title,
36
+ lede,
37
+ partners = [],
38
+ becomePartner,
39
+ gridColumns = 3,
40
+ background = 'default',
41
+ spacing = 'default',
42
+ className,
43
+ }) {
44
+ return (
45
+ <Section background={background} spacing={spacing} className={className}>
46
+ <SectionHead eyebrow={eyebrow} title={title} lede={lede} />
47
+ <PartnerGrid columns={gridColumns}>
48
+ {partners.map((p, i) => (
49
+ <PartnerCard key={p.href || p.name || i} {...p} />
50
+ ))}
51
+ {becomePartner && <BecomePartner {...becomePartner} />}
52
+ </PartnerGrid>
53
+ </Section>
54
+ );
55
+ }
@@ -41,6 +41,7 @@ export {default as SolutionCard, SolutionGrid} from './SolutionCard/SolutionCard
41
41
  export {default as PartnerCard, PartnerGrid, BecomePartner} from './PartnerCard/PartnerCard.jsx';
42
42
  export {default as PartnerDirectory} from './PartnerDirectory/PartnerDirectory.jsx';
43
43
  export {default as PartnerSidecard} from './PartnerSidecard/PartnerSidecard.jsx';
44
+ export {default as PartnersFor} from './PartnersFor/PartnersFor.jsx';
44
45
  export {default as ManagedCommonGround} from './ManagedCommonGround/ManagedCommonGround.jsx';
45
46
  export {default as Clients, DEFAULT_CLIENTS, DEFAULT_PARTNERS} from './Clients/Clients.jsx';
46
47
  export {default as ReferenceCard, ReferenceGrid} from './ReferenceCard/ReferenceCard.jsx';
@@ -23,6 +23,7 @@ import Link from '@docusaurus/Link';
23
23
  import {useLocation} from '@docusaurus/router';
24
24
  import {useThemeConfig} from '@docusaurus/theme-common';
25
25
  import useIsBrowser from '@docusaurus/useIsBrowser';
26
+ import Translate, {translate} from '@docusaurus/Translate';
26
27
  import {brandFor} from '../brand.jsx';
27
28
  import {useLazyScript} from '../../utils/lazyScript';
28
29
  import {useLazyStylesheet} from '../../utils/lazyStylesheet';
@@ -272,21 +273,32 @@ export default function Footer() {
272
273
  they're absent on a product page. */}
273
274
  {minigamesOn && (
274
275
  <>
275
- <div className="game-hud" aria-live="polite" aria-label="Boat-sinking mini game">
276
+ <div className="game-hud" aria-live="polite" aria-label={translate({id: 'preset.footer.game.ariaLabel', message: 'Boat-sinking mini game', description: 'Accessible name for the footer minigame region'})}>
276
277
  <div className="hud-block hud-counter">
277
278
  <span className="hud-num" data-counter="">100</span>
278
- <span className="hud-label">Boats left</span>
279
+ <span className="hud-label">
280
+ <Translate id="preset.footer.game.boatsLeft" description="HUD label counting boats remaining">Boats left</Translate>
281
+ </span>
279
282
  </div>
280
283
  <div className="hud-block hud-timer">
281
284
  <span className="hud-num" data-timer="">60</span>
282
- <span className="hud-label">Seconds</span>
285
+ <span className="hud-label">
286
+ <Translate id="preset.footer.game.seconds" description="HUD label for the seconds-remaining timer">Seconds</Translate>
287
+ </span>
283
288
  </div>
284
289
  </div>
285
290
 
286
- <div className="game-over" role="dialog" aria-label="Mini game over">
287
- <p className="go-title" data-go-title="">Time's up</p>
288
- <p className="go-stat"><span data-go-sunk="">0</span> sunk</p>
289
- <button type="button" data-restart="">Play again</button>
291
+ <div className="game-over" role="dialog" aria-label={translate({id: 'preset.footer.game.overAriaLabel', message: 'Mini game over', description: 'Accessible name for the game-over dialog'})}>
292
+ <p className="go-title" data-go-title="">
293
+ <Translate id="preset.footer.game.timesUp" description="Default headline shown when the minigame timer hits zero">Time's up</Translate>
294
+ </p>
295
+ <p className="go-stat">
296
+ <span data-go-sunk="">0</span>{' '}
297
+ <Translate id="preset.footer.game.sunk" description="Suffix after the number of boats sunk in the game-over stat">sunk</Translate>
298
+ </p>
299
+ <button type="button" data-restart="">
300
+ <Translate id="preset.footer.game.playAgain" description="Button label to restart the minigame">Play again</Translate>
301
+ </button>
290
302
  </div>
291
303
  </>
292
304
  )}
@@ -337,8 +349,12 @@ export default function Footer() {
337
349
  <div className="wm">{wordmark}</div>
338
350
  )}
339
351
  <p>
340
- Open-source apps for <span className="next-blue">Nextcloud</span>. Built and
341
- maintained by Conduction in Amsterdam, released under EUPL-1.2.
352
+ <Translate
353
+ id="preset.footer.brandBlurb"
354
+ description="Footer brand-citation paragraph. {nextcloud} is the styled Nextcloud word."
355
+ values={{nextcloud: <span className="next-blue">Nextcloud</span>}}>
356
+ {'Open-source apps for {nextcloud}. Built and maintained by Conduction in Amsterdam, released under EUPL-1.2.'}
357
+ </Translate>
342
358
  </p>
343
359
  {/*
344
360
  Brand citation. The producer chain stays dot-separated
@@ -48,6 +48,7 @@ import {useLocation} from '@docusaurus/router';
48
48
  import useBaseUrl from '@docusaurus/useBaseUrl';
49
49
  import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
50
50
  import {useThemeConfig} from '@docusaurus/theme-common';
51
+ import {translate} from '@docusaurus/Translate';
51
52
  import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
52
53
  import {brandFor, productWordmark, deriveStability} from '../brand.jsx';
53
54
  import {ICONS} from '../../components/primitives/icons';
@@ -96,7 +97,7 @@ function NavItem({item, location, appVersion}) {
96
97
  className={styles.iconLink}
97
98
  target="_blank"
98
99
  rel="noopener noreferrer"
99
- aria-label={item['aria-label'] || 'GitHub repository'}
100
+ aria-label={item['aria-label'] || translate({id: 'preset.navbar.github.ariaLabel', message: 'GitHub repository', description: 'Default accessible label for the icon-only GitHub link in the navbar'})}
100
101
  title="GitHub"
101
102
  >
102
103
  <span className={styles.iconGlyph} aria-hidden="true">{ICONS.github}</span>
@@ -108,7 +109,7 @@ function NavItem({item, location, appVersion}) {
108
109
  (the Redocusaurus mount point used by every Conduction docs site).
109
110
  Sites can override via `to` or `href`. */
110
111
  if (typeIs(item, 'apiDocs')) {
111
- const label = item.label || 'API Documentation';
112
+ const label = item.label || translate({id: 'preset.navbar.apiDocs.label', message: 'API Documentation', description: 'Default label for the API Documentation navbar link when the consuming site does not set one'});
112
113
  const to = item.to || '/api';
113
114
  const href = item.href;
114
115
  const isActive = !href && (location?.pathname === to ||
@@ -225,7 +226,10 @@ export default function Navbar() {
225
226
  sub-route (e.g. /docs/intro/img/app-logo.svg). */
226
227
  const logoSrcRaw = navbar.logo?.src;
227
228
  const logoSrc = useBaseUrl(logoSrcRaw || '');
228
- const logoAlt = navbar.logo?.alt || `${navbar.title} avatar`;
229
+ const logoAlt = navbar.logo?.alt || translate(
230
+ {id: 'preset.navbar.logoAlt', message: '{title} avatar', description: 'Default alt text for the navbar logo. {title} is the site title.'},
231
+ {title: navbar.title},
232
+ );
229
233
 
230
234
  /* Split into "left links" (regular nav) and "right CTAs" (locale,
231
235
  external links, install button, GitHub icon, version pill).