@mrrocket/sdk 0.18.0 → 0.22.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +36 -1
  3. package/dist/api-client.d.ts +28 -4
  4. package/dist/api-client.js +30 -13
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/browser/mrrocket-flows.js +8 -3
  7. package/dist/browser/mrrocket-messenger.js +38 -5
  8. package/dist/browser/mrrocket.js +1 -1
  9. package/dist/browser.d.ts +2 -2
  10. package/dist/browser.js +2 -1
  11. package/dist/browser.js.map +1 -1
  12. package/dist/client.d.ts +17 -1
  13. package/dist/client.js +25 -5
  14. package/dist/client.js.map +1 -1
  15. package/dist/core.d.ts +5 -1
  16. package/dist/core.js +1 -1
  17. package/dist/core.js.map +1 -1
  18. package/dist/feedback.js +161 -119
  19. package/dist/feedback.js.map +1 -1
  20. package/dist/flows-ui.d.ts +1 -1
  21. package/dist/flows-ui.js +100 -72
  22. package/dist/flows-ui.js.map +1 -1
  23. package/dist/flows-web.js +2 -1
  24. package/dist/flows-web.js.map +1 -1
  25. package/dist/flows.d.ts +38 -6
  26. package/dist/flows.js +51 -17
  27. package/dist/flows.js.map +1 -1
  28. package/dist/i18n.d.ts +22 -0
  29. package/dist/i18n.js +74 -0
  30. package/dist/i18n.js.map +1 -0
  31. package/dist/index.d.ts +4 -1
  32. package/dist/index.js +5 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/messenger-ui.d.ts +9 -3
  35. package/dist/messenger-ui.js +324 -152
  36. package/dist/messenger-ui.js.map +1 -1
  37. package/dist/messenger.d.ts +3 -1
  38. package/dist/messenger.js +3 -2
  39. package/dist/messenger.js.map +1 -1
  40. package/dist/onboarding-report.d.ts +19 -0
  41. package/dist/onboarding-report.js +23 -0
  42. package/dist/onboarding-report.js.map +1 -0
  43. package/dist/onboarding-state.d.ts +3 -0
  44. package/dist/onboarding-state.js +7 -1
  45. package/dist/onboarding-state.js.map +1 -1
  46. package/dist/paywall.js +2 -1
  47. package/dist/paywall.js.map +1 -1
  48. package/dist/react-native/index.d.cts +93 -8
  49. package/dist/react-native/index.d.ts +93 -8
  50. package/dist/react-native/index.js +819 -511
  51. package/dist/react-native/index.js.map +1 -1
  52. package/dist/server.d.ts +4 -2
  53. package/dist/server.js +2 -2
  54. package/dist/server.js.map +1 -1
  55. package/dist/styles-css.d.ts +1 -1
  56. package/dist/styles-css.js +35 -2
  57. package/dist/styles-css.js.map +1 -1
  58. package/dist/survey.js +18 -14
  59. package/dist/survey.js.map +1 -1
  60. package/dist/ui.d.ts +2 -0
  61. package/dist/ui.js +15 -12
  62. package/dist/ui.js.map +1 -1
  63. package/package.json +6 -3
package/dist/feedback.js CHANGED
@@ -7,7 +7,8 @@ import {
7
7
  useApi
8
8
  } from "./api.js";
9
9
  import { useCtx } from "./context.js";
10
- import { Chat, ChevronUp, Cross, Moon, Plus, Root, Search, StatusPill, Sun, VoteButton, similarPosts, timeAgo, useEscape } from "./ui.js";
10
+ import { currentLocale, t, tf } from "./i18n.js";
11
+ import { Chat, ChevronUp, Cross, Moon, Plus, Root, Search, StatusPill, Sun, VoteButton, similarPosts, timeAgo, useEscape, votesWord } from "./ui.js";
11
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
13
  var whoKey = (apiKey) => `ckw-board-who:${apiKey}`;
13
14
  var readWho = (apiKey) => {
@@ -101,20 +102,21 @@ var useFeedbackList = (api, opts) => {
101
102
  patch(post.id, flip);
102
103
  as.vote(post.id).then((r) => patch(post.id, (p) => ({ ...p, voted: r.voted, votes: r.votes }))).catch((e) => {
103
104
  patch(post.id, flip);
104
- setError(`Your vote was not saved. ${e.message}`);
105
+ setError(`${t("Your vote was not saved.")} ${e.message}`);
105
106
  });
106
107
  },
107
108
  [api, patch]
108
109
  );
109
- return { posts, setPosts, patch, boards, product, loading, error, setError, vote, reload: () => setTick((t) => t + 1) };
110
+ return { posts, setPosts, patch, boards, product, loading, error, setError, vote, reload: () => setTick((t2) => t2 + 1) };
110
111
  };
112
+ var authorName = (author) => author === "Team" ? t("Team") : author === "Anonymous" ? t("Anonymous") : author;
111
113
  var BOARD_TONE = { feature: "blue", bug: "red", integration: "violet", improvement: "green" };
112
114
  var TypeChip = ({ board, boards }) => /* @__PURE__ */ jsx("span", { className: "ckw-type", "data-tone": BOARD_TONE[board] ?? "gray", children: boards.find((b) => b.id === board)?.label ?? board });
113
115
  var Avatar = ({ name, team }) => /* @__PURE__ */ jsx("span", { className: "ckw-av", "data-team": team || void 0, "aria-hidden": "true", children: (name.trim()[0] ?? "?").toUpperCase() });
114
116
  var Skeleton = ({ rows = 3 }) => /* @__PURE__ */ jsx("div", { "aria-hidden": "true", children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx("div", { className: "ckw-skel" }, i)) });
115
117
  var ErrorBar = ({ message, onRetry, onClose }) => /* @__PURE__ */ jsxs("div", { className: "ckw-error", role: "alert", children: [
116
118
  /* @__PURE__ */ jsx("span", { children: message }),
117
- onRetry ? /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn", onClick: onRetry, children: "Retry" }) : onClose && /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: onClose, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx(Cross, {}) })
119
+ onRetry ? /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn", onClick: onRetry, children: t("Retry") }) : onClose && /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: onClose, "aria-label": t("Dismiss"), children: /* @__PURE__ */ jsx(Cross, {}) })
118
120
  ] });
119
121
  var Modal = ({ title, onClose, children }) => {
120
122
  const box = useRef(null);
@@ -137,7 +139,7 @@ var Modal = ({ title, onClose, children }) => {
137
139
  return /* @__PURE__ */ jsx("div", { className: "ckw-scrim", onMouseDown: (e) => e.target === e.currentTarget && onClose(), children: /* @__PURE__ */ jsxs("div", { ref: box, className: "ckw-modal", role: "dialog", "aria-modal": "true", "aria-labelledby": id, onKeyDown: trap, children: [
138
140
  /* @__PURE__ */ jsxs("div", { className: "ckw-modal-head", children: [
139
141
  /* @__PURE__ */ jsx("h2", { id, children: title }),
140
- /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-icon-btn", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(Cross, {}) })
142
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-icon-btn", onClick: onClose, "aria-label": t("Close"), children: /* @__PURE__ */ jsx(Cross, {}) })
141
143
  ] }),
142
144
  children
143
145
  ] }) });
@@ -147,7 +149,7 @@ var IdentityFields = ({ name, email, setName, setEmail }) => {
147
149
  return /* @__PURE__ */ jsxs(Fragment, { children: [
148
150
  /* @__PURE__ */ jsxs("div", { className: "ckw-pair", children: [
149
151
  /* @__PURE__ */ jsxs("div", { className: "ckw-field", children: [
150
- /* @__PURE__ */ jsx("label", { className: "ckw-label", htmlFor: `${id}-n`, children: "Your name" }),
152
+ /* @__PURE__ */ jsx("label", { className: "ckw-label", htmlFor: `${id}-n`, children: t("Your name") }),
151
153
  /* @__PURE__ */ jsx(
152
154
  "input",
153
155
  {
@@ -162,7 +164,7 @@ var IdentityFields = ({ name, email, setName, setEmail }) => {
162
164
  )
163
165
  ] }),
164
166
  /* @__PURE__ */ jsxs("div", { className: "ckw-field", children: [
165
- /* @__PURE__ */ jsx("label", { className: "ckw-label", htmlFor: `${id}-e`, children: "Email" }),
167
+ /* @__PURE__ */ jsx("label", { className: "ckw-label", htmlFor: `${id}-e`, children: t("Email") }),
166
168
  /* @__PURE__ */ jsx(
167
169
  "input",
168
170
  {
@@ -178,7 +180,7 @@ var IdentityFields = ({ name, email, setName, setEmail }) => {
178
180
  )
179
181
  ] })
180
182
  ] }),
181
- /* @__PURE__ */ jsx("p", { className: "ckw-fine", children: "Others see your first name and last initial. Your email is only for updates on what you posted or voted for." })
183
+ /* @__PURE__ */ jsx("p", { className: "ckw-fine", children: t("Others see your first name and last initial. Your email is only for updates on what you posted or voted for.") })
182
184
  ] });
183
185
  };
184
186
  var IdentityDialog = ({ identity }) => {
@@ -187,7 +189,7 @@ var IdentityDialog = ({ identity }) => {
187
189
  const ok = name.trim().length > 0 && EMAIL.test(email.trim());
188
190
  const run = identity.asking;
189
191
  if (!run) return null;
190
- return /* @__PURE__ */ jsx(Modal, { title: "Before you vote or comment", onClose: identity.cancel, children: /* @__PURE__ */ jsxs(
192
+ return /* @__PURE__ */ jsx(Modal, { title: t("Before you vote or comment"), onClose: identity.cancel, children: /* @__PURE__ */ jsxs(
191
193
  "form",
192
194
  {
193
195
  className: "ckw-modal-body",
@@ -201,8 +203,8 @@ var IdentityDialog = ({ identity }) => {
201
203
  children: [
202
204
  /* @__PURE__ */ jsx(IdentityFields, { name, email, setName, setEmail }),
203
205
  /* @__PURE__ */ jsxs("div", { className: "ckw-actions", children: [
204
- /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: identity.cancel, children: "Cancel" }),
205
- /* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", disabled: !ok, children: "Continue" })
206
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: identity.cancel, children: t("Cancel") }),
207
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", disabled: !ok, children: t("Continue") })
206
208
  ] })
207
209
  ]
208
210
  }
@@ -249,7 +251,7 @@ var SubmitForm = ({
249
251
  };
250
252
  return /* @__PURE__ */ jsxs("form", { className: bare ? "ckw-modal-body" : "ckw-form", onSubmit: submit, children: [
251
253
  /* @__PURE__ */ jsxs("div", { className: "ckw-field", children: [
252
- /* @__PURE__ */ jsx("label", { className: "ckw-label", htmlFor: `${id}-t`, children: "Title" }),
254
+ /* @__PURE__ */ jsx("label", { className: "ckw-label", htmlFor: `${id}-t`, children: t("Title") }),
253
255
  /* @__PURE__ */ jsx(
254
256
  "input",
255
257
  {
@@ -260,21 +262,22 @@ var SubmitForm = ({
260
262
  required: true,
261
263
  autoFocus: true,
262
264
  onChange: (e) => setTitle(e.target.value),
263
- placeholder: "Short, specific summary"
265
+ placeholder: t("Short, specific summary")
264
266
  }
265
267
  )
266
268
  ] }),
267
269
  dupes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ckw-dupes", role: "status", children: [
268
- /* @__PURE__ */ jsx("p", { children: "Similar requests. Vote instead of adding a duplicate:" }),
270
+ /* @__PURE__ */ jsx("p", { children: t("Similar requests. Vote instead of adding a duplicate:") }),
269
271
  dupes.map((d) => /* @__PURE__ */ jsxs("div", { className: "ckw-dupe", children: [
270
272
  /* @__PURE__ */ jsx("span", { title: d.title, children: d.title }),
271
- /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn", disabled: d.voted, onClick: () => onVote(d), children: d.voted ? "Voted" : "Vote instead" })
273
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn", disabled: d.voted, onClick: () => onVote(d), children: d.voted ? t("Voted") : t("Vote instead") })
272
274
  ] }, d.id))
273
275
  ] }),
274
276
  !compact && /* @__PURE__ */ jsxs("div", { className: "ckw-field", children: [
275
277
  /* @__PURE__ */ jsxs("label", { className: "ckw-label", htmlFor: `${id}-b`, children: [
276
- "Details ",
277
- /* @__PURE__ */ jsx("span", { className: "ckw-opt-l", children: "optional" })
278
+ t("Details"),
279
+ " ",
280
+ /* @__PURE__ */ jsx("span", { className: "ckw-opt-l", children: t("optional") })
278
281
  ] }),
279
282
  /* @__PURE__ */ jsx(
280
283
  "textarea",
@@ -284,19 +287,19 @@ var SubmitForm = ({
284
287
  value: body,
285
288
  maxLength: 2e3,
286
289
  onChange: (e) => setBody(e.target.value),
287
- placeholder: "What are you trying to do? What gets in the way?"
290
+ placeholder: t("What are you trying to do? What gets in the way?")
288
291
  }
289
292
  )
290
293
  ] }),
291
294
  boards.length > 1 && /* @__PURE__ */ jsxs("div", { className: "ckw-field", children: [
292
- /* @__PURE__ */ jsx("span", { className: "ckw-label", id: `${id}-c`, children: "Type" }),
295
+ /* @__PURE__ */ jsx("span", { className: "ckw-label", id: `${id}-c`, children: t("Type") }),
293
296
  /* @__PURE__ */ jsx("div", { className: "ckw-chips", role: "radiogroup", "aria-labelledby": `${id}-c`, style: { marginBottom: 0 }, children: boards.map((b) => /* @__PURE__ */ jsx("button", { type: "button", role: "radio", "aria-checked": chosen === b.id, className: "ckw-chip", onClick: () => setBoard(b.id), children: b.label }, b.id)) })
294
297
  ] }),
295
298
  ask && /* @__PURE__ */ jsx(IdentityFields, { name, email, setName, setEmail }),
296
299
  error && /* @__PURE__ */ jsx("div", { className: "ckw-error", role: "alert", children: error }),
297
300
  /* @__PURE__ */ jsxs("div", { className: "ckw-actions", children: [
298
- onCancel && /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: onCancel, children: "Cancel" }),
299
- /* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", disabled: busy || !ready, children: busy ? "Sending\u2026" : "Submit" })
301
+ onCancel && /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: onCancel, children: t("Cancel") }),
302
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", disabled: busy || !ready, children: busy ? t("Sending\u2026") : t("Submit") })
300
303
  ] })
301
304
  ] });
302
305
  };
@@ -313,12 +316,12 @@ var PostCard = ({
313
316
  /* @__PURE__ */ jsxs("div", { className: "ckw-meta", children: [
314
317
  /* @__PURE__ */ jsx(TypeChip, { board: post.board, boards }),
315
318
  post.status !== "review" && /* @__PURE__ */ jsx(StatusPill, { status: post.status }),
316
- /* @__PURE__ */ jsxs("span", { className: "ckw-stat", "aria-label": `${post.comments} comments`, children: [
319
+ /* @__PURE__ */ jsxs("span", { className: "ckw-stat", "aria-label": post.comments === 1 ? t("1 comment") : tf("{n} comments", { n: post.comments }), children: [
317
320
  /* @__PURE__ */ jsx(Chat, {}),
318
321
  " ",
319
322
  post.comments
320
323
  ] }),
321
- /* @__PURE__ */ jsx("span", { className: "ckw-dot-sep", children: [post.author, timeAgo(post.createdAt)].filter(Boolean).join(" \xB7 ") })
324
+ /* @__PURE__ */ jsx("span", { className: "ckw-dot-sep", children: [post.author && authorName(post.author), timeAgo(post.createdAt)].filter(Boolean).join(" \xB7 ") })
322
325
  ] })
323
326
  ] })
324
327
  ] });
@@ -331,28 +334,53 @@ var Requests = ({
331
334
  q,
332
335
  onOpen,
333
336
  onVote,
334
- onNew
337
+ onNew,
338
+ roadmap,
339
+ onRoadmap
335
340
  }) => {
336
341
  const { posts, boards } = list;
337
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs("div", { children: [
338
- /* @__PURE__ */ jsxs("div", { className: "ckw-tools", children: [
339
- /* @__PURE__ */ jsx("div", { className: "ckw-seg", role: "group", "aria-label": "Sort", children: ["top", "new"].map((s) => /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": sort === s, onClick: () => setSort(s), children: s === "top" ? "Top" : "New" }, s)) }),
340
- boards.length > 1 && /* @__PURE__ */ jsxs("div", { className: "ckw-chips ckw-chips-inline", role: "group", "aria-label": "Type", children: [
341
- /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-chip", "aria-pressed": !board, onClick: () => setBoard(void 0), children: "All" }),
342
- boards.map((b) => /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-chip", "aria-pressed": board === b.id, onClick: () => setBoard(b.id), children: b.label }, b.id))
343
- ] })
344
- ] }),
345
- /* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-busy": list.loading, children: list.loading && posts.length === 0 ? /* @__PURE__ */ jsx(Skeleton, {}) : posts.length === 0 && !list.error ? (
346
- // Things' empty look (owner, 2026-10-21): a large faint glyph, one line and the one next step, no frame.
347
- /* @__PURE__ */ jsxs("div", { className: "ckw-empty", children: [
348
- /* @__PURE__ */ jsx(EmptyGlyph, {}),
349
- /* @__PURE__ */ jsx("p", { children: q ? "No request matches your search." : board ? "Nothing of this type yet." : "No requests yet." }),
350
- !q && /* @__PURE__ */ jsxs("button", { type: "button", className: "ckw-btn ckw-btn-primary", onClick: onNew, children: [
351
- /* @__PURE__ */ jsx(Plus, {}),
352
- " Suggest the first one"
342
+ const side = !!roadmap || boards.length > 1;
343
+ return /* @__PURE__ */ jsx("div", { className: "ckw-req", children: /* @__PURE__ */ jsxs("div", { className: "ckw-req-grid", "data-side": side || void 0, children: [
344
+ /* @__PURE__ */ jsxs("div", { className: "ckw-req-main", children: [
345
+ /* @__PURE__ */ jsxs("div", { className: "ckw-tools", children: [
346
+ /* @__PURE__ */ jsx("div", { className: "ckw-seg", role: "group", "aria-label": t("Sort"), children: ["top", "new"].map((s) => /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": sort === s, onClick: () => setSort(s), children: s === "top" ? t("Top") : t("New") }, s)) }),
347
+ boards.length > 1 && /* @__PURE__ */ jsxs("div", { className: "ckw-chips ckw-chips-inline", role: "group", "aria-label": t("Type"), children: [
348
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-chip", "aria-pressed": !board, onClick: () => setBoard(void 0), children: t("All") }),
349
+ boards.map((b) => /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-chip", "aria-pressed": board === b.id, onClick: () => setBoard(b.id), children: b.label }, b.id))
350
+ ] })
351
+ ] }),
352
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-busy": list.loading, children: list.loading && posts.length === 0 ? /* @__PURE__ */ jsx(Skeleton, {}) : posts.length === 0 && !list.error ? (
353
+ // Things' empty look (owner, 2026-10-21): a large faint glyph, one line and the one next step, no frame.
354
+ /* @__PURE__ */ jsxs("div", { className: "ckw-empty", children: [
355
+ /* @__PURE__ */ jsx(EmptyGlyph, {}),
356
+ /* @__PURE__ */ jsx("p", { children: q ? t("No request matches your search.") : board ? t("Nothing of this type yet.") : t("No requests yet.") }),
357
+ !q && /* @__PURE__ */ jsxs("button", { type: "button", className: "ckw-btn ckw-btn-primary", onClick: onNew, children: [
358
+ /* @__PURE__ */ jsx(Plus, {}),
359
+ " ",
360
+ t("Suggest the first one")
361
+ ] })
353
362
  ] })
363
+ ) : /* @__PURE__ */ jsx("ul", { className: "ckw-posts", children: posts.map((p) => /* @__PURE__ */ jsx(PostCard, { post: p, boards, onOpen, onVote }, p.id)) }) })
364
+ ] }),
365
+ side && // The other half of the board, after Featurebase's and Canny's: what kind of request, and what comes next.
366
+ /* @__PURE__ */ jsxs("aside", { className: "ckw-req-side", children: [
367
+ boards.length > 1 && /* @__PURE__ */ jsxs("nav", { className: "ckw-side-part", "aria-label": t("Type"), children: [
368
+ /* @__PURE__ */ jsx("h2", { children: t("Type") }),
369
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-side-row", "aria-pressed": !board, onClick: () => setBoard(void 0), children: t("All") }),
370
+ boards.map((b) => /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-side-row", "aria-pressed": board === b.id, onClick: () => setBoard(b.id), children: b.label }, b.id))
371
+ ] }),
372
+ roadmap && /* @__PURE__ */ jsxs("section", { className: "ckw-side-part", "aria-label": t("Roadmap"), children: [
373
+ /* @__PURE__ */ jsx("h2", { children: t("Roadmap") }),
374
+ COLUMNS().filter((c) => c.status !== "review").map((c) => {
375
+ const cards = roadmap.columns[c.status] ?? [];
376
+ return /* @__PURE__ */ jsxs("button", { type: "button", className: "ckw-side-row", onClick: onRoadmap, children: [
377
+ /* @__PURE__ */ jsx("span", { className: "ckw-sdot", "data-s": c.status }),
378
+ c.label,
379
+ /* @__PURE__ */ jsx("span", { className: "ckw-count", children: cards.length })
380
+ ] }, c.status);
381
+ })
354
382
  ] })
355
- ) : /* @__PURE__ */ jsx("ul", { className: "ckw-posts", children: posts.map((p) => /* @__PURE__ */ jsx(PostCard, { post: p, boards, onOpen, onVote }, p.id)) }) })
383
+ ] })
356
384
  ] }) });
357
385
  };
358
386
  var PostDetail = ({
@@ -382,7 +410,7 @@ var PostDetail = ({
382
410
  identity.need(async (as) => {
383
411
  setBusy(true);
384
412
  setError("");
385
- const temp = { id: `tmp-${Date.now()}`, body, author: "You", isTeam: false, postedAt: (/* @__PURE__ */ new Date()).toISOString() };
413
+ const temp = { id: `tmp-${Date.now()}`, body, author: t("You"), isTeam: false, postedAt: (/* @__PURE__ */ new Date()).toISOString() };
386
414
  setComments((c) => [...c ?? [], temp]);
387
415
  setText("");
388
416
  try {
@@ -409,16 +437,17 @@ var PostDetail = ({
409
437
  /* @__PURE__ */ jsx("h2", { className: "ckw-detail-title", children: post.title }),
410
438
  /* @__PURE__ */ jsxs("div", { className: "ckw-byline", children: [
411
439
  /* @__PURE__ */ jsx(Avatar, { name: post.author ?? "?", team: post.author === "Team" }),
412
- /* @__PURE__ */ jsx("span", { children: post.author ?? "Someone" }),
440
+ /* @__PURE__ */ jsx("span", { children: post.author ? authorName(post.author) : t("Someone") }),
413
441
  /* @__PURE__ */ jsx("span", { className: "ckw-dot-sep", children: timeAgo(post.createdAt) })
414
442
  ] }),
415
443
  post.body && /* @__PURE__ */ jsx("p", { className: "ckw-body", children: post.body }),
416
444
  /* @__PURE__ */ jsxs("h3", { className: "ckw-h", children: [
417
- "Comments ",
445
+ t("Comments"),
446
+ " ",
418
447
  /* @__PURE__ */ jsx("span", { className: "ckw-count", children: count })
419
448
  ] }),
420
449
  /* @__PURE__ */ jsxs("form", { className: "ckw-composer", onSubmit: send, children: [
421
- /* @__PURE__ */ jsx("label", { className: "ckw-sr", htmlFor: id, children: "Leave a comment" }),
450
+ /* @__PURE__ */ jsx("label", { className: "ckw-sr", htmlFor: id, children: t("Leave a comment") }),
422
451
  /* @__PURE__ */ jsx(
423
452
  "textarea",
424
453
  {
@@ -426,28 +455,25 @@ var PostDetail = ({
426
455
  className: "ckw-textarea",
427
456
  value: text,
428
457
  maxLength: 1e3,
429
- placeholder: "Leave a comment",
458
+ placeholder: t("Leave a comment"),
430
459
  onChange: (e) => setText(e.target.value)
431
460
  }
432
461
  ),
433
462
  /* @__PURE__ */ jsxs("div", { className: "ckw-actions", children: [
434
- identity.known && identity.name && /* @__PURE__ */ jsxs("span", { className: "ckw-fine ckw-as", children: [
435
- "Commenting as ",
436
- identity.name
437
- ] }),
438
- /* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", disabled: busy || !text.trim(), children: "Comment" })
463
+ identity.known && identity.name && /* @__PURE__ */ jsx("span", { className: "ckw-fine ckw-as", children: tf("Commenting as {name}", { name: identity.name }) }),
464
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", disabled: busy || !text.trim(), children: t("Comment") })
439
465
  ] })
440
466
  ] }),
441
467
  error && /* @__PURE__ */ jsx(ErrorBar, { message: error, onClose: () => setError("") }),
442
468
  /* @__PURE__ */ jsxs("div", { "aria-live": "polite", children: [
443
469
  comments === null && !error && /* @__PURE__ */ jsx(Skeleton, { rows: 2 }),
444
- comments?.length === 0 && /* @__PURE__ */ jsx("p", { className: "ckw-sub", children: "No comments yet." }),
470
+ comments?.length === 0 && /* @__PURE__ */ jsx("p", { className: "ckw-sub", children: t("No comments yet.") }),
445
471
  comments?.map((c) => /* @__PURE__ */ jsxs("div", { className: "ckw-comment", children: [
446
472
  /* @__PURE__ */ jsx(Avatar, { name: c.author, team: c.isTeam }),
447
473
  /* @__PURE__ */ jsxs("div", { className: "ckw-main", children: [
448
474
  /* @__PURE__ */ jsxs("div", { children: [
449
- /* @__PURE__ */ jsx("b", { children: c.author }),
450
- c.isTeam && /* @__PURE__ */ jsx("span", { className: "ckw-team", children: "Team" }),
475
+ /* @__PURE__ */ jsx("b", { children: authorName(c.author) }),
476
+ c.isTeam && /* @__PURE__ */ jsx("span", { className: "ckw-team", children: t("Team") }),
451
477
  /* @__PURE__ */ jsx("span", { className: "ckw-time", children: timeAgo(c.postedAt) })
452
478
  ] }),
453
479
  /* @__PURE__ */ jsx("p", { children: c.body })
@@ -455,49 +481,49 @@ var PostDetail = ({
455
481
  ] }, c.id))
456
482
  ] })
457
483
  ] }),
458
- /* @__PURE__ */ jsxs("aside", { className: "ckw-side", "aria-label": "About this request", children: [
484
+ /* @__PURE__ */ jsxs("aside", { className: "ckw-side", "aria-label": t("About this request"), children: [
459
485
  /* @__PURE__ */ jsxs(
460
486
  "button",
461
487
  {
462
488
  type: "button",
463
489
  className: "ckw-upvote",
464
490
  "aria-pressed": post.voted,
465
- "aria-label": `${post.voted ? "Remove your vote" : "Upvote"}. ${post.votes} votes`,
491
+ "aria-label": `${post.voted ? t("Remove your vote") : t("Upvote")}. ${votesWord(post.votes)}`,
466
492
  onClick: () => onVote(post),
467
493
  children: [
468
494
  /* @__PURE__ */ jsx(ChevronUp, {}),
469
- /* @__PURE__ */ jsx("span", { children: post.voted ? "Upvoted" : "Upvote" }),
495
+ /* @__PURE__ */ jsx("span", { children: post.voted ? t("Upvoted") : t("Upvote") }),
470
496
  /* @__PURE__ */ jsx("b", { children: post.votes })
471
497
  ]
472
498
  }
473
499
  ),
474
500
  /* @__PURE__ */ jsxs("dl", { className: "ckw-facts", children: [
475
501
  /* @__PURE__ */ jsxs("div", { children: [
476
- /* @__PURE__ */ jsx("dt", { children: "Status" }),
502
+ /* @__PURE__ */ jsx("dt", { children: t("Status") }),
477
503
  /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(StatusPill, { status: post.status }) })
478
504
  ] }),
479
505
  /* @__PURE__ */ jsxs("div", { children: [
480
- /* @__PURE__ */ jsx("dt", { children: "Type" }),
506
+ /* @__PURE__ */ jsx("dt", { children: t("Type") }),
481
507
  /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx(TypeChip, { board: post.board, boards }) })
482
508
  ] }),
483
509
  /* @__PURE__ */ jsxs("div", { children: [
484
- /* @__PURE__ */ jsx("dt", { children: "Posted" }),
485
- /* @__PURE__ */ jsx("dd", { children: new Date(post.createdAt).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" }) })
510
+ /* @__PURE__ */ jsx("dt", { children: t("Posted") }),
511
+ /* @__PURE__ */ jsx("dd", { children: new Date(post.createdAt).toLocaleDateString(currentLocale(), { month: "short", day: "numeric", year: "numeric" }) })
486
512
  ] })
487
513
  ] })
488
514
  ] })
489
515
  ] })
490
516
  ] });
491
517
  };
492
- var COLUMNS = [
493
- { status: "review", label: "Backlog", note: "Under consideration" },
494
- { status: "planned", label: "Next up", note: "Committed and queued" },
495
- { status: "progress", label: "In progress", note: "Actively being built" },
496
- { status: "shipped", label: "Done", note: "Recently shipped" }
518
+ var COLUMNS = () => [
519
+ { status: "review", label: t("Backlog"), note: t("Under consideration") },
520
+ { status: "planned", label: t("Next up"), note: t("Committed and queued") },
521
+ { status: "progress", label: t("In progress"), note: t("Actively being built") },
522
+ { status: "shipped", label: t("Done"), note: t("Recently shipped") }
497
523
  ];
498
524
  var matchesQ = (q, ...texts) => {
499
- const t = q.trim().toLowerCase();
500
- return !t || texts.some((x) => x.toLowerCase().includes(t));
525
+ const q2 = q.trim().toLowerCase();
526
+ return !q2 || texts.some((x) => x.toLowerCase().includes(q2));
501
527
  };
502
528
  var RoadmapCard = ({
503
529
  post,
@@ -511,7 +537,7 @@ var RoadmapCard = ({
511
537
  /* @__PURE__ */ jsxs("div", { className: "ckw-card-foot", children: [
512
538
  /* @__PURE__ */ jsxs("span", { className: "ckw-who", children: [
513
539
  /* @__PURE__ */ jsx(Avatar, { name: post.author ?? "?", team: post.author === "Team" }),
514
- /* @__PURE__ */ jsx("span", { children: post.author ?? "" })
540
+ /* @__PURE__ */ jsx("span", { children: post.author ? authorName(post.author) : "" })
515
541
  ] }),
516
542
  /* @__PURE__ */ jsx(VoteButton, { post, onVote, small: true })
517
543
  ] })
@@ -541,10 +567,10 @@ var Roadmap = ({
541
567
  const vote = (post) => voteRef(post, (r) => {
542
568
  if (r) patch(post.id, (p) => ({ ...p, ...r }));
543
569
  });
544
- if (error) return /* @__PURE__ */ jsx(ErrorBar, { message: error, onRetry: () => setTick((t) => t + 1) });
570
+ if (error) return /* @__PURE__ */ jsx(ErrorBar, { message: error, onRetry: () => setTick((t2) => t2 + 1) });
545
571
  if (!data)
546
- return /* @__PURE__ */ jsx("div", { className: "ckw-cols", "aria-hidden": "true", children: COLUMNS.map((c) => /* @__PURE__ */ jsx("div", { className: "ckw-col", children: /* @__PURE__ */ jsx(Skeleton, { rows: 2 }) }, c.status)) });
547
- return /* @__PURE__ */ jsx("div", { className: "ckw-cols", children: COLUMNS.map((c) => {
572
+ return /* @__PURE__ */ jsx("div", { className: "ckw-cols", "aria-hidden": "true", children: COLUMNS().map((c) => /* @__PURE__ */ jsx("div", { className: "ckw-col", children: /* @__PURE__ */ jsx(Skeleton, { rows: 2 }) }, c.status)) });
573
+ return /* @__PURE__ */ jsx("div", { className: "ckw-cols", children: COLUMNS().map((c) => {
548
574
  const cards = (data.columns[c.status] ?? []).filter((p) => matchesQ(q, p.title, p.body));
549
575
  return /* @__PURE__ */ jsxs("section", { className: "ckw-col", "aria-label": c.label, children: [
550
576
  /* @__PURE__ */ jsxs("header", { className: "ckw-col-head", children: [
@@ -555,7 +581,7 @@ var Roadmap = ({
555
581
  ] }),
556
582
  /* @__PURE__ */ jsx("p", { children: c.note })
557
583
  ] }),
558
- cards.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ckw-col-empty", children: q ? "No match" : "Nothing here yet" }) : /* @__PURE__ */ jsx("ul", { className: "ckw-col-list", children: cards.map((p) => /* @__PURE__ */ jsx(RoadmapCard, { post: p, boards, onOpen, onVote: vote }, p.id)) })
584
+ cards.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ckw-col-empty", children: q ? t("No match") : t("Nothing here yet") }) : /* @__PURE__ */ jsx("ul", { className: "ckw-col-list", children: cards.map((p) => /* @__PURE__ */ jsx(RoadmapCard, { post: p, boards, onOpen, onVote: vote }, p.id)) })
559
585
  ] }, c.status);
560
586
  }) });
561
587
  };
@@ -568,22 +594,22 @@ var Changelog = ({ api, q }) => {
568
594
  api.changelog(ctl.signal).then((r) => (setEntries(r.entries), setError(""))).catch((e) => e.name !== "AbortError" && setError(e.message));
569
595
  return () => ctl.abort();
570
596
  }, [api, tick]);
571
- if (error) return /* @__PURE__ */ jsx(ErrorBar, { message: error, onRetry: () => setTick((t) => t + 1) });
597
+ if (error) return /* @__PURE__ */ jsx(ErrorBar, { message: error, onRetry: () => setTick((t2) => t2 + 1) });
572
598
  if (!entries) return /* @__PURE__ */ jsx(Skeleton, {});
573
599
  const shown = entries.filter((e) => matchesQ(q, e.title, e.body));
574
- if (shown.length === 0) return /* @__PURE__ */ jsx("div", { className: "ckw-empty", children: q ? "No update matches your search." : "No updates published yet." });
600
+ if (shown.length === 0) return /* @__PURE__ */ jsx("div", { className: "ckw-empty", children: q ? t("No update matches your search.") : t("No updates published yet.") });
575
601
  return /* @__PURE__ */ jsx("div", { className: "ckw-log", children: shown.map((e) => /* @__PURE__ */ jsxs("article", { className: "ckw-entry", children: [
576
- /* @__PURE__ */ jsx("time", { dateTime: e.publishedAt, children: new Date(e.publishedAt).toLocaleDateString(void 0, { month: "long", day: "numeric", year: "numeric" }) }),
602
+ /* @__PURE__ */ jsx("time", { dateTime: e.publishedAt, children: new Date(e.publishedAt).toLocaleDateString(currentLocale(), { month: "long", day: "numeric", year: "numeric" }) }),
577
603
  /* @__PURE__ */ jsxs("div", { children: [
578
604
  /* @__PURE__ */ jsx("h3", { children: e.title }),
579
605
  /* @__PURE__ */ jsx("p", { children: e.body })
580
606
  ] })
581
607
  ] }, e.id)) });
582
608
  };
583
- var TABS = [
584
- ["requests", "Feedback"],
585
- ["roadmap", "Roadmap"],
586
- ["changelog", "Changelog"]
609
+ var TABS = () => [
610
+ ["requests", t("Feedback")],
611
+ ["roadmap", t("Roadmap")],
612
+ ["changelog", t("Changelog")]
587
613
  ];
588
614
  var readUrl = () => {
589
615
  const p = new URLSearchParams(window.location.search);
@@ -604,8 +630,8 @@ var THEME_KEY = "ckw-theme";
604
630
  var systemDark = () => typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
605
631
  var EmptyGlyph = () => /* @__PURE__ */ jsx("svg", { className: "ckw-empty-icon", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 2.5c-5.25 0-9.5 3.73-9.5 8.33 0 2.47 1.23 4.69 3.18 6.21-.17 1.33-.73 2.6-1.63 3.63a.6.6 0 0 0 .5 1c2.13-.12 4-.86 5.38-1.92.67.13 1.36.2 2.07.2 5.25 0 9.5-3.73 9.5-8.33S17.25 2.5 12 2.5Zm.9 4.2-.6 3.3h2.2a.5.5 0 0 1 .4.8l-3.5 4.5a.4.4 0 0 1-.7-.35l.6-3.15H9.1a.5.5 0 0 1-.4-.8l3.5-4.65a.4.4 0 0 1 .7.35Z" }) });
606
632
  var FeedbackBoard = ({
607
- title = "Feedback",
608
- subtitle = "Tell us what to build next and vote on what matters to you.",
633
+ title = t("Feedback"),
634
+ subtitle = t("Tell us what to build next and vote on what matters to you."),
609
635
  theme,
610
636
  accent,
611
637
  header = true,
@@ -628,11 +654,19 @@ var FeedbackBoard = ({
628
654
  const list = useFeedbackList(identity.api, { board, sort, q: tab === "requests" ? q : "" });
629
655
  const { posts, setPosts, vote } = list;
630
656
  const searchRef = useRef(null);
657
+ const [plan, setPlan] = useState(null);
658
+ useEffect(() => {
659
+ if (!header) return;
660
+ const ctl = new AbortController();
661
+ identity.api.roadmap(ctl.signal).then(setPlan).catch(() => {
662
+ });
663
+ return () => ctl.abort();
664
+ }, [header, identity.api]);
631
665
  useEffect(() => {
632
666
  if (!header) return;
633
667
  try {
634
- const t = window.localStorage.getItem(THEME_KEY);
635
- if (t === "light" || t === "dark") setMode(t);
668
+ const t2 = window.localStorage.getItem(THEME_KEY);
669
+ if (t2 === "light" || t2 === "dark") setMode(t2);
636
670
  } catch {
637
671
  }
638
672
  }, [header]);
@@ -693,15 +727,16 @@ var FeedbackBoard = ({
693
727
  }).catch((e) => {
694
728
  setOpened((o) => o && o.id === p.id ? { ...o, voted: p.voted, votes: p.votes } : o);
695
729
  done?.({ voted: p.voted, votes: p.votes });
696
- list.setError(`Your vote was not saved. ${e.message}`);
730
+ list.setError(`${t("Your vote was not saved.")} ${e.message}`);
697
731
  });
698
732
  });
699
733
  const onTabKey = (e) => {
700
- const i = TABS.findIndex(([k]) => k === tab);
734
+ const tabs = TABS();
735
+ const i = tabs.findIndex(([k]) => k === tab);
701
736
  const n = e.key === "ArrowRight" ? i + 1 : e.key === "ArrowLeft" ? i - 1 : null;
702
737
  if (n === null) return;
703
738
  e.preventDefault();
704
- go(TABS[(n + TABS.length) % TABS.length][0]);
739
+ go(tabs[(n + tabs.length) % tabs.length][0]);
705
740
  };
706
741
  const product = list.product;
707
742
  const abs = (u) => u ? u.startsWith("/") ? `${host}${u}` : u : null;
@@ -710,8 +745,8 @@ var FeedbackBoard = ({
710
745
  const logo = marks[failed] ?? null;
711
746
  const name = product?.name ?? title;
712
747
  const newPost = () => (setThanks(false), setCreating(true));
713
- const searchLabel = tab === "roadmap" ? "Search roadmap" : tab === "changelog" ? "Search updates" : "Search requests";
714
- return /* @__PURE__ */ jsxs(Root, { theme: header ? dark ? "dark" : "light" : theme, accent, className: `ckw-board${fullPage ? "ckw-full" : ""}`, children: [
748
+ const searchLabel = tab === "roadmap" ? t("Search roadmap") : tab === "changelog" ? t("Search updates") : t("Search requests");
749
+ return /* @__PURE__ */ jsxs(Root, { theme: header ? dark ? "dark" : "light" : theme, accent, className: `ckw-board${fullPage ? " ckw-full" : ""}`, children: [
715
750
  header ? /* @__PURE__ */ jsx("header", { className: "ckw-top", children: /* @__PURE__ */ jsxs("div", { className: "ckw-top-row", children: [
716
751
  /* @__PURE__ */ jsxs("button", { type: "button", className: "ckw-brand", onClick: () => go("requests"), children: [
717
752
  logo ? (
@@ -720,7 +755,7 @@ var FeedbackBoard = ({
720
755
  ) : /* @__PURE__ */ jsx("span", { className: "ckw-brand-mark", "aria-hidden": "true", children: name.slice(0, 1).toUpperCase() }),
721
756
  /* @__PURE__ */ jsx("span", { children: name })
722
757
  ] }),
723
- /* @__PURE__ */ jsx("nav", { className: "ckw-nav", role: "tablist", "aria-label": "Board sections", onKeyDown: onTabKey, children: TABS.map(([k, label]) => /* @__PURE__ */ jsx(
758
+ /* @__PURE__ */ jsx("nav", { className: "ckw-nav", role: "tablist", "aria-label": t("Board sections"), onKeyDown: onTabKey, children: TABS().map(([k, label]) => /* @__PURE__ */ jsx(
724
759
  "button",
725
760
  {
726
761
  type: "button",
@@ -756,7 +791,7 @@ var FeedbackBoard = ({
756
791
  type: "search",
757
792
  className: "ckw-search-input",
758
793
  "aria-label": searchLabel,
759
- placeholder: "Search\u2026",
794
+ placeholder: t("Search\u2026"),
760
795
  value: q,
761
796
  onChange: (e) => {
762
797
  setQ(e.target.value);
@@ -773,14 +808,14 @@ var FeedbackBoard = ({
773
808
  type: "button",
774
809
  className: "ckw-icon-btn",
775
810
  onClick: flipTheme,
776
- "aria-label": dark ? "Switch to light mode" : "Switch to dark mode",
811
+ "aria-label": dark ? t("Switch to light mode") : t("Switch to dark mode"),
777
812
  children: dark ? /* @__PURE__ */ jsx(Sun, {}) : /* @__PURE__ */ jsx(Moon, {})
778
813
  }
779
814
  ),
780
- /* @__PURE__ */ jsxs("button", { type: "button", className: "ckw-btn ckw-btn-primary ckw-submit", "aria-label": "Submit feedback", onClick: newPost, children: [
815
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "ckw-btn ckw-btn-primary ckw-submit", "aria-label": t("Submit feedback"), onClick: newPost, children: [
781
816
  /* @__PURE__ */ jsx(Plus, {}),
782
817
  " ",
783
- /* @__PURE__ */ jsx("span", { children: "Submit feedback" })
818
+ /* @__PURE__ */ jsx("span", { children: t("Submit feedback") })
784
819
  ] })
785
820
  ] })
786
821
  ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -791,10 +826,11 @@ var FeedbackBoard = ({
791
826
  ] }),
792
827
  /* @__PURE__ */ jsxs("button", { type: "button", className: "ckw-btn ckw-btn-primary", onClick: newPost, children: [
793
828
  /* @__PURE__ */ jsx(Plus, {}),
794
- " Request a feature"
829
+ " ",
830
+ t("Request a feature")
795
831
  ] })
796
832
  ] }),
797
- /* @__PURE__ */ jsx("div", { className: "ckw-tabs", role: "tablist", "aria-label": "Feedback sections", onKeyDown: onTabKey, children: TABS.map(([k, label]) => /* @__PURE__ */ jsx(
833
+ /* @__PURE__ */ jsx("div", { className: "ckw-tabs", role: "tablist", "aria-label": t("Feedback sections"), onKeyDown: onTabKey, children: TABS().map(([k, label]) => /* @__PURE__ */ jsx(
798
834
  "button",
799
835
  {
800
836
  type: "button",
@@ -804,7 +840,7 @@ var FeedbackBoard = ({
804
840
  tabIndex: tab === k ? 0 : -1,
805
841
  className: "ckw-tab",
806
842
  onClick: () => go(k),
807
- children: k === "requests" ? "Requests" : label
843
+ children: k === "requests" ? t("Requests") : label
808
844
  },
809
845
  k
810
846
  )) }),
@@ -823,12 +859,12 @@ var FeedbackBoard = ({
823
859
  ] }),
824
860
  /* @__PURE__ */ jsxs("div", { className: "ckw-body-wrap", role: "tabpanel", "aria-labelledby": `ckw-tab-${tab}`, children: [
825
861
  header && !current && /* @__PURE__ */ jsxs("div", { className: "ckw-intro", children: [
826
- /* @__PURE__ */ jsx("h1", { children: tab === "roadmap" ? "Roadmap" : tab === "changelog" ? "Changelog" : "Feedback" }),
827
- /* @__PURE__ */ jsx("p", { children: tab === "roadmap" ? "What we are considering, building next and shipped." : tab === "changelog" ? "New and improved, newest first." : subtitle })
862
+ /* @__PURE__ */ jsx("h1", { children: tab === "roadmap" ? t("Roadmap") : tab === "changelog" ? t("Changelog") : t("Feedback") }),
863
+ /* @__PURE__ */ jsx("p", { children: tab === "roadmap" ? t("What we are considering, building next and shipped.") : tab === "changelog" ? t("New and improved, newest first.") : subtitle })
828
864
  ] }),
829
865
  thanks && !current && /* @__PURE__ */ jsxs("div", { className: "ckw-note", role: "status", children: [
830
- "Thanks, your request is in. You can follow it here.",
831
- /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-icon-btn", "aria-label": "Dismiss", onClick: () => setThanks(false), children: /* @__PURE__ */ jsx(Cross, {}) })
866
+ t("Thanks, your request is in. You can follow it here."),
867
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-icon-btn", "aria-label": t("Dismiss"), onClick: () => setThanks(false), children: /* @__PURE__ */ jsx(Cross, {}) })
832
868
  ] }),
833
869
  list.error && /* @__PURE__ */ jsx(ErrorBar, { message: list.error, onRetry: tab === "requests" && !current ? list.reload : void 0, onClose: () => list.setError("") }),
834
870
  current ? /* @__PURE__ */ jsx(
@@ -837,7 +873,7 @@ var FeedbackBoard = ({
837
873
  post: current,
838
874
  boards: list.boards,
839
875
  identity,
840
- backLabel: tab === "roadmap" ? "Roadmap" : "All requests",
876
+ backLabel: tab === "roadmap" ? t("Roadmap") : t("All requests"),
841
877
  onBack: () => go(tab),
842
878
  onVote: (p) => voteAny(p)
843
879
  }
@@ -852,11 +888,13 @@ var FeedbackBoard = ({
852
888
  q,
853
889
  onOpen: (p) => go("requests", p),
854
890
  onVote: (p) => voteAny(p),
855
- onNew: newPost
891
+ onNew: newPost,
892
+ roadmap: plan,
893
+ onRoadmap: () => go("roadmap")
856
894
  }
857
895
  ) : tab === "roadmap" ? /* @__PURE__ */ jsx(Roadmap, { api: identity.api, boards: list.boards, q, onOpen: (p) => go("roadmap", p), voteRef: (p, done) => voteAny(p, done) }) : /* @__PURE__ */ jsx(Changelog, { api: identity.api, q })
858
896
  ] }),
859
- creating && /* @__PURE__ */ jsx(Modal, { title: "Submit feedback", onClose: () => setCreating(false), children: /* @__PURE__ */ jsx(
897
+ creating && /* @__PURE__ */ jsx(Modal, { title: t("Submit feedback"), onClose: () => setCreating(false), children: /* @__PURE__ */ jsx(
860
898
  SubmitForm,
861
899
  {
862
900
  bare: true,
@@ -875,11 +913,14 @@ var FeedbackBoard = ({
875
913
  ) }),
876
914
  /* @__PURE__ */ jsx(IdentityDialog, { identity }),
877
915
  identity.known && !identity.fromApp && header && /* @__PURE__ */ jsxs("p", { className: "ckw-foot", children: [
878
- "Posting as ",
879
- identity.name,
880
- ".",
916
+ tf("Posting as {name}.", { name: identity.name }),
881
917
  " ",
882
- /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-linkish", onClick: identity.forget, children: "Not you?" })
918
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-linkish", onClick: identity.forget, children: t("Not you?") })
919
+ ] }),
920
+ header && /* @__PURE__ */ jsxs("a", { className: "ckw-powered", href: "https://mrrocket.dev/?utm_source=board&utm_medium=powered-by", target: "_blank", rel: "noreferrer", children: [
921
+ t("Powered by {brand}").split("{brand}")[0],
922
+ /* @__PURE__ */ jsx("b", { children: "MRRocket" }),
923
+ t("Powered by {brand}").split("{brand}")[1]
883
924
  ] })
884
925
  ] });
885
926
  };
@@ -899,12 +940,12 @@ var WidgetPanel = ({ boardUrl, onClose }) => {
899
940
  const vote = (p) => identity.need((as) => list.vote(p, as));
900
941
  return /* @__PURE__ */ jsxs(Fragment, { children: [
901
942
  /* @__PURE__ */ jsxs("div", { className: "ckw-pop-head", children: [
902
- /* @__PURE__ */ jsx("span", { children: suggesting ? "Suggest something" : "Top requests" }),
903
- /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: onClose, "aria-label": "Close feedback", children: /* @__PURE__ */ jsx(Cross, {}) })
943
+ /* @__PURE__ */ jsx("span", { children: suggesting ? t("Suggest something") : t("Top requests") }),
944
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: onClose, "aria-label": t("Close feedback"), children: /* @__PURE__ */ jsx(Cross, {}) })
904
945
  ] }),
905
946
  /* @__PURE__ */ jsxs("div", { className: "ckw-pop-body", children: [
906
947
  list.error && /* @__PURE__ */ jsx(ErrorBar, { message: list.error, onClose: () => list.setError("") }),
907
- identity.asking ? /* @__PURE__ */ jsx(WidgetIdentity, { identity }) : thanks ? /* @__PURE__ */ jsx("div", { className: "ckw-empty", role: "status", children: "Thanks, your request is in." }) : suggesting ? /* @__PURE__ */ jsx(
948
+ identity.asking ? /* @__PURE__ */ jsx(WidgetIdentity, { identity }) : thanks ? /* @__PURE__ */ jsx("div", { className: "ckw-empty", role: "status", children: t("Thanks, your request is in.") }) : suggesting ? /* @__PURE__ */ jsx(
908
949
  SubmitForm,
909
950
  {
910
951
  compact: true,
@@ -919,7 +960,7 @@ var WidgetPanel = ({ boardUrl, onClose }) => {
919
960
  setThanks(true);
920
961
  }
921
962
  }
922
- ) : list.loading && posts.length === 0 ? /* @__PURE__ */ jsx(Skeleton, {}) : posts.length === 0 && !list.error ? /* @__PURE__ */ jsx("div", { className: "ckw-empty", children: "No requests yet. Suggest the first one." }) : /* @__PURE__ */ jsx("ul", { className: "ckw-list", "aria-live": "polite", children: posts.slice(0, 5).map((p) => /* @__PURE__ */ jsxs("li", { className: "ckw-row", children: [
963
+ ) : list.loading && posts.length === 0 ? /* @__PURE__ */ jsx(Skeleton, {}) : posts.length === 0 && !list.error ? /* @__PURE__ */ jsx("div", { className: "ckw-empty", children: t("No requests yet. Suggest the first one.") }) : /* @__PURE__ */ jsx("ul", { className: "ckw-list", "aria-live": "polite", children: posts.slice(0, 5).map((p) => /* @__PURE__ */ jsxs("li", { className: "ckw-row", children: [
923
964
  /* @__PURE__ */ jsx(VoteButton, { post: p, onVote: vote }),
924
965
  /* @__PURE__ */ jsxs("div", { className: "ckw-main", children: [
925
966
  /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, overflowWrap: "anywhere" }, children: p.title }),
@@ -928,12 +969,13 @@ var WidgetPanel = ({ boardUrl, onClose }) => {
928
969
  ] }, p.id)) })
929
970
  ] }),
930
971
  /* @__PURE__ */ jsxs("div", { className: "ckw-pop-foot", children: [
931
- boardUrl ? /* @__PURE__ */ jsx("a", { href: boardUrl, children: "See all" }) : /* @__PURE__ */ jsx("span", {}),
972
+ boardUrl ? /* @__PURE__ */ jsx("a", { href: boardUrl, children: t("See all") }) : /* @__PURE__ */ jsx("span", {}),
932
973
  !suggesting && !thanks && !identity.asking && /* @__PURE__ */ jsxs("button", { type: "button", className: "ckw-btn ckw-btn-primary", onClick: () => setSuggesting(true), children: [
933
974
  /* @__PURE__ */ jsx(Plus, {}),
934
- " Suggest something"
975
+ " ",
976
+ t("Suggest something")
935
977
  ] }),
936
- thanks && /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn", onClick: () => setThanks(false), children: "Back" })
978
+ thanks && /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn", onClick: () => setThanks(false), children: t("Back") })
937
979
  ] })
938
980
  ] });
939
981
  };
@@ -956,14 +998,14 @@ var WidgetIdentity = ({ identity }) => {
956
998
  children: [
957
999
  /* @__PURE__ */ jsx(IdentityFields, { name, email, setName, setEmail }),
958
1000
  /* @__PURE__ */ jsxs("div", { className: "ckw-actions", children: [
959
- /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: identity.cancel, children: "Cancel" }),
960
- /* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", disabled: !ok, children: "Continue" })
1001
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ckw-btn ckw-btn-ghost", onClick: identity.cancel, children: t("Cancel") }),
1002
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "ckw-btn ckw-btn-primary", disabled: !ok, children: t("Continue") })
961
1003
  ] })
962
1004
  ]
963
1005
  }
964
1006
  );
965
1007
  };
966
- var FeedbackWidget = ({ position = "bottom-right", label = "Feedback", boardUrl, theme, accent }) => {
1008
+ var FeedbackWidget = ({ position = "bottom-right", label = t("Feedback"), boardUrl, theme, accent }) => {
967
1009
  const [open, setOpen] = useState(false);
968
1010
  const fab = useRef(null);
969
1011
  const pop = useRef(null);
@@ -1018,7 +1060,7 @@ var FeedbackWidget = ({ position = "bottom-right", label = "Feedback", boardUrl,
1018
1060
  ]
1019
1061
  }
1020
1062
  ),
1021
- open && /* @__PURE__ */ jsx("div", { ref: pop, className: "ckw-pop", "data-pos": position, role: "dialog", "aria-label": "Feedback", onKeyDown: trap, children: /* @__PURE__ */ jsx(WidgetPanel, { boardUrl, onClose: close }) })
1063
+ open && /* @__PURE__ */ jsx("div", { ref: pop, className: "ckw-pop", "data-pos": position, role: "dialog", "aria-label": t("Feedback"), onKeyDown: trap, children: /* @__PURE__ */ jsx(WidgetPanel, { boardUrl, onClose: close }) })
1022
1064
  ] });
1023
1065
  };
1024
1066
  export {