@eifi1/ui-kit 0.38.0 → 0.39.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 (94) hide show
  1. package/README.md +6 -5
  2. package/dist/auth/auth-errors.d.ts +8 -3
  3. package/dist/auth/auth-errors.js +3 -1
  4. package/dist/auth/auth-errors.js.map +1 -1
  5. package/dist/billing/billing-labels.d.ts +25 -2
  6. package/dist/billing/billing-labels.js +11 -2
  7. package/dist/billing/billing-labels.js.map +1 -1
  8. package/dist/billing/billing-overview.d.ts +50 -1
  9. package/dist/billing/checkout-processing.d.ts +15 -3
  10. package/dist/billing/checkout-processing.js +3 -1
  11. package/dist/billing/checkout-processing.js.map +1 -1
  12. package/dist/billing/pay-page.d.ts +55 -18
  13. package/dist/billing/pay-page.js +44 -10
  14. package/dist/billing/pay-page.js.map +1 -1
  15. package/dist/billing/plan-switch.d.ts +79 -0
  16. package/dist/billing/plan-switch.js +93 -0
  17. package/dist/billing/plan-switch.js.map +1 -0
  18. package/dist/bin/eifi1-pay-page.mjs +109 -21
  19. package/dist/eslint/index.js +1 -1
  20. package/dist/feedback/feedback-comment-thread.d.ts +13 -6
  21. package/dist/feedback/feedback-comment-thread.js +10 -2
  22. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  23. package/dist/feedback/feedback-labels.d.ts +11 -0
  24. package/dist/feedback/feedback-labels.js +4 -1
  25. package/dist/feedback/feedback-labels.js.map +1 -1
  26. package/dist/feedback/feedback-public-labels.d.ts +3 -0
  27. package/dist/feedback/feedback-public-labels.js +2 -1
  28. package/dist/feedback/feedback-public-labels.js.map +1 -1
  29. package/dist/feedback/feedback-public-list.d.ts +25 -4
  30. package/dist/feedback/feedback-public-list.js +54 -3
  31. package/dist/feedback/feedback-public-list.js.map +1 -1
  32. package/dist/feedback/feedback-public-switch.js +3 -3
  33. package/dist/feedback/feedback-public-switch.js.map +1 -1
  34. package/dist/feedback/feedback-record.d.ts +7 -7
  35. package/dist/feedback/feedback-record.js.map +1 -1
  36. package/dist/feedback/feedback-replies-context.d.ts +7 -0
  37. package/dist/feedback/feedback-replies-context.js +7 -0
  38. package/dist/feedback/feedback-replies-context.js.map +1 -0
  39. package/dist/feedback/feedback-row-detail.d.ts +12 -3
  40. package/dist/feedback/feedback-row-detail.js +2 -1
  41. package/dist/feedback/feedback-row-detail.js.map +1 -1
  42. package/dist/feedback/feedback-table.js +2 -2
  43. package/dist/feedback/feedback-table.js.map +1 -1
  44. package/dist/feedback/feedback-vote-button.d.ts +2 -1
  45. package/dist/feedback/feedback-vote-button.js.map +1 -1
  46. package/dist/i18n/german.js +19 -4
  47. package/dist/i18n/german.js.map +1 -1
  48. package/dist/i18n/locales/en.js +22 -6
  49. package/dist/i18n/locales/en.js.map +2 -2
  50. package/dist/i18n/locales/es.js +19 -4
  51. package/dist/i18n/locales/es.js.map +1 -1
  52. package/dist/i18n/locales/fr.js +19 -4
  53. package/dist/i18n/locales/fr.js.map +1 -1
  54. package/dist/i18n/locales/hu.js +19 -4
  55. package/dist/i18n/locales/hu.js.map +1 -1
  56. package/dist/i18n/locales/it.js +19 -4
  57. package/dist/i18n/locales/it.js.map +1 -1
  58. package/dist/i18n/locales/zh.js +19 -4
  59. package/dist/i18n/locales/zh.js.map +1 -1
  60. package/dist/i18n/review.js +4 -0
  61. package/dist/i18n/review.js.map +1 -1
  62. package/dist/index.d.ts +6 -1
  63. package/dist/index.js +4 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/lib/translation-review.d.ts +11 -0
  66. package/dist/lib/translation-review.js.map +1 -1
  67. package/dist/pay/pay.js +102 -40
  68. package/package.json +1 -1
  69. package/src/auth/auth-errors.ts +12 -3
  70. package/src/billing/billing-labels.ts +38 -3
  71. package/src/billing/billing-overview.ts +53 -1
  72. package/src/billing/checkout-processing.ts +17 -3
  73. package/src/billing/pay-page.ts +111 -27
  74. package/src/billing/plan-switch.tsx +201 -0
  75. package/src/feedback/feedback-comment-thread.tsx +29 -7
  76. package/src/feedback/feedback-labels.ts +14 -0
  77. package/src/feedback/feedback-public-labels.ts +4 -0
  78. package/src/feedback/feedback-public-list.tsx +88 -5
  79. package/src/feedback/feedback-public-switch.tsx +3 -3
  80. package/src/feedback/feedback-record.ts +7 -7
  81. package/src/feedback/feedback-replies-context.ts +9 -0
  82. package/src/feedback/feedback-row-detail.tsx +16 -4
  83. package/src/feedback/feedback-table.tsx +2 -2
  84. package/src/feedback/feedback-vote-button.tsx +2 -1
  85. package/src/i18n/german.ts +17 -1
  86. package/src/i18n/locales/en.ts +1 -0
  87. package/src/i18n/locales/es.ts +16 -1
  88. package/src/i18n/locales/fr.ts +16 -2
  89. package/src/i18n/locales/hu.ts +16 -1
  90. package/src/i18n/locales/it.ts +16 -1
  91. package/src/i18n/locales/zh.ts +16 -1
  92. package/src/i18n/review.ts +4 -0
  93. package/src/index.ts +8 -0
  94. package/src/lib/translation-review.ts +12 -0
@@ -1,7 +1,9 @@
1
1
  import { useMemo, useState } from "react";
2
2
  import type { ReactNode } from "react";
3
- import { MessageSquare, ThumbsUp } from "lucide-react";
3
+ import { ChevronLeft, ChevronRight, MessageSquare, ThumbsUp } from "lucide-react";
4
4
  import { DataTable, type DataTableColumn } from "../components/data-table";
5
+ import { DEFAULT_DATA_TABLE_LABELS } from "../components/data-table-labels";
6
+ import { IconButton } from "../components/icon-button";
5
7
  import { SearchField } from "../components/search-field";
6
8
  import { ToggleGroup } from "../components/toggle-group";
7
9
  import type { CommitScope } from "../components/write-lock";
@@ -80,6 +82,55 @@ function VoteCount({ count, voted, labels }: { count: number; voted: boolean; la
80
82
  );
81
83
  }
82
84
 
85
+ /** 0.38.1: previous / next over the server's pages when it answers no total
86
+ * (`GET /feedback/public` — Kurvenschmiede): "Page 3" between two arrows. The arrows'
87
+ * names are `dataTable`'s, the strip's; the page's is `feedbackPublic.page`. */
88
+ function PublicPager({
89
+ page,
90
+ hasMore,
91
+ onPageChange,
92
+ labels,
93
+ }: {
94
+ page: number;
95
+ hasMore: boolean;
96
+ onPageChange: (page: number) => void;
97
+ labels: FeedbackPublicLabels;
98
+ }) {
99
+ const tableLabels = useKitLabels("dataTable", DEFAULT_DATA_TABLE_LABELS);
100
+ const name = labels.page(page + 1);
101
+ return (
102
+ <div
103
+ role="group"
104
+ aria-label={name}
105
+ className="flex items-center justify-end gap-2 text-sm text-secondary"
106
+ data-feedback-public-pager={page}
107
+ >
108
+ <IconButton
109
+ size="sm"
110
+ variant="secondary"
111
+ label={tableLabels.prevPage}
112
+ disabled={page <= 0}
113
+ onClick={() => onPageChange(page - 1)}
114
+ >
115
+ {/* "Previous" points back along the line — left in LTR, right in RTL. */}
116
+ <ChevronLeft className="rtl:-scale-x-100" />
117
+ </IconButton>
118
+ <span className="tabular-nums" aria-hidden>
119
+ {name}
120
+ </span>
121
+ <IconButton
122
+ size="sm"
123
+ variant="secondary"
124
+ label={tableLabels.nextPage}
125
+ disabled={!hasMore}
126
+ onClick={() => onPageChange(page + 1)}
127
+ >
128
+ <ChevronRight className="rtl:-scale-x-100" />
129
+ </IconButton>
130
+ </div>
131
+ );
132
+ }
133
+
83
134
  /* ── The list ─────────────────────────────────────────────────────────────── */
84
135
 
85
136
  export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = FeedbackPublicListItem> {
@@ -110,6 +161,26 @@ export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = Feed
110
161
  * keeps it itself. */
111
162
  openId?: number | null;
112
163
  onOpenChange?: (id: number | null) => void;
164
+ /**
165
+ * 0.38.1: the opened report when it is NOT on this page — a `?row=` link to an older
166
+ * one (Kurvenschmiede). The app fetches it (`GET /feedback/public/{id}`, a list item's
167
+ * fields and more) and passes it with `openId`; while `openId` is its id it is drawn
168
+ * first, above the page's rows, and opened with `renderDetail` (on the phone, in the
169
+ * full-screen dialog). Closed, it is gone. A row the page has already is never drawn
170
+ * twice: the page's own is opened instead.
171
+ */
172
+ openRow?: T | null;
173
+ /**
174
+ * 0.38.1: the server's page, from 0 — for a pager where `/feedback/public` answers no
175
+ * total (Kurvenschmiede). With {@link onPageChange} a **Previous / Page n / Next**
176
+ * strip stands under the table; Next is off without {@link hasMore}. Left out, no
177
+ * pager: the server's `limit` is all there is.
178
+ */
179
+ page?: number;
180
+ /** 0.38.1: there is a page after this one (e.g. the page came back full). */
181
+ hasMore?: boolean;
182
+ /** 0.38.1: go to another page (`page ± 1`); the app fetches it. Draws the pager. */
183
+ onPageChange?: (page: number) => void;
113
184
  /** A vote COMMITS — see Button's `commit`. */
114
185
  commit?: CommitScope;
115
186
  /** See {@link FeedbackRenderDate}. Default: the kit's, with the weekday. */
@@ -140,9 +211,10 @@ export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = Feed
140
211
  * counts; the vote itself is in the opened report (a button inside the card, which is
141
212
  * the row's own button, would be two controls in one).
142
213
  *
143
- * No paging of its own: the server's `limit` is the page, and `total` says how many
144
- * there are (an app with more than one page adds its pager below). Needs a router, as
145
- * every `DataTable` does.
214
+ * No client paging: the server's `limit` is the page, and `total` says how many there
215
+ * are. For more than one page, `page` / `hasMore` / `onPageChange` draw a previous /
216
+ * next strip that needs no total (0.38.1); `openRow` shows a `?row=` report from
217
+ * another page. Needs a router, as every `DataTable` does.
146
218
  */
147
219
  export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPublicListItem>({
148
220
  rows,
@@ -156,6 +228,10 @@ export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPu
156
228
  loading = false,
157
229
  openId: openIdProp,
158
230
  onOpenChange,
231
+ openRow,
232
+ page: pageIndex = 0,
233
+ hasMore = false,
234
+ onPageChange,
159
235
  commit,
160
236
  renderDate: renderDateProp,
161
237
  labels: labelsProp,
@@ -172,6 +248,10 @@ export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPu
172
248
  if (openIdProp === undefined) setOwnOpenId(id);
173
249
  onOpenChange?.(id);
174
250
  };
251
+ // A `?row=` report from another page: first, while it is the open one, and only when
252
+ // the page does not have it itself.
253
+ const shownRows =
254
+ openRow && openId === openRow.id && !rows.some((row) => row.id === openRow.id) ? [openRow, ...rows] : rows;
175
255
 
176
256
  const columns = useMemo<DataTableColumn<T>[]>(
177
257
  () => [
@@ -257,7 +337,7 @@ export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPu
257
337
  )}
258
338
  </div>
259
339
  <DataTable
260
- rows={rows}
340
+ rows={shownRows}
261
341
  columns={columns}
262
342
  rowKey={(row) => row.id}
263
343
  rowName={(row) => row.title}
@@ -284,6 +364,9 @@ export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPu
284
364
  </div>
285
365
  )}
286
366
  />
367
+ {onPageChange && (pageIndex > 0 || hasMore) && (
368
+ <PublicPager page={pageIndex} hasMore={hasMore} onPageChange={onPageChange} labels={labels} />
369
+ )}
287
370
  </div>
288
371
  );
289
372
  }
@@ -19,7 +19,7 @@ export interface FeedbackPublicSwitchViewer {
19
19
  /** The viewer filed the row — `mine` when the server sent it, else the ids (never for an
20
20
  * erased author, `user_id: null`). */
21
21
  function isAuthorOf(row: FeedbackPublicSwitchRow, viewerId: number | null | undefined): boolean {
22
- if (row.mine !== undefined) return row.mine;
22
+ if (row.mine != null) return row.mine;
23
23
  return row.user_id !== null && viewerId !== undefined && viewerId !== null && row.user_id === viewerId;
24
24
  }
25
25
 
@@ -83,7 +83,7 @@ export function FeedbackPublicSwitch({
83
83
  className,
84
84
  }: FeedbackPublicSwitchProps) {
85
85
  const labels = useKitLabels("feedbackPublic", DEFAULT_FEEDBACK_PUBLIC_LABELS, labelsProp);
86
- if (row.category === "CRASH" || row.public === undefined) return null;
86
+ if (row.category === "CRASH" || row.public == null) return null;
87
87
  if (!isAdmin && !isAuthorOf(row, viewerId)) return null;
88
88
  const allowed = feedbackPublicSwitchAllowed(row, { isAdmin, viewerId });
89
89
  return (
@@ -91,7 +91,7 @@ export function FeedbackPublicSwitch({
91
91
  className={className}
92
92
  label={labels.visibleToOthers}
93
93
  description={labels.visibleHint}
94
- checked={row.public}
94
+ checked={row.public === true}
95
95
  disabled={pending}
96
96
  disabledReason={allowed ? undefined : labels.visibleLocked}
97
97
  commit={commit}
@@ -117,20 +117,20 @@ export interface FeedbackRecord {
117
117
  /** The admin: the full name; the author: their own; `null`: none to show. */
118
118
  author_name?: string | null;
119
119
  /** The viewer filed it. */
120
- mine?: boolean;
120
+ mine?: boolean | null;
121
121
  /** Visible to the app's other users (§9.3); never on a CRASH. */
122
- public?: boolean;
122
+ public?: boolean | null;
123
123
  /** Made public by the 0.38 migration: the author may switch it off at any status. */
124
- filed_private?: boolean;
124
+ filed_private?: boolean | null;
125
125
  /** People who want this (the author's own vote included, §9.6). */
126
- votes?: number;
126
+ votes?: number | null;
127
127
  /** The viewer's vote. */
128
- voted?: boolean;
128
+ voted?: boolean | null;
129
129
  /** Comments (an admin's count includes hidden ones). */
130
- comment_count?: number;
130
+ comment_count?: number | null;
131
131
  /** The admin's answers, oldest first (§9.7). Absent: a server before 0.38 — read
132
132
  * `outcome` instead. */
133
- replies?: FeedbackReply[];
133
+ replies?: FeedbackReply[] | null;
134
134
  }
135
135
 
136
136
  /* ── Constants (§3.4, §3.5, §4.3) ─────────────────────────────────────────── */
@@ -0,0 +1,9 @@
1
+ import { createContext } from "react";
2
+
3
+ /**
4
+ * `true` inside `FeedbackRowDetail`'s Comments section (0.38.1, Kurvenschmiede): the
5
+ * detail's own Replies section draws the report's replies already, so a
6
+ * `FeedbackCommentThread` there draws the comments alone — whatever `replies` it was
7
+ * handed. Internal: not re-exported.
8
+ */
9
+ export const FeedbackRepliesDrawnAboveContext = createContext(false);
@@ -42,6 +42,7 @@ import type { CommitScope } from "../components/write-lock";
42
42
  import { DEFAULT_FEEDBACK_DETAIL_LABELS } from "./feedback-row-detail-labels";
43
43
  import { FeedbackReplyComposer, FeedbackReplyTimeline, feedbackReplyNudge } from "./feedback-replies";
44
44
  import { FeedbackPublicSwitch } from "./feedback-public-switch";
45
+ import { FeedbackRepliesDrawnAboveContext } from "./feedback-replies-context";
45
46
  import { DEFAULT_FEEDBACK_COMMENTS_LABELS } from "./feedback-thread-parts-labels";
46
47
 
47
48
  /**
@@ -373,6 +374,10 @@ export interface FeedbackDetailPatch {
373
374
  * answers 422; the detail no longer sends it (a reply is written instead). */
374
375
  outcome?: string | null;
375
376
  status?: FeedbackStatus;
377
+ /** 0.38.1: the **Visible to others** flip (§9.3) — what `onPublicChange`'s
378
+ * `PATCH /feedback/{id} {"public": next}` carries, alone, so an app can send it
379
+ * through the same `onUpdate` mutation. */
380
+ public?: boolean;
376
381
  }
377
382
 
378
383
  /**
@@ -582,17 +587,22 @@ export interface FeedbackRowDetailProps {
582
587
  * 0.38: `POST /feedback/{id}/replies` (§9.7) — the body and the status picked
583
588
  * (`undefined`: "No change"). With `canEdit` and a row that carries `replies`, the
584
589
  * reply composer stands under the timeline. Return the promise: the box clears when it
585
- * resolves.
590
+ * resolves. A failure is the app's to toast — the server's `detail`, else
591
+ * `feedbackToast.replyFailed` (0.38.1).
586
592
  */
587
593
  onReply?: (body: string, status?: FeedbackStatus) => void | Promise<unknown>;
588
594
  /** 0.38: the reply is in flight. */
589
595
  replying?: boolean;
590
596
  /** 0.38: the report's comments — the app's `FeedbackCommentThread` — in a section of
591
- * their own under the replies. */
597
+ * their own under the replies. A thread here draws the comments alone (0.38.1): the
598
+ * Replies section above has the replies, so whatever `replies` the thread is handed
599
+ * are dropped, and nothing is drawn twice. */
592
600
  comments?: ReactNode;
593
601
  /** 0.38: `PATCH /feedback/{id} {"public": next}` — draws the **Visible to others**
594
602
  * switch (`FeedbackPublicSwitch`) in the Status section, for the admin (`canEdit`)
595
- * and, within §9.3's window, the author. */
603
+ * and, within §9.3's window, the author. The page's own mutation fits:
604
+ * `onPublicChange={(next) => update.mutate({ id: row.id, public: next })}` —
605
+ * {@link FeedbackDetailPatch}'s `public` (0.38.1). */
596
606
  onPublicChange?: (next: boolean) => void;
597
607
  labels?: LabelOverride<FeedbackDetailLabels>;
598
608
  className?: string;
@@ -818,7 +828,9 @@ export function FeedbackRowDetail({
818
828
  </FeedbackDetailSection>
819
829
 
820
830
  {comments != null && (
821
- <FeedbackDetailSection title={commentLabels.comments}>{comments}</FeedbackDetailSection>
831
+ <FeedbackDetailSection title={commentLabels.comments}>
832
+ <FeedbackRepliesDrawnAboveContext.Provider value>{comments}</FeedbackRepliesDrawnAboveContext.Provider>
833
+ </FeedbackDetailSection>
822
834
  )}
823
835
 
824
836
  {showRework && (
@@ -472,7 +472,7 @@ export function feedbackColumns<T extends FeedbackTableRow>({
472
472
  key: "votes",
473
473
  header: votesHeader,
474
474
  className: "whitespace-nowrap",
475
- cell: (row) => <FeedbackVoteCount votes={row.votes} />,
475
+ cell: (row) => <FeedbackVoteCount votes={row.votes ?? undefined} />,
476
476
  sortBy: (row) => row.votes ?? 0,
477
477
  filter: { type: "number", getValue: (row) => row.votes ?? 0 },
478
478
  mobileHidden: true,
@@ -710,7 +710,7 @@ export function FeedbackMobileCard({
710
710
  </div>
711
711
  <div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted">
712
712
  <FeedbackCategoryBadge category={row.category} compact />
713
- {votes && <FeedbackVoteCount votes={row.votes} />}
713
+ {votes && <FeedbackVoteCount votes={row.votes ?? undefined} />}
714
714
  <FeedbackDate value={row.created_at} renderDate={renderDate} />
715
715
  {showSubmitter && (
716
716
  // The short id form: "#7" says enough on a card that has room for little.
@@ -19,7 +19,8 @@ import { DEFAULT_FEEDBACK_VOTE_LABELS } from "./feedback-thread-parts-labels";
19
19
  * - **Optimistic with rollback:** the press shows the new state at once, `onVote(next)`
20
20
  * sends `PUT` (`true`) or `DELETE` (`false`) and its answer — the server's count —
21
21
  * replaces the guess; a rejection puts back what was there. The error is the app's
22
- * to toast (its mutation's `onError`), as with every kit part that takes a promise.
22
+ * to toast (its mutation's `onError`), as with every kit part that takes a promise —
23
+ * the server's `detail`, else `feedbackToast.voteFailed` (0.38.1).
23
24
  * - **The author's own report** counts as their vote (§9.6, decision 20): `own` shows it
24
25
  * pressed and `aria-disabled` with "Your report" as the reason (the server would
25
26
  * answer 409).
@@ -113,6 +113,9 @@ export function germanLabels(numberLocale: string): UiKitLabels {
113
113
  statusChanged: (status, title) => `Auf „${status}“ gesetzt: ${title}`,
114
114
  statusUndo: "Rückgängig",
115
115
  statusRestored: (status, title) => `Zurück auf „${status}“: ${title}`,
116
+ commentFailed: "Kommentar konnte nicht gesendet werden.",
117
+ voteFailed: "Stimme konnte nicht gespeichert werden.",
118
+ replyFailed: "Antwort konnte nicht gesendet werden.",
116
119
  },
117
120
  feedbackMenu: {
118
121
  trigger: "Feedback senden",
@@ -142,6 +145,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
142
145
  visibleToOthers: "Für andere sichtbar",
143
146
  visibleHint: "Andere Personen sehen Titel und Text, nie Ihre Screenshots oder Dateien.",
144
147
  visibleLocked: "Nur solange die Meldung noch offen ist.",
148
+ page: (page) => `Seite ${n(page)}`,
145
149
  },
146
150
  feedbackNotificationTypes: {
147
151
  answered: "Antworten auf Ihre Meldungen",
@@ -1745,6 +1749,14 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1745
1749
  "Der Bezahlvorgang konnte nicht geöffnet werden. Bitte kehren Sie zurück und versuchen Sie es gleich noch einmal.",
1746
1750
  payBack:
1747
1751
  "Der Bezahlvorgang wurde geschlossen. Sie können zurückkehren oder diese Seite neu laden, um ihn wieder zu öffnen.",
1752
+ payDone: "Zahlung abgeschlossen. Sie können diese Seite schließen.",
1753
+ // 0.39 (§15.2.10): PlanSwitchConfirm.
1754
+ switchTitle: "Tarif ändern",
1755
+ switchFromTo: (from, to) => `Von ${from} zu ${to}`,
1756
+ switchChargedNow: "Jetzt belastet",
1757
+ switchCredited: "Gutschrift auf die nächsten Belastungen",
1758
+ switchNextCharge: (date) => `Nächste Belastung am ${date}`,
1759
+ switchConfirm: "Tarif ändern",
1748
1760
  waitingChanges: (count) =>
1749
1761
  count === 1
1750
1762
  ? `${n(count)} Änderung wartet auf einen Tarif`
@@ -1756,9 +1768,13 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1756
1768
  // §14.2: server-kit 0.7's refusals. "Zahlung und Rechnungen", as `manage`.
1757
1769
  providerUnavailable:
1758
1770
  "Der Zahlungsanbieter ist gerade nicht erreichbar. Bitte versuchen Sie es gleich noch einmal.",
1759
- alreadySubscribed: "Sie haben bereits ein Abonnement. Ihren Tarif ändern Sie unter „Zahlung und Rechnungen“.",
1771
+ alreadySubscribed: "Sie haben bereits ein Abonnement. Ändern Sie Ihren Tarif oben.",
1760
1772
  planNotSold:
1761
1773
  "Dieser Tarif ist für diesen Abrechnungszeitraum oder diese Währung nicht erhältlich. Bitte wählen Sie einen anderen.",
1774
+ // 0.39 (§15.2.10): the plan switch's refusals.
1775
+ notSubscribed:
1776
+ "Es läuft kein Abonnement, das sich ändern ließe. Wählen Sie einen Tarif, um eines abzuschließen.",
1777
+ planUnchanged: "Das ist bereits Ihr Tarif. Wählen Sie einen anderen Tarif oder Abrechnungszeitraum.",
1762
1778
  },
1763
1779
  // 0.33.0 — docs/billing-harmonization.md §14.12: the operator's plan parts, "Tarif" as
1764
1780
  // in `billing`; keksdose's reviewed admin wording where it had one.
@@ -54,6 +54,7 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
54
54
  total: (count) => (count === 1 ? "1 report" : `${n(count)} reports`),
55
55
  comments: (count) => (count === 1 ? "1 comment" : `${n(count)} comments`),
56
56
  votes: (count) => (count === 1 ? "1 vote" : `${n(count)} votes`),
57
+ page: (page) => `Page ${n(page)}`,
57
58
  },
58
59
  measuredGrid: {
59
60
  ...d.measuredGrid,
@@ -104,6 +104,9 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
104
104
  statusChanged: (status, title) => `Cambiado a «${status}»: ${title}`,
105
105
  statusUndo: "Deshacer",
106
106
  statusRestored: (status, title) => `De vuelta a «${status}»: ${title}`,
107
+ commentFailed: "No se ha podido publicar el comentario.",
108
+ voteFailed: "No se ha podido guardar su voto.",
109
+ replyFailed: "No se ha podido enviar la respuesta.",
107
110
  },
108
111
  feedbackMenu: {
109
112
  trigger: "Enviar comentarios",
@@ -133,6 +136,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
133
136
  visibleToOthers: "Visible para otras personas",
134
137
  visibleHint: "Otras personas ven el título y el texto, nunca sus capturas ni sus archivos.",
135
138
  visibleLocked: "Solo mientras el informe siga abierto.",
139
+ page: (page) => `Página ${n(page)}`,
136
140
  },
137
141
  feedbackNotificationTypes: {
138
142
  answered: "Respuestas a sus informes",
@@ -1695,6 +1699,14 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1695
1699
  payNothing: "Aquí no hay nada que pagar. Para elegir un plan, vuelva a la aplicación.",
1696
1700
  payFailed: "No se ha podido abrir el pago. Vuelva atrás e inténtelo de nuevo en un momento.",
1697
1701
  payBack: "El pago se ha cerrado. Puede volver atrás o recargar esta página para abrirlo de nuevo.",
1702
+ payDone: "Pago completado. Puede cerrar esta página.",
1703
+ // 0.39 (§15.2.10): PlanSwitchConfirm.
1704
+ switchTitle: "Cambiar de plan",
1705
+ switchFromTo: (from, to) => `De ${from} a ${to}`,
1706
+ switchChargedNow: "Cargo ahora",
1707
+ switchCredited: "Abonado a sus próximos cargos",
1708
+ switchNextCharge: (date) => `Próximo cargo el ${date}`,
1709
+ switchConfirm: "Cambiar de plan",
1698
1710
  waitingChanges: (count) => `${n(count)} ${plural(count, "cambio", "cambios")} a la espera de un plan`,
1699
1711
  // "Descartar", as `wizard.cancelConfirmLabel`.
1700
1712
  discardWaiting: "Descartar los cambios en espera",
@@ -1702,8 +1714,11 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1702
1714
  disabled: "Las suscripciones no están disponibles aquí.",
1703
1715
  // §14.2: server-kit 0.7's refusals. "Pagos y facturas", as `manage`. "Usted".
1704
1716
  providerUnavailable: "El proveedor de pagos no está disponible en este momento. Inténtelo de nuevo en un momento.",
1705
- alreadySubscribed: "Ya tiene una suscripción. Para cambiar de plan, vaya a «Pagos y facturas».",
1717
+ alreadySubscribed: "Ya tiene una suscripción. Cambie de plan más arriba.",
1706
1718
  planNotSold: "Este plan no está disponible para ese periodo de facturación o esa moneda. Elija otro.",
1719
+ // 0.39 (§15.2.10): the plan switch's refusals.
1720
+ notSubscribed: "No hay ninguna suscripción activa que cambiar. Elija un plan para suscribirse.",
1721
+ planUnchanged: "Ese ya es su plan. Elija otro plan u otro periodo de facturación.",
1707
1722
  },
1708
1723
  // 0.33.0 — docs/billing-harmonization.md §14.12: the operator's plan parts, "plan" as
1709
1724
  // in `billing`. "Usted".
@@ -102,6 +102,9 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
102
102
  statusChanged: (status, title) => `Passé à «\u202f${status}\u202f»\u00a0: ${title}`,
103
103
  statusUndo: "Annuler",
104
104
  statusRestored: (status, title) => `Revenu à «\u202f${status}\u202f»\u00a0: ${title}`,
105
+ commentFailed: "Le commentaire n’a pas pu être publié.",
106
+ voteFailed: "Votre vote n’a pas pu être enregistré.",
107
+ replyFailed: "La réponse n’a pas pu être envoyée.",
105
108
  },
106
109
  feedbackMenu: {
107
110
  trigger: "Envoyer un retour",
@@ -131,6 +134,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
131
134
  visibleToOthers: "Visible par les autres",
132
135
  visibleHint: "Les autres personnes voient le titre et le texte, jamais vos captures d’écran ni vos fichiers.",
133
136
  visibleLocked: "Seulement tant que le retour est encore ouvert.",
137
+ page: (page) => `Page ${n(page)}`,
134
138
  },
135
139
  feedbackNotificationTypes: {
136
140
  answered: "Réponses à vos retours",
@@ -1729,6 +1733,14 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1729
1733
  payNothing: "Il n’y a rien à payer ici. Pour choisir une formule, revenez à l’application.",
1730
1734
  payFailed: "Le paiement n’a pas pu s’ouvrir. Veuillez revenir en arrière et réessayer dans un instant.",
1731
1735
  payBack: "Le paiement a été fermé. Vous pouvez revenir en arrière, ou recharger cette page pour le rouvrir.",
1736
+ payDone: "Paiement effectué. Vous pouvez fermer cette page.",
1737
+ // 0.39 (§15.2.10): PlanSwitchConfirm.
1738
+ switchTitle: "Changer de formule",
1739
+ switchFromTo: (from, to) => `De ${from} à ${to}`,
1740
+ switchChargedNow: "Débité maintenant",
1741
+ switchCredited: "Crédité sur vos prochains paiements",
1742
+ switchNextCharge: (date) => `Prochain prélèvement le ${date}`,
1743
+ switchConfirm: "Changer de formule",
1732
1744
  waitingChanges: (count) =>
1733
1745
  `${n(count)} ${plural(count, "modification en attente", "modifications en attente")} d’une formule`,
1734
1746
  // "Abandonner", as `wizard.cancelConfirmLabel`.
@@ -1738,10 +1750,12 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1738
1750
  // §14.2: server-kit 0.7's refusals. "Paiement et factures", as `manage`.
1739
1751
  providerUnavailable:
1740
1752
  "Le prestataire de paiement est injoignable pour le moment. Veuillez réessayer dans un instant.",
1741
- alreadySubscribed:
1742
- "Vous avez déjà un abonnement. Pour changer de formule, allez dans «\u202fPaiement et factures\u202f».",
1753
+ alreadySubscribed: "Vous avez déjà un abonnement. Changez de formule ci-dessus.",
1743
1754
  planNotSold:
1744
1755
  "Cette formule n’est pas proposée pour cette période de facturation ou cette devise. Veuillez en choisir une autre.",
1756
+ // 0.39 (§15.2.10): the plan switch's refusals.
1757
+ notSubscribed: "Aucun abonnement en cours à modifier. Choisissez une formule pour vous abonner.",
1758
+ planUnchanged: "C’est déjà votre formule. Choisissez une autre formule ou une autre période de facturation.",
1745
1759
  },
1746
1760
  // 0.33.0 — docs/billing-harmonization.md §14.12: the operator's plan parts, "formule"
1747
1761
  // as in `billing`. "Vous".
@@ -101,6 +101,9 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
101
101
  statusChanged: (status, title) => `„${status}” állapotra állítva: ${title}`,
102
102
  statusUndo: "Visszavonás",
103
103
  statusRestored: (status, title) => `Vissza „${status}” állapotra: ${title}`,
104
+ commentFailed: "A hozzászólást nem sikerült elküldeni.",
105
+ voteFailed: "A szavazatot nem sikerült menteni.",
106
+ replyFailed: "A választ nem sikerült elküldeni.",
104
107
  },
105
108
  feedbackMenu: {
106
109
  trigger: "Visszajelzés küldése",
@@ -130,6 +133,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
130
133
  visibleToOthers: "Mások számára látható",
131
134
  visibleHint: "Mások a címet és a szöveget látják, a képernyőképeit és fájljait soha.",
132
135
  visibleLocked: "Csak amíg a bejelentés nyitott.",
136
+ page: (page) => `${n(page)}. oldal`,
133
137
  },
134
138
  feedbackNotificationTypes: {
135
139
  answered: "Válaszok a bejelentéseire",
@@ -1698,6 +1702,14 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1698
1702
  payNothing: "Itt nincs mit kifizetni. Csomag választásához térjen vissza az alkalmazásba.",
1699
1703
  payFailed: "A fizetést nem sikerült megnyitni. Kérjük, lépjen vissza, és próbálja újra egy kis idő múlva.",
1700
1704
  payBack: "A fizetés bezárult. Visszaléphet, vagy a megnyitásához töltse újra ezt az oldalt.",
1705
+ payDone: "A fizetés sikerült. Bezárhatja ezt az oldalt.",
1706
+ // 0.39 (§15.2.10): PlanSwitchConfirm.
1707
+ switchTitle: "Csomag módosítása",
1708
+ switchFromTo: (from, to) => `${from} helyett ${to}`,
1709
+ switchChargedNow: "Most terhelt összeg",
1710
+ switchCredited: "A következő terhelésekből levonva",
1711
+ switchNextCharge: (date) => `Következő terhelés: ${date}`,
1712
+ switchConfirm: "Csomag módosítása",
1701
1713
  // No plural after a numeral (convention 2).
1702
1714
  waitingChanges: (count) => `${n(count)} módosítás csomagra vár`,
1703
1715
  // "Elvetés", as `wizard.cancelConfirmLabel`.
@@ -1706,9 +1718,12 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1706
1718
  disabled: "Az előfizetés itt nem érhető el.",
1707
1719
  // §14.2: server-kit 0.7's refusals. "Fizetés és számlák", as `manage`.
1708
1720
  providerUnavailable: "A fizetési szolgáltató jelenleg nem érhető el. Kérjük, próbálja újra egy kis idő múlva.",
1709
- alreadySubscribed: "Már van előfizetése. A csomag módosításához nyissa meg a „Fizetés és számlák” részt.",
1721
+ alreadySubscribed: "Már van előfizetése. A csomagot fent módosíthatja.",
1710
1722
  planNotSold:
1711
1723
  "Ez a csomag ehhez a számlázási időszakhoz vagy pénznemhez nem érhető el. Kérjük, válasszon másikat.",
1724
+ // 0.39 (§15.2.10): the plan switch's refusals.
1725
+ notSubscribed: "Nincs futó előfizetés, amelyet módosítani lehetne. Előfizetéshez válasszon egy csomagot.",
1726
+ planUnchanged: "Ez már az Ön csomagja. Válasszon másik csomagot vagy számlázási időszakot.",
1712
1727
  },
1713
1728
  // 0.33.0 — docs/billing-harmonization.md §14.12: the operator's plan parts, "csomag"
1714
1729
  // as in `billing`. A plan's name takes no case ending: it follows a colon or an arrow.
@@ -104,6 +104,9 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
104
104
  statusChanged: (status, title) => `Impostato su «${status}»: ${title}`,
105
105
  statusUndo: "Annulla",
106
106
  statusRestored: (status, title) => `Tornato a «${status}»: ${title}`,
107
+ commentFailed: "Non è stato possibile pubblicare il commento.",
108
+ voteFailed: "Non è stato possibile salvare il Suo voto.",
109
+ replyFailed: "Non è stato possibile inviare la risposta.",
107
110
  },
108
111
  feedbackMenu: {
109
112
  trigger: "Invia feedback",
@@ -133,6 +136,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
133
136
  visibleToOthers: "Visibile agli altri",
134
137
  visibleHint: "Le altre persone vedono il titolo e il testo, mai i Suoi screenshot o file.",
135
138
  visibleLocked: "Solo finché la segnalazione è ancora aperta.",
139
+ page: (page) => `Pagina ${n(page)}`,
136
140
  },
137
141
  feedbackNotificationTypes: {
138
142
  answered: "Risposte alle Sue segnalazioni",
@@ -1711,6 +1715,14 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1711
1715
  payNothing: "Qui non c’è nulla da pagare. Per scegliere un piano, torni all’app.",
1712
1716
  payFailed: "Non è stato possibile aprire il pagamento. Torni indietro e riprovi tra un momento.",
1713
1717
  payBack: "Il pagamento è stato chiuso. Può tornare indietro, oppure ricaricare questa pagina per riaprirlo.",
1718
+ payDone: "Pagamento completato. Può chiudere questa pagina.",
1719
+ // 0.39 (§15.2.10): PlanSwitchConfirm.
1720
+ switchTitle: "Cambia piano",
1721
+ switchFromTo: (from, to) => `Da ${from} a ${to}`,
1722
+ switchChargedNow: "Addebitato ora",
1723
+ switchCredited: "Accreditato sui prossimi addebiti",
1724
+ switchNextCharge: (date) => `Prossimo addebito il ${date}`,
1725
+ switchConfirm: "Cambia piano",
1714
1726
  waitingChanges: (count) =>
1715
1727
  `${n(count)} ${plural(count, "modifica in attesa", "modifiche in attesa")} di un piano`,
1716
1728
  // "Scarta", as `wizard.cancelConfirmLabel`.
@@ -1719,9 +1731,12 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1719
1731
  disabled: "Gli abbonamenti non sono disponibili qui.",
1720
1732
  // §14.2: server-kit 0.7's refusals. "Pagamento e fatture", as `manage`. "Lei".
1721
1733
  providerUnavailable: "Il fornitore dei pagamenti non è raggiungibile al momento. Riprovi tra un momento.",
1722
- alreadySubscribed: "Ha già un abbonamento. Per cambiare piano, vada su «Pagamento e fatture».",
1734
+ alreadySubscribed: "Ha già un abbonamento. Cambi piano qui sopra.",
1723
1735
  planNotSold:
1724
1736
  "Questo piano non è disponibile per questo periodo di fatturazione o questa valuta. Ne scelga un altro.",
1737
+ // 0.39 (§15.2.10): the plan switch's refusals.
1738
+ notSubscribed: "Non c’è nessun abbonamento attivo da modificare. Scelga un piano per abbonarsi.",
1739
+ planUnchanged: "Questo è già il Suo piano. Scelga un altro piano o un altro periodo di fatturazione.",
1725
1740
  },
1726
1741
  // 0.33.0 — docs/billing-harmonization.md §14.12: the operator's plan parts, "piano" as
1727
1742
  // in `billing`. "Lei" in sentences, the imperative on the title and the button.
@@ -94,6 +94,9 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
94
94
  statusChanged: (status, title) => `已设为“${status}”:${title}`,
95
95
  statusUndo: "撤销",
96
96
  statusRestored: (status, title) => `已恢复为“${status}”:${title}`,
97
+ commentFailed: "无法发表评论。",
98
+ voteFailed: "无法保存您的投票。",
99
+ replyFailed: "无法发送回复。",
97
100
  },
98
101
  feedbackMenu: {
99
102
  trigger: "发送反馈",
@@ -123,6 +126,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
123
126
  visibleToOthers: "对他人可见",
124
127
  visibleHint: "其他用户可以看到标题和正文,绝不会看到您的截图或文件。",
125
128
  visibleLocked: "仅在反馈仍处于打开状态时可更改。",
129
+ page: (page) => `第 ${n(page)} 页`,
126
130
  },
127
131
  feedbackNotificationTypes: {
128
132
  answered: "您的反馈收到的回复",
@@ -1625,6 +1629,14 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1625
1629
  payNothing: "这里没有需要支付的款项。如需选择套餐,请返回应用。",
1626
1630
  payFailed: "无法打开支付页面。请返回后稍候再试。",
1627
1631
  payBack: "支付页面已关闭。您可以返回,或重新加载此页面以再次打开。",
1632
+ payDone: "付款已完成。您可以关闭此页面。",
1633
+ // 0.39 (§15.2.10): PlanSwitchConfirm.
1634
+ switchTitle: "更改套餐",
1635
+ switchFromTo: (from, to) => `从 ${from} 更改为 ${to}`,
1636
+ switchChargedNow: "立即扣款",
1637
+ switchCredited: "抵扣后续扣款",
1638
+ switchNextCharge: (date) => `下次扣款日期:${date}`,
1639
+ switchConfirm: "更改套餐",
1628
1640
  waitingChanges: (count) => `${n(count)} 项修改正在等待套餐`,
1629
1641
  // "放弃", as `wizard.cancelConfirmLabel`.
1630
1642
  discardWaiting: "放弃等待中的修改",
@@ -1632,8 +1644,11 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1632
1644
  disabled: "此处不提供订阅。",
1633
1645
  // §14.2: server-kit 0.7's refusals. "付款与发票", as `manage`.
1634
1646
  providerUnavailable: "暂时无法连接支付服务商,请稍后再试。",
1635
- alreadySubscribed: "您已有订阅。如需更改套餐,请前往“付款与发票”。",
1647
+ alreadySubscribed: "您已有订阅。请在上方更改套餐。",
1636
1648
  planNotSold: "该套餐不支持此计费周期或货币,请选择其他套餐。",
1649
+ // 0.39 (§15.2.10): the plan switch's refusals.
1650
+ notSubscribed: "没有可更改的有效订阅。请选择一个套餐进行订阅。",
1651
+ planUnchanged: "这已经是您当前的套餐。请选择其他套餐或计费周期。",
1637
1652
  },
1638
1653
  // 0.33.0 — docs/billing-harmonization.md §14.12: the operator's plan parts, "套餐" as
1639
1654
  // in `billing`. "测试版", as `landing.beta`.
@@ -216,6 +216,7 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
216
216
  "feedbackPublic.comments": one,
217
217
  "feedbackPublic.votes": one,
218
218
  "feedbackPublic.filedBy": [["{{name}}"]],
219
+ "feedbackPublic.page": [[3]],
219
220
  "accountSettings.passkeys.renameItem": [["{{name}}"]],
220
221
  "accountSettings.passkeys.renameField": [["{{name}}"]],
221
222
  "accountSettings.passkeys.deleteItem": [["{{name}}"]],
@@ -389,6 +390,9 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
389
390
  "billing.waitingChanges": one,
390
391
  // 0.33.0: the limit's figure line (§14.11) and the operator's plan parts (§14.12).
391
392
  "billing.limitUsageLine": [["{{dimension}}", "{{used}}", "{{limit}}"]],
393
+ // 0.39: PlanSwitchConfirm's two lines with figures (§15.2.10).
394
+ "billing.switchFromTo": [["{{from}}", "{{to}}"]],
395
+ "billing.switchNextCharge": [["{{date}}"]],
392
396
  "planChange.current": [["{{plan}}"]],
393
397
  "planChange.changed": [["{{from}}", "{{to}}"]],
394
398
  "planChange.set": [["{{to}}"]],
package/src/index.ts CHANGED
@@ -79,6 +79,7 @@ export type {
79
79
  ReviewStatus,
80
80
  TranslationRow,
81
81
  ApiTranslationReview,
82
+ ApiTranslationReviewsResponse,
82
83
  ApiTranslationReviewWrite,
83
84
  TranslationRowsInput,
84
85
  TranslationSummary,
@@ -842,11 +843,18 @@ export type { CheckoutOverview, CheckoutProcessingOptions } from "./billing/chec
842
843
  // The link to the pay page and Paddle's locale (§14.4); the page itself ships as dist/pay/.
843
844
  export { PAY_PAGE_LANG_PARAM, paddleLocale, payPageUrl } from "./billing/pay-page";
844
845
  export type { PaddleEnvironment } from "./billing/pay-page";
846
+ // 0.39 (§15.2.6): the apps every pay page serves, by their `billing_app` tag.
847
+ export { PAY_PAGE_APPS } from "./billing/pay-page";
848
+ export type { PayPageApp } from "./billing/pay-page";
845
849
  // The API's shapes the parts read, server-kit 0.7's (§4, §14.5), and the plan limit as
846
850
  // a toast (§14.11).
847
851
  export type { BillingOverview, SubscriptionSource, PortalTarget, PortalRequest } from "./billing/billing-overview";
848
852
  export { usePlanLimitToast } from "./billing/plan-limit-toast";
849
853
  export type { PlanLimitToastOptions } from "./billing/plan-limit-toast";
854
+ // 0.39 (§15.2.10): the payer's plan switch, its shapes, and what the payer pays.
855
+ export { PlanSwitchConfirm } from "./billing/plan-switch";
856
+ export type { PlanSwitchConfirmProps, PlanSwitchSide } from "./billing/plan-switch";
857
+ export type { PaidPrice, PlanSwitchRequest, PlanSwitchPreview } from "./billing/billing-overview";
850
858
  // The operator's plan parts (§14.12) are admin/plan-change, in the admin section.
851
859
 
852
860
  // ── settings ─────────────────────────────────────────────────────────────────