@eifi1/ui-kit 0.37.0 → 0.38.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/README.md +11 -4
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-comment-thread.d.ts +80 -0
- package/dist/feedback/feedback-comment-thread.js +162 -0
- package/dist/feedback/feedback-comment-thread.js.map +1 -0
- package/dist/feedback/feedback-dialog-labels.js +2 -1
- package/dist/feedback/feedback-dialog-labels.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +13 -0
- package/dist/feedback/feedback-dialog.js +12 -4
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-menu-labels.js +2 -1
- package/dist/feedback/feedback-menu-labels.js.map +1 -1
- package/dist/feedback/feedback-menu.d.ts +17 -3
- package/dist/feedback/feedback-menu.js +11 -1
- package/dist/feedback/feedback-menu.js.map +1 -1
- package/dist/feedback/feedback-public-labels.d.ts +50 -0
- package/dist/feedback/feedback-public-labels.js +26 -0
- package/dist/feedback/feedback-public-labels.js.map +1 -0
- package/dist/feedback/feedback-public-list.d.ts +118 -0
- package/dist/feedback/feedback-public-list.js +248 -0
- package/dist/feedback/feedback-public-list.js.map +1 -0
- package/dist/feedback/feedback-public-switch.d.ts +51 -0
- package/dist/feedback/feedback-public-switch.js +50 -0
- package/dist/feedback/feedback-public-switch.js.map +1 -0
- package/dist/feedback/feedback-public-types.d.ts +75 -0
- package/dist/feedback/feedback-public-types.js +2 -0
- package/dist/feedback/feedback-public-types.js.map +1 -0
- package/dist/feedback/feedback-record.d.ts +32 -1
- package/dist/feedback/feedback-record.js +10 -0
- package/dist/feedback/feedback-record.js.map +1 -1
- package/dist/feedback/feedback-replies.d.ts +97 -0
- package/dist/feedback/feedback-replies.js +114 -0
- package/dist/feedback/feedback-replies.js.map +1 -0
- package/dist/feedback/feedback-row-detail-labels.js +1 -0
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +38 -10
- package/dist/feedback/feedback-row-detail.js +50 -50
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-submit-dialog.d.ts +3 -1
- package/dist/feedback/feedback-submit-dialog.js +6 -1
- package/dist/feedback/feedback-submit-dialog.js.map +1 -1
- package/dist/feedback/feedback-submit.d.ts +8 -0
- package/dist/feedback/feedback-submit.js +3 -2
- package/dist/feedback/feedback-submit.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +15 -5
- package/dist/feedback/feedback-table.js +46 -5
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback/feedback-thread-parts-labels.d.ts +6 -0
- package/dist/feedback/feedback-thread-parts-labels.js +36 -0
- package/dist/feedback/feedback-thread-parts-labels.js.map +1 -0
- package/dist/feedback/feedback-thread.d.ts +8 -0
- package/dist/feedback/feedback-thread.js +7 -2
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback/feedback-vote-button.d.ts +48 -0
- package/dist/feedback/feedback-vote-button.js +72 -0
- package/dist/feedback/feedback-vote-button.js.map +1 -0
- package/dist/feedback.d.ts +6 -0
- package/dist/feedback.js +6 -0
- package/dist/feedback.js.map +1 -1
- package/dist/i18n/defaults.js +12 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +64 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +16 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +79 -2
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +64 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +64 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +64 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +64 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +64 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +4 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/notifications/feedback-notification-types-labels.d.ts +15 -0
- package/dist/notifications/feedback-notification-types-labels.js +11 -0
- package/dist/notifications/feedback-notification-types-labels.js.map +1 -0
- package/dist/notifications/feedback-notification-types.d.ts +28 -0
- package/dist/notifications/feedback-notification-types.js +21 -0
- package/dist/notifications/feedback-notification-types.js.map +1 -0
- package/package.json +1 -1
- package/src/feedback/feedback-comment-thread.tsx +255 -0
- package/src/feedback/feedback-dialog-labels.ts +1 -0
- package/src/feedback/feedback-dialog.tsx +30 -2
- package/src/feedback/feedback-menu-labels.ts +1 -0
- package/src/feedback/feedback-menu.tsx +29 -3
- package/src/feedback/feedback-public-labels.ts +76 -0
- package/src/feedback/feedback-public-list.tsx +395 -0
- package/src/feedback/feedback-public-switch.tsx +102 -0
- package/src/feedback/feedback-public-types.ts +82 -0
- package/src/feedback/feedback-record.ts +39 -1
- package/src/feedback/feedback-replies.tsx +235 -0
- package/src/feedback/feedback-row-detail-labels.ts +1 -0
- package/src/feedback/feedback-row-detail.tsx +103 -74
- package/src/feedback/feedback-submit-dialog.tsx +9 -0
- package/src/feedback/feedback-submit.tsx +10 -1
- package/src/feedback/feedback-table.tsx +73 -6
- package/src/feedback/feedback-thread-parts-labels.ts +41 -0
- package/src/feedback/feedback-thread.tsx +18 -1
- package/src/feedback/feedback-vote-button.tsx +122 -0
- package/src/feedback.ts +10 -0
- package/src/i18n/defaults.ts +12 -0
- package/src/i18n/german.ts +62 -0
- package/src/i18n/kit-labels.tsx +16 -0
- package/src/i18n/locales/en.ts +7 -0
- package/src/i18n/locales/es.ts +62 -0
- package/src/i18n/locales/fr.ts +62 -0
- package/src/i18n/locales/hu.ts +62 -0
- package/src/i18n/locales/it.ts +62 -0
- package/src/i18n/locales/zh.ts +62 -0
- package/src/i18n/review.ts +4 -0
- package/src/index.ts +9 -0
- package/src/notifications/feedback-notification-types-labels.ts +24 -0
- package/src/notifications/feedback-notification-types.ts +46 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import type { MouseEvent, ReactElement, ReactNode } from "react";
|
|
3
|
-
import { ExternalLink } from "lucide-react";
|
|
3
|
+
import { ExternalLink, ThumbsUp } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
5
|
import { toLocalIso } from "../lib/dates";
|
|
6
6
|
import { EMPTY_FORMATTED_VALUE, formatDate, toDate } from "../lib/format";
|
|
@@ -33,6 +33,7 @@ import type { FeedbackStatusChange } from "./feedback-status-undo";
|
|
|
33
33
|
import { feedbackPageHref, feedbackPagePath } from "./feedback-row-detail";
|
|
34
34
|
import type { CommitScope } from "../components/write-lock";
|
|
35
35
|
import { DEFAULT_FEEDBACK_PAGE_LABELS } from "./feedback-table-labels";
|
|
36
|
+
import { DEFAULT_FEEDBACK_PUBLIC_LABELS } from "./feedback-public-labels";
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* The two feedback pages' TABLE — `/feedback` (the admin inbox) and `/my-feedback` (the
|
|
@@ -135,7 +136,17 @@ export function useFeedbackPageLabels(labels?: LabelOverride<FeedbackPageLabels>
|
|
|
135
136
|
* row type with more of them passes as it is. */
|
|
136
137
|
export type FeedbackTableRow = Pick<
|
|
137
138
|
FeedbackRecord,
|
|
138
|
-
|
|
139
|
+
| "id"
|
|
140
|
+
| "user_id"
|
|
141
|
+
| "user_email"
|
|
142
|
+
| "title"
|
|
143
|
+
| "body"
|
|
144
|
+
| "category"
|
|
145
|
+
| "status"
|
|
146
|
+
| "context"
|
|
147
|
+
| "resolved_at"
|
|
148
|
+
| "created_at"
|
|
149
|
+
| "votes"
|
|
139
150
|
>;
|
|
140
151
|
|
|
141
152
|
/** A context value as a trimmed string, or `""` — every key is optional on read (§3.2)
|
|
@@ -388,6 +399,23 @@ export interface FeedbackColumnsOptions {
|
|
|
388
399
|
labels: FeedbackPageLabels;
|
|
389
400
|
statusLabels: FeedbackStatusLabels;
|
|
390
401
|
categoryLabels: FeedbackCategoryLabels;
|
|
402
|
+
/** 0.38 (§9.6): a sortable **Votes** column before Status — `row.votes`, a missing
|
|
403
|
+
* one as 0 — hidden on the phone (the card's chip, {@link FeedbackMobileCardProps.votes},
|
|
404
|
+
* carries it there). Off by default. */
|
|
405
|
+
votes?: boolean;
|
|
406
|
+
/** The Votes header — `feedbackPublic.columnVotes`, so the inbox and the public list
|
|
407
|
+
* say the same ({@link useFeedbackColumns} reads it). Default "Votes". */
|
|
408
|
+
votesHeader?: string;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** The vote count, a quiet chip with the thumb (§9.6) — the Votes cell and the card's. */
|
|
412
|
+
function FeedbackVoteCount({ votes, className }: { votes: number | undefined; className?: string }) {
|
|
413
|
+
return (
|
|
414
|
+
<Chip size="sm" className={className} data-feedback-vote-count={votes ?? 0}>
|
|
415
|
+
<ThumbsUp className="size-3" aria-hidden />
|
|
416
|
+
<span className="tabular-nums">{votes ?? 0}</span>
|
|
417
|
+
</Chip>
|
|
418
|
+
);
|
|
391
419
|
}
|
|
392
420
|
|
|
393
421
|
/** A status's place in the chain, unknown ones last (as `feedbackCategoryRank`). */
|
|
@@ -434,8 +462,23 @@ export function feedbackColumns<T extends FeedbackTableRow>({
|
|
|
434
462
|
labels,
|
|
435
463
|
statusLabels,
|
|
436
464
|
categoryLabels,
|
|
465
|
+
votes = false,
|
|
466
|
+
votesHeader = DEFAULT_FEEDBACK_PUBLIC_LABELS.columnVotes,
|
|
437
467
|
}: FeedbackColumnsOptions): DataTableColumn<T>[] {
|
|
438
468
|
const editable = canEdit && !mine;
|
|
469
|
+
const votesColumn: DataTableColumn<T>[] = votes
|
|
470
|
+
? [
|
|
471
|
+
{
|
|
472
|
+
key: "votes",
|
|
473
|
+
header: votesHeader,
|
|
474
|
+
className: "whitespace-nowrap",
|
|
475
|
+
cell: (row) => <FeedbackVoteCount votes={row.votes} />,
|
|
476
|
+
sortBy: (row) => row.votes ?? 0,
|
|
477
|
+
filter: { type: "number", getValue: (row) => row.votes ?? 0 },
|
|
478
|
+
mobileHidden: true,
|
|
479
|
+
},
|
|
480
|
+
]
|
|
481
|
+
: [];
|
|
439
482
|
return [
|
|
440
483
|
{
|
|
441
484
|
key: "id",
|
|
@@ -521,6 +564,7 @@ export function feedbackColumns<T extends FeedbackTableRow>({
|
|
|
521
564
|
filterBy: (row) => feedbackPagePath(row.context) ?? "",
|
|
522
565
|
mobileHidden: true,
|
|
523
566
|
},
|
|
567
|
+
...votesColumn,
|
|
524
568
|
{
|
|
525
569
|
key: "status",
|
|
526
570
|
header: labels.columnStatus,
|
|
@@ -560,7 +604,7 @@ export function feedbackColumns<T extends FeedbackTableRow>({
|
|
|
560
604
|
/** What {@link useFeedbackColumns} takes: {@link FeedbackColumnsOptions} with the words
|
|
561
605
|
* read from the provider, and `renderDate` optional. */
|
|
562
606
|
export interface UseFeedbackColumnsOptions
|
|
563
|
-
extends Omit<FeedbackColumnsOptions, "renderDate" | "labels" | "statusLabels" | "categoryLabels"> {
|
|
607
|
+
extends Omit<FeedbackColumnsOptions, "renderDate" | "labels" | "statusLabels" | "categoryLabels" | "votesHeader"> {
|
|
564
608
|
/** Default: the kit's date with the weekday ({@link useFeedbackRenderDate}). */
|
|
565
609
|
renderDate?: FeedbackRenderDate;
|
|
566
610
|
/** Over the `feedbackPage` namespace. Keep it stable (module scope or memoised): it is
|
|
@@ -580,16 +624,29 @@ export function useFeedbackColumns<T extends FeedbackTableRow>({
|
|
|
580
624
|
onStatus,
|
|
581
625
|
renderDate: renderDateProp,
|
|
582
626
|
commit,
|
|
627
|
+
votes,
|
|
583
628
|
labels: labelsProp,
|
|
584
629
|
}: UseFeedbackColumnsOptions): DataTableColumn<T>[] {
|
|
585
630
|
const labels = useFeedbackPageLabels(labelsProp);
|
|
586
631
|
const statusLabels = useFeedbackStatusLabels();
|
|
587
632
|
const categoryLabels = useFeedbackCategoryLabels();
|
|
633
|
+
const votesHeader = useKitLabels("feedbackPublic", DEFAULT_FEEDBACK_PUBLIC_LABELS).columnVotes;
|
|
588
634
|
const renderDate = useFeedbackRenderDate(renderDateProp);
|
|
589
635
|
return useMemo(
|
|
590
636
|
() =>
|
|
591
|
-
feedbackColumns<T>({
|
|
592
|
-
|
|
637
|
+
feedbackColumns<T>({
|
|
638
|
+
mine,
|
|
639
|
+
canEdit,
|
|
640
|
+
onStatus,
|
|
641
|
+
renderDate,
|
|
642
|
+
commit,
|
|
643
|
+
labels,
|
|
644
|
+
statusLabels,
|
|
645
|
+
categoryLabels,
|
|
646
|
+
votes,
|
|
647
|
+
votesHeader,
|
|
648
|
+
}),
|
|
649
|
+
[mine, canEdit, onStatus, renderDate, commit, labels, statusLabels, categoryLabels, votes, votesHeader],
|
|
593
650
|
);
|
|
594
651
|
}
|
|
595
652
|
|
|
@@ -617,6 +674,9 @@ export interface FeedbackMobileCardProps {
|
|
|
617
674
|
/** See {@link FeedbackRenderDate}; the same function as the columns' and the day
|
|
618
675
|
* headers'. Default: the kit's ({@link useFeedbackRenderDate}). */
|
|
619
676
|
renderDate?: FeedbackRenderDate;
|
|
677
|
+
/** 0.38: the vote count as a chip beside the category — with `useFeedbackColumns`'
|
|
678
|
+
* `votes`, whose column the phone hides. */
|
|
679
|
+
votes?: boolean;
|
|
620
680
|
/** Over the `feedbackPage` namespace. */
|
|
621
681
|
labels?: LabelOverride<FeedbackPageLabels>;
|
|
622
682
|
}
|
|
@@ -631,7 +691,13 @@ export interface FeedbackMobileCardProps {
|
|
|
631
691
|
* chips itself: with a `mobileCard`, DataTable never renders the `mobilePrimary` cell,
|
|
632
692
|
* which is how keksdose's cards lost them.
|
|
633
693
|
*/
|
|
634
|
-
export function FeedbackMobileCard({
|
|
694
|
+
export function FeedbackMobileCard({
|
|
695
|
+
row,
|
|
696
|
+
showSubmitter = false,
|
|
697
|
+
renderDate: renderDateProp,
|
|
698
|
+
votes = false,
|
|
699
|
+
labels: labelsProp,
|
|
700
|
+
}: FeedbackMobileCardProps) {
|
|
635
701
|
const labels = useFeedbackPageLabels(labelsProp);
|
|
636
702
|
const renderDate = useFeedbackRenderDate(renderDateProp);
|
|
637
703
|
return (
|
|
@@ -644,6 +710,7 @@ export function FeedbackMobileCard({ row, showSubmitter = false, renderDate: ren
|
|
|
644
710
|
</div>
|
|
645
711
|
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted">
|
|
646
712
|
<FeedbackCategoryBadge category={row.category} compact />
|
|
713
|
+
{votes && <FeedbackVoteCount votes={row.votes} />}
|
|
647
714
|
<FeedbackDate value={row.created_at} renderDate={renderDate} />
|
|
648
715
|
{showSubmitter && (
|
|
649
716
|
// The short id form: "#7" says enough on a card that has room for little.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// The English words of round 0.38's thread parts — ./feedback-vote-button,
|
|
2
|
+
// ./feedback-comment-thread and ./feedback-replies — in a module with no component code:
|
|
3
|
+
// the English catalogue (src/i18n/defaults.ts) imports them without the components, so
|
|
4
|
+
// `en` loads as one chunk like the other languages (0.34, KS U8). Each component module
|
|
5
|
+
// re-exports its own.
|
|
6
|
+
|
|
7
|
+
import type { FeedbackCommentsLabels } from "./feedback-comment-thread";
|
|
8
|
+
import type { FeedbackRepliesLabels } from "./feedback-replies";
|
|
9
|
+
import type { FeedbackVoteLabels } from "./feedback-vote-button";
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_FEEDBACK_VOTE_LABELS: FeedbackVoteLabels = {
|
|
12
|
+
vote: "I want this too",
|
|
13
|
+
own: "Your report",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_FEEDBACK_REPLIES_LABELS: FeedbackRepliesLabels = {
|
|
17
|
+
replies: "Replies",
|
|
18
|
+
empty: "No reply yet",
|
|
19
|
+
admin: "Admin",
|
|
20
|
+
statusChanged: "Status changed to",
|
|
21
|
+
field: "Write a reply",
|
|
22
|
+
placeholder: "Write a reply…",
|
|
23
|
+
reply: "Reply",
|
|
24
|
+
status: "Status with the reply",
|
|
25
|
+
noChange: "No change",
|
|
26
|
+
nudge: "The author has not been told. Add a reply.",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_FEEDBACK_COMMENTS_LABELS: FeedbackCommentsLabels = {
|
|
30
|
+
comments: "Comments",
|
|
31
|
+
empty: "No comments yet",
|
|
32
|
+
hidden: "Hidden by an admin",
|
|
33
|
+
hide: "Hide",
|
|
34
|
+
unhide: "Unhide",
|
|
35
|
+
delete: "Delete",
|
|
36
|
+
deleteTitle: "Delete this comment?",
|
|
37
|
+
deleteBody: "It is removed for everyone. This cannot be undone.",
|
|
38
|
+
field: "Write a comment",
|
|
39
|
+
placeholder: "Write a comment…",
|
|
40
|
+
send: "Comment",
|
|
41
|
+
};
|
|
@@ -119,6 +119,14 @@ export interface FeedbackThreadMessage {
|
|
|
119
119
|
own?: boolean;
|
|
120
120
|
/** Written by the team rather than a user: gets the "Staff" badge. */
|
|
121
121
|
staff?: boolean;
|
|
122
|
+
/** 0.38: beside the author and time — a reply's "→ Done" status chip. Not
|
|
123
|
+
* `data-private`: it is the app's, not somebody's words. */
|
|
124
|
+
badge?: ReactNode;
|
|
125
|
+
/** 0.38: the body struck through and muted — a hidden comment as its admin sees it. */
|
|
126
|
+
struck?: boolean;
|
|
127
|
+
/** 0.38: under the body — a note ("Hidden by an admin") and the message's actions
|
|
128
|
+
* (Hide, Delete). Not `data-private`. */
|
|
129
|
+
footer?: ReactNode;
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
/**
|
|
@@ -296,13 +304,17 @@ export function FeedbackThread({
|
|
|
296
304
|
{labels.staff}
|
|
297
305
|
</span>
|
|
298
306
|
)}
|
|
307
|
+
{message.badge}
|
|
299
308
|
{time}
|
|
300
309
|
</header>
|
|
301
310
|
{message.body && (
|
|
302
311
|
<p
|
|
303
312
|
data-private={priv}
|
|
304
313
|
dir="auto"
|
|
305
|
-
className=
|
|
314
|
+
className={cn(
|
|
315
|
+
"whitespace-pre-wrap break-words text-start",
|
|
316
|
+
message.struck ? "text-muted line-through" : "text-primary",
|
|
317
|
+
)}
|
|
306
318
|
>
|
|
307
319
|
{message.body}
|
|
308
320
|
</p>
|
|
@@ -320,6 +332,11 @@ export function FeedbackThread({
|
|
|
320
332
|
))}
|
|
321
333
|
</ul>
|
|
322
334
|
)}
|
|
335
|
+
{message.footer != null && (
|
|
336
|
+
<div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-caption text-muted">
|
|
337
|
+
{message.footer}
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
323
340
|
</article>
|
|
324
341
|
</li>,
|
|
325
342
|
);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import { ThumbsUp } from "lucide-react";
|
|
3
|
+
import { Button } from "../components/button";
|
|
4
|
+
import { Tooltip } from "../components/tooltip";
|
|
5
|
+
import type { CommitScope } from "../components/write-lock";
|
|
6
|
+
import { useKitLabels, useKitLocale, type LabelOverride } from "../i18n/kit-labels";
|
|
7
|
+
import { cn } from "../lib/cn";
|
|
8
|
+
import { formatNumber } from "../lib/format";
|
|
9
|
+
import type { FeedbackVoteState } from "./feedback-public-types";
|
|
10
|
+
import { DEFAULT_FEEDBACK_VOTE_LABELS } from "./feedback-thread-parts-labels";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* "I want this too" — the one rating a report has (docs/feedback-harmonization.md §9.6,
|
|
14
|
+
* decision 2): an upvote, one per person, a count. No down-vote, no stars.
|
|
15
|
+
*
|
|
16
|
+
* - **A toggle** (`aria-pressed`): the thumb, the count, and "I want this too" as its
|
|
17
|
+
* name — written out at `size="md"`, the Tooltip and the accessible name at `"sm"`
|
|
18
|
+
* (a list row's cell).
|
|
19
|
+
* - **Optimistic with rollback:** the press shows the new state at once, `onVote(next)`
|
|
20
|
+
* sends `PUT` (`true`) or `DELETE` (`false`) and its answer — the server's count —
|
|
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.
|
|
23
|
+
* - **The author's own report** counts as their vote (§9.6, decision 20): `own` shows it
|
|
24
|
+
* pressed and `aria-disabled` with "Your report" as the reason (the server would
|
|
25
|
+
* answer 409).
|
|
26
|
+
* - A new `votes` / `voted` from the app (a refetch) wins over the last answer.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** The `feedbackVote` namespace of `<UiKitProvider labels>`. */
|
|
30
|
+
export interface FeedbackVoteLabels {
|
|
31
|
+
/** The button's name. de-CH: "Will ich auch" */
|
|
32
|
+
vote: string;
|
|
33
|
+
/** Why the author's own button is disabled. de-CH: "Ihr Bericht" */
|
|
34
|
+
own: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { DEFAULT_FEEDBACK_VOTE_LABELS };
|
|
38
|
+
|
|
39
|
+
export interface FeedbackVoteButtonProps {
|
|
40
|
+
/** People who want this — the author's own vote included. */
|
|
41
|
+
votes: number;
|
|
42
|
+
/** The viewer has voted. */
|
|
43
|
+
voted: boolean;
|
|
44
|
+
/** The viewer filed it: pressed, disabled, "Your report". */
|
|
45
|
+
own?: boolean;
|
|
46
|
+
/** `PUT` (`next` true) or `DELETE` (`false`) `/feedback/{id}/vote`; resolve with the
|
|
47
|
+
* answer, reject to roll back. */
|
|
48
|
+
onVote: (next: boolean) => Promise<FeedbackVoteState>;
|
|
49
|
+
/** Not now (e.g. a demo): plain `disabled`. A string is the reason, as Button's. */
|
|
50
|
+
disabled?: boolean | string;
|
|
51
|
+
/** The vote COMMITS — see Button's `commit`. */
|
|
52
|
+
commit?: CommitScope;
|
|
53
|
+
/** `"sm"`: the count alone, for a table cell. Default `"md"`. */
|
|
54
|
+
size?: "sm" | "md";
|
|
55
|
+
labels?: LabelOverride<FeedbackVoteLabels>;
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function FeedbackVoteButton({
|
|
60
|
+
votes,
|
|
61
|
+
voted,
|
|
62
|
+
own = false,
|
|
63
|
+
onVote,
|
|
64
|
+
disabled = false,
|
|
65
|
+
commit,
|
|
66
|
+
size = "md",
|
|
67
|
+
labels: labelsProp,
|
|
68
|
+
className,
|
|
69
|
+
}: FeedbackVoteButtonProps) {
|
|
70
|
+
const labels = useKitLabels("feedbackVote", DEFAULT_FEEDBACK_VOTE_LABELS, labelsProp);
|
|
71
|
+
const locale = useKitLocale();
|
|
72
|
+
// The last press's state, valid only while the app's props are still the ones it was
|
|
73
|
+
// pressed against: a refetch that brings new numbers is the truth.
|
|
74
|
+
const [shown, setShown] = useState<{ base: FeedbackVoteState; state: FeedbackVoteState } | null>(null);
|
|
75
|
+
const busy = useRef(false);
|
|
76
|
+
const current =
|
|
77
|
+
shown && shown.base.votes === votes && shown.base.voted === voted ? shown.state : { votes, voted };
|
|
78
|
+
const pressed = own || current.voted;
|
|
79
|
+
|
|
80
|
+
const press = () => {
|
|
81
|
+
if (own || disabled || busy.current) return;
|
|
82
|
+
const base = { votes, voted };
|
|
83
|
+
const before = current;
|
|
84
|
+
const next = !before.voted;
|
|
85
|
+
busy.current = true;
|
|
86
|
+
setShown({ base, state: { votes: Math.max(0, before.votes + (next ? 1 : -1)), voted: next } });
|
|
87
|
+
onVote(next).then(
|
|
88
|
+
(answer) => setShown({ base, state: { votes: answer.votes, voted: answer.voted } }),
|
|
89
|
+
() => setShown({ base, state: before }),
|
|
90
|
+
).finally(() => {
|
|
91
|
+
busy.current = false;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const count = formatNumber(current.votes, { locale });
|
|
96
|
+
const button = (
|
|
97
|
+
<Button
|
|
98
|
+
type="button"
|
|
99
|
+
variant="secondary"
|
|
100
|
+
size="sm"
|
|
101
|
+
pressed={pressed}
|
|
102
|
+
commit={own ? undefined : commit}
|
|
103
|
+
disabled={disabled === true}
|
|
104
|
+
disabledReason={own ? labels.own : typeof disabled === "string" ? disabled : undefined}
|
|
105
|
+
onClick={(event) => {
|
|
106
|
+
// A row around it may expand on a click.
|
|
107
|
+
event.stopPropagation();
|
|
108
|
+
press();
|
|
109
|
+
}}
|
|
110
|
+
data-feedback-vote=""
|
|
111
|
+
className={cn("gap-1.5", className)}
|
|
112
|
+
>
|
|
113
|
+
<ThumbsUp className="size-3.5 shrink-0" aria-hidden />
|
|
114
|
+
<span className={size === "sm" ? "sr-only" : undefined}>{labels.vote}</span>
|
|
115
|
+
<span className="tabular-nums" data-feedback-votes="">
|
|
116
|
+
{count}
|
|
117
|
+
</span>
|
|
118
|
+
</Button>
|
|
119
|
+
);
|
|
120
|
+
// At "sm" the name is not on screen: the Tooltip shows it (the own reason has its own).
|
|
121
|
+
return size === "sm" && !own ? <Tooltip label={labels.vote}>{button}</Tooltip> : button;
|
|
122
|
+
}
|
package/src/feedback.ts
CHANGED
|
@@ -20,3 +20,13 @@ export * from "./feedback/feedback-submit";
|
|
|
20
20
|
export * from "./feedback/feedback-table";
|
|
21
21
|
export * from "./feedback/feedback-row-detail";
|
|
22
22
|
export * from "./feedback/feedback-crash";
|
|
23
|
+
// 0.38.0: the public pages (§9.15) — the "Visible to others" switch, the list every user
|
|
24
|
+
// reads and one report opened.
|
|
25
|
+
export * from "./feedback/feedback-public-switch";
|
|
26
|
+
export * from "./feedback/feedback-public-list";
|
|
27
|
+
// 0.38.0: public feedback (docs/feedback-harmonization.md §9) — the shapes, the vote, the
|
|
28
|
+
// comment thread, the replies' timeline and composer.
|
|
29
|
+
export * from "./feedback/feedback-public-types";
|
|
30
|
+
export * from "./feedback/feedback-vote-button";
|
|
31
|
+
export * from "./feedback/feedback-comment-thread";
|
|
32
|
+
export * from "./feedback/feedback-replies";
|
package/src/i18n/defaults.ts
CHANGED
|
@@ -41,9 +41,16 @@ import {
|
|
|
41
41
|
DEFAULT_FEEDBACK_TOAST_LABELS,
|
|
42
42
|
} from "../feedback/feedback-labels";
|
|
43
43
|
import { DEFAULT_FEEDBACK_MENU_LABELS } from "../feedback/feedback-menu-labels";
|
|
44
|
+
import { DEFAULT_FEEDBACK_PUBLIC_LABELS } from "../feedback/feedback-public-labels";
|
|
45
|
+
import { DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS } from "../notifications/feedback-notification-types-labels";
|
|
44
46
|
import { DEFAULT_FEEDBACK_CONTEXT_LABELS } from "../feedback/feedback-context-labels";
|
|
45
47
|
import { DEFAULT_FEEDBACK_PAGE_LABELS } from "../feedback/feedback-table-labels";
|
|
46
48
|
import { DEFAULT_FEEDBACK_DETAIL_LABELS } from "../feedback/feedback-row-detail-labels";
|
|
49
|
+
import {
|
|
50
|
+
DEFAULT_FEEDBACK_COMMENTS_LABELS,
|
|
51
|
+
DEFAULT_FEEDBACK_REPLIES_LABELS,
|
|
52
|
+
DEFAULT_FEEDBACK_VOTE_LABELS,
|
|
53
|
+
} from "../feedback/feedback-thread-parts-labels";
|
|
47
54
|
import { DEFAULT_ACCOUNT_SETTINGS_LABELS } from "../account/account-settings-labels";
|
|
48
55
|
import { DEFAULT_COPY_BUTTON_LABELS } from "../components/copy-button-labels";
|
|
49
56
|
import { DEFAULT_BULK_ACTION_BAR_LABELS } from "../components/bulk-action-bar-labels";
|
|
@@ -180,9 +187,14 @@ export const DEFAULT_UI_KIT_LABELS: UiKitLabels = {
|
|
|
180
187
|
feedbackCategory: DEFAULT_FEEDBACK_CATEGORY_LABELS,
|
|
181
188
|
feedbackToast: DEFAULT_FEEDBACK_TOAST_LABELS,
|
|
182
189
|
feedbackMenu: DEFAULT_FEEDBACK_MENU_LABELS,
|
|
190
|
+
feedbackPublic: DEFAULT_FEEDBACK_PUBLIC_LABELS,
|
|
191
|
+
feedbackNotificationTypes: DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS,
|
|
183
192
|
feedbackContext: DEFAULT_FEEDBACK_CONTEXT_LABELS,
|
|
184
193
|
feedbackPage: DEFAULT_FEEDBACK_PAGE_LABELS,
|
|
185
194
|
feedbackDetail: DEFAULT_FEEDBACK_DETAIL_LABELS,
|
|
195
|
+
feedbackVote: DEFAULT_FEEDBACK_VOTE_LABELS,
|
|
196
|
+
feedbackComments: DEFAULT_FEEDBACK_COMMENTS_LABELS,
|
|
197
|
+
feedbackReplies: DEFAULT_FEEDBACK_REPLIES_LABELS,
|
|
186
198
|
accountSettings: DEFAULT_ACCOUNT_SETTINGS_LABELS,
|
|
187
199
|
confirmDialog: DEFAULT_CONFIRM_DIALOG_LABELS,
|
|
188
200
|
floatingPanel: DEFAULT_FLOATING_PANEL_LABELS,
|
package/src/i18n/german.ts
CHANGED
|
@@ -59,6 +59,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
59
59
|
submitHint: (apple) => (apple ? "⌘ Enter zum Senden" : "Strg+Enter zum Senden"),
|
|
60
60
|
cancel: "Abbrechen",
|
|
61
61
|
save: "Senden",
|
|
62
|
+
publicNotice: "Andere Personen sehen Titel und Text (nie Ihre Screenshots oder Dateien).",
|
|
62
63
|
},
|
|
63
64
|
feedbackThread: {
|
|
64
65
|
thread: "Kommentare",
|
|
@@ -117,6 +118,36 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
117
118
|
trigger: "Feedback senden",
|
|
118
119
|
myFeedback: "Mein Feedback",
|
|
119
120
|
viewFeedback: "Feedback ansehen",
|
|
121
|
+
allFeedback: "Alle Rückmeldungen",
|
|
122
|
+
},
|
|
123
|
+
// 0.38.0 — docs/feedback-harmonization.md §9: the public pages and the two feedback
|
|
124
|
+
// notification types. "Rückmeldungen" and "Eine Person" are the contract's (§9.2, §9.5).
|
|
125
|
+
feedbackPublic: {
|
|
126
|
+
title: "Alle Rückmeldungen",
|
|
127
|
+
sort: "Sortieren",
|
|
128
|
+
sortNewest: "Neueste",
|
|
129
|
+
sortVotes: "Meistgewünscht",
|
|
130
|
+
sortUpdated: "Zuletzt aktualisiert",
|
|
131
|
+
search: "Titel durchsuchen",
|
|
132
|
+
columnVotes: "Stimmen",
|
|
133
|
+
total: (count) => (count === 1 ? "1 Meldung" : `${n(count)} Meldungen`),
|
|
134
|
+
comments: (count) => (count === 1 ? "1 Kommentar" : `${n(count)} Kommentare`),
|
|
135
|
+
votes: (count) => (count === 1 ? "1 Stimme" : `${n(count)} Stimmen`),
|
|
136
|
+
anonymous: "Eine Person",
|
|
137
|
+
filedBy: (name) => `Von ${name}`,
|
|
138
|
+
body: "Beschreibung",
|
|
139
|
+
conversation: "Antworten und Kommentare",
|
|
140
|
+
loading: "Wird geladen…",
|
|
141
|
+
empty: "Noch keine Meldungen",
|
|
142
|
+
visibleToOthers: "Für andere sichtbar",
|
|
143
|
+
visibleHint: "Andere Personen sehen Titel und Text, nie Ihre Screenshots oder Dateien.",
|
|
144
|
+
visibleLocked: "Nur solange die Meldung noch offen ist.",
|
|
145
|
+
},
|
|
146
|
+
feedbackNotificationTypes: {
|
|
147
|
+
answered: "Antworten auf Ihre Meldungen",
|
|
148
|
+
answeredDescription: "Wenn eine Meldung, die Sie geschrieben oder kommentiert haben, eine Antwort erhält.",
|
|
149
|
+
comment: "Kommentare zu Meldungen, denen Sie folgen",
|
|
150
|
+
commentDescription: "Wenn jemand eine Meldung kommentiert, die Sie geschrieben oder kommentiert haben.",
|
|
120
151
|
},
|
|
121
152
|
feedbackContext: {
|
|
122
153
|
user: "Nutzer",
|
|
@@ -157,6 +188,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
157
188
|
download: (name) => `${name} herunterladen`,
|
|
158
189
|
downloadFailed: "Der Anhang konnte nicht heruntergeladen werden.",
|
|
159
190
|
outcome: "Ergebnis",
|
|
191
|
+
replies: "Antworten",
|
|
160
192
|
resolvedAt: (date) => `Erledigt am: ${date}`,
|
|
161
193
|
outcomeAdd: "Ergebnis hinzufügen",
|
|
162
194
|
outcomeUpdate: "Aktualisieren",
|
|
@@ -170,6 +202,36 @@ export function germanLabels(numberLocale: string): UiKitLabels {
|
|
|
170
202
|
status: "Status",
|
|
171
203
|
note: "Notiz",
|
|
172
204
|
},
|
|
205
|
+
// 0.38.0 — docs/feedback-harmonization.md §9.15: the vote, the comment thread, the replies.
|
|
206
|
+
feedbackVote: {
|
|
207
|
+
vote: "Will ich auch",
|
|
208
|
+
own: "Ihr Bericht",
|
|
209
|
+
},
|
|
210
|
+
feedbackComments: {
|
|
211
|
+
comments: "Kommentare",
|
|
212
|
+
empty: "Noch keine Kommentare",
|
|
213
|
+
hidden: "Von einem Admin ausgeblendet",
|
|
214
|
+
hide: "Ausblenden",
|
|
215
|
+
unhide: "Einblenden",
|
|
216
|
+
delete: "Löschen",
|
|
217
|
+
deleteTitle: "Diesen Kommentar löschen?",
|
|
218
|
+
deleteBody: "Er wird für alle entfernt. Das kann nicht rückgängig gemacht werden.",
|
|
219
|
+
field: "Kommentar schreiben",
|
|
220
|
+
placeholder: "Kommentar schreiben…",
|
|
221
|
+
send: "Kommentieren",
|
|
222
|
+
},
|
|
223
|
+
feedbackReplies: {
|
|
224
|
+
replies: "Antworten",
|
|
225
|
+
empty: "Noch keine Antwort",
|
|
226
|
+
admin: "Admin",
|
|
227
|
+
statusChanged: "Status geändert auf",
|
|
228
|
+
field: "Antwort schreiben",
|
|
229
|
+
placeholder: "Antwort schreiben…",
|
|
230
|
+
reply: "Antworten",
|
|
231
|
+
status: "Status mit der Antwort",
|
|
232
|
+
noChange: "Keine Änderung",
|
|
233
|
+
nudge: "Die meldende Person wurde nicht benachrichtigt. Schreiben Sie eine Antwort.",
|
|
234
|
+
},
|
|
173
235
|
accountSettings: {
|
|
174
236
|
profile: {
|
|
175
237
|
title: "Profil",
|
package/src/i18n/kit-labels.tsx
CHANGED
|
@@ -50,9 +50,14 @@ import type {
|
|
|
50
50
|
FeedbackToastLabels,
|
|
51
51
|
} from "../feedback/feedback-labels";
|
|
52
52
|
import type { FeedbackMenuLabels } from "../feedback/feedback-menu";
|
|
53
|
+
import type { FeedbackPublicLabels } from "../feedback/feedback-public-labels";
|
|
54
|
+
import type { FeedbackNotificationTypesLabels } from "../notifications/feedback-notification-types-labels";
|
|
53
55
|
import type { FeedbackContextLabels } from "../feedback/feedback-context";
|
|
54
56
|
import type { FeedbackPageLabels } from "../feedback/feedback-table";
|
|
55
57
|
import type { FeedbackDetailLabels } from "../feedback/feedback-row-detail";
|
|
58
|
+
import type { FeedbackVoteLabels } from "../feedback/feedback-vote-button";
|
|
59
|
+
import type { FeedbackCommentsLabels } from "../feedback/feedback-comment-thread";
|
|
60
|
+
import type { FeedbackRepliesLabels } from "../feedback/feedback-replies";
|
|
56
61
|
import type { AccountSettingsLabels } from "../account/account-settings-labels";
|
|
57
62
|
import type { ConfirmDialogLabels } from "../components/confirm-dialog";
|
|
58
63
|
import type { FloatingPanelLabels } from "../components/floating-panel";
|
|
@@ -338,9 +343,20 @@ export interface UiKitLabels {
|
|
|
338
343
|
feedbackCategory: FeedbackCategoryLabels;
|
|
339
344
|
feedbackToast: FeedbackToastLabels;
|
|
340
345
|
feedbackMenu: FeedbackMenuLabels;
|
|
346
|
+
/** 0.38.0: the public feedback pages — `FeedbackPublicList`, `FeedbackPublicDetail`,
|
|
347
|
+
* `FeedbackPublicSwitch` (docs/feedback-harmonization.md §9.15). */
|
|
348
|
+
feedbackPublic: FeedbackPublicLabels;
|
|
349
|
+
/** 0.38.0: `useFeedbackNotificationTypes` — the two feedback types' setting words
|
|
350
|
+
* (§9.11). */
|
|
351
|
+
feedbackNotificationTypes: FeedbackNotificationTypesLabels;
|
|
341
352
|
feedbackContext: FeedbackContextLabels;
|
|
342
353
|
feedbackPage: FeedbackPageLabels;
|
|
343
354
|
feedbackDetail: FeedbackDetailLabels;
|
|
355
|
+
/** 0.38.0: public feedback (docs/feedback-harmonization.md §9.15) — the vote button,
|
|
356
|
+
* the comment thread, the replies' timeline and composer. */
|
|
357
|
+
feedbackVote: FeedbackVoteLabels;
|
|
358
|
+
feedbackComments: FeedbackCommentsLabels;
|
|
359
|
+
feedbackReplies: FeedbackRepliesLabels;
|
|
344
360
|
/** 0.12.0: `ProfileSetting`, `PasswordSetting`, `TwoFactorSetting`, `PasskeysSetting`
|
|
345
361
|
* — one record per section. The provider takes a whole section record; the
|
|
346
362
|
* components merge it key by key, as a prop. */
|
package/src/i18n/locales/en.ts
CHANGED
|
@@ -48,6 +48,13 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
|
|
|
48
48
|
// `userFallback`'s number is an id, not a count: it stays as the default prints it.
|
|
49
49
|
reworkChip: (count) => (count === 1 ? "Rework" : `Rework ×${n(count)}`),
|
|
50
50
|
},
|
|
51
|
+
// 0.38.0: the public list's two counts.
|
|
52
|
+
feedbackPublic: {
|
|
53
|
+
...d.feedbackPublic,
|
|
54
|
+
total: (count) => (count === 1 ? "1 report" : `${n(count)} reports`),
|
|
55
|
+
comments: (count) => (count === 1 ? "1 comment" : `${n(count)} comments`),
|
|
56
|
+
votes: (count) => (count === 1 ? "1 vote" : `${n(count)} votes`),
|
|
57
|
+
},
|
|
51
58
|
measuredGrid: {
|
|
52
59
|
...d.measuredGrid,
|
|
53
60
|
points: (count) => (count === 1 ? "1 point" : `${n(count)} points`),
|
package/src/i18n/locales/es.ts
CHANGED
|
@@ -55,6 +55,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
55
55
|
submitHint: (apple) => (apple ? "⌘ Intro para enviar" : "Ctrl+Intro para enviar"),
|
|
56
56
|
cancel: "Cancelar",
|
|
57
57
|
save: "Enviar",
|
|
58
|
+
publicNotice: "Otras personas verán el título y el texto (nunca sus capturas ni sus archivos).",
|
|
58
59
|
},
|
|
59
60
|
feedbackThread: {
|
|
60
61
|
thread: "Comentarios",
|
|
@@ -108,6 +109,36 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
108
109
|
trigger: "Enviar comentarios",
|
|
109
110
|
myFeedback: "Mis comentarios",
|
|
110
111
|
viewFeedback: "Ver comentarios",
|
|
112
|
+
allFeedback: "Todos los comentarios",
|
|
113
|
+
},
|
|
114
|
+
// 0.38.0 — docs/feedback-harmonization.md §9: the public pages and the two feedback
|
|
115
|
+
// notification types. "Informe" for one report, so it never reads as "comentario".
|
|
116
|
+
feedbackPublic: {
|
|
117
|
+
title: "Todos los comentarios",
|
|
118
|
+
sort: "Ordenar",
|
|
119
|
+
sortNewest: "Más recientes",
|
|
120
|
+
sortVotes: "Más deseados",
|
|
121
|
+
sortUpdated: "Actualizados recientemente",
|
|
122
|
+
search: "Buscar en los títulos",
|
|
123
|
+
columnVotes: "Votos",
|
|
124
|
+
total: (count) => `${n(count)} ${plural(count, "informe", "informes")}`,
|
|
125
|
+
comments: (count) => `${n(count)} ${plural(count, "comentario", "comentarios")}`,
|
|
126
|
+
votes: (count) => `${n(count)} ${plural(count, "voto", "votos")}`,
|
|
127
|
+
anonymous: "Una persona",
|
|
128
|
+
filedBy: (name) => `De ${name}`,
|
|
129
|
+
body: "Descripción",
|
|
130
|
+
conversation: "Respuestas y comentarios",
|
|
131
|
+
loading: "Cargando…",
|
|
132
|
+
empty: "Todavía no hay informes",
|
|
133
|
+
visibleToOthers: "Visible para otras personas",
|
|
134
|
+
visibleHint: "Otras personas ven el título y el texto, nunca sus capturas ni sus archivos.",
|
|
135
|
+
visibleLocked: "Solo mientras el informe siga abierto.",
|
|
136
|
+
},
|
|
137
|
+
feedbackNotificationTypes: {
|
|
138
|
+
answered: "Respuestas a sus informes",
|
|
139
|
+
answeredDescription: "Cuando un informe que usted escribió o comentó recibe una respuesta.",
|
|
140
|
+
comment: "Comentarios en informes que sigue",
|
|
141
|
+
commentDescription: "Cuando alguien comenta un informe que usted escribió o comentó.",
|
|
111
142
|
},
|
|
112
143
|
feedbackContext: {
|
|
113
144
|
user: "Usuario",
|
|
@@ -148,6 +179,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
148
179
|
download: (name) => `Descargar ${name}`,
|
|
149
180
|
downloadFailed: "No se ha podido descargar el adjunto.",
|
|
150
181
|
outcome: "Resultado",
|
|
182
|
+
replies: "Respuestas",
|
|
151
183
|
resolvedAt: (date) => `Resuelto el ${date}`,
|
|
152
184
|
outcomeAdd: "Añadir resultado",
|
|
153
185
|
outcomeUpdate: "Actualizar",
|
|
@@ -161,6 +193,36 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
|
|
|
161
193
|
status: "Estado",
|
|
162
194
|
note: "Nota",
|
|
163
195
|
},
|
|
196
|
+
// 0.38.0 — docs/feedback-harmonization.md §9.15: the vote, the comment thread, the replies.
|
|
197
|
+
feedbackVote: {
|
|
198
|
+
vote: "Yo también lo quiero",
|
|
199
|
+
own: "Su informe",
|
|
200
|
+
},
|
|
201
|
+
feedbackComments: {
|
|
202
|
+
comments: "Comentarios",
|
|
203
|
+
empty: "Aún no hay comentarios",
|
|
204
|
+
hidden: "Ocultado por un administrador",
|
|
205
|
+
hide: "Ocultar",
|
|
206
|
+
unhide: "Mostrar",
|
|
207
|
+
delete: "Eliminar",
|
|
208
|
+
deleteTitle: "¿Eliminar este comentario?",
|
|
209
|
+
deleteBody: "Se eliminará para todos. No se puede deshacer.",
|
|
210
|
+
field: "Escribir un comentario",
|
|
211
|
+
placeholder: "Escriba un comentario…",
|
|
212
|
+
send: "Comentar",
|
|
213
|
+
},
|
|
214
|
+
feedbackReplies: {
|
|
215
|
+
replies: "Respuestas",
|
|
216
|
+
empty: "Aún no hay respuesta",
|
|
217
|
+
admin: "Admin",
|
|
218
|
+
statusChanged: "Estado cambiado a",
|
|
219
|
+
field: "Escribir una respuesta",
|
|
220
|
+
placeholder: "Escriba una respuesta…",
|
|
221
|
+
reply: "Responder",
|
|
222
|
+
status: "Estado con la respuesta",
|
|
223
|
+
noChange: "Sin cambios",
|
|
224
|
+
nudge: "No se ha avisado a quien lo informó. Añada una respuesta.",
|
|
225
|
+
},
|
|
164
226
|
accountSettings: {
|
|
165
227
|
profile: {
|
|
166
228
|
title: "Perfil",
|