feat: add ability to provide feedback

This commit is contained in:
zhom committed 2026-10-10 01:11:04 +04:00
1 parent bf625fac2c
commit 18a891f709
32 files changed
+2693 -106

No files matched your search

+19
View File
@@ -22,6 +22,7 @@ import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialo
import { DeviceCodeVerifyDialog } from "@/components/device-code-verify-dialog";
import { ExtensionGroupAssignmentDialog } from "@/components/extension-group-assignment-dialog";
import { ExtensionManagementDialog } from "@/components/extension-management-dialog";
import { FeedbackDialog } from "@/components/feedback-dialog";
import { GroupAssignmentDialog } from "@/components/group-assignment-dialog";
import { GroupManagementDialog } from "@/components/group-management-dialog";
import HomeHeader from "@/components/home-header";
@@ -529,6 +530,7 @@ export default function Home() {
useState<BrowserProfile | null>(null);
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
const [aboutDialogOpen, setAboutDialogOpen] = useState(false);
const [feedbackDialogOpen, setFeedbackDialogOpen] = useState(false);
// Pre-launch gate. Requests queue instead of overwriting a single resolver:
// a bulk run enqueues one per profile, and every waiter must settle or the
// Promise.allSettled below it never resolves and the bulk spinner sticks.
@@ -666,6 +668,9 @@ export default function Home() {
case "palette":
setCommandPaletteOpen(true);
break;
case "feedback":
setFeedbackDialogOpen(true);
break;
}
},
[closeTips, handleRailNavigate],
@@ -680,6 +685,9 @@ export default function Home() {
case "openTips":
openTips();
break;
case "sendFeedback":
setFeedbackDialogOpen(true);
break;
case "openShortcuts":
handleRailNavigate("shortcuts");
break;
@@ -2319,6 +2327,9 @@ export default function Home() {
setAboutDialogOpen(true);
}}
onOpenTips={() => openTips()}
onOpenFeedback={() => {
setFeedbackDialogOpen(true);
}}
cookieBotRunning={Object.keys(cookieBotLiveSessions).length > 0}
agentRequests={agentConsole.openRequests.length}
/>
@@ -2602,6 +2613,14 @@ export default function Home() {
}}
/>
<FeedbackDialog
isOpen={feedbackDialogOpen}
onClose={() => {
setFeedbackDialogOpen(false);
}}
accountEmail={cloudUser?.email ?? null}
/>
<PreLaunchGateDialog
isOpen={gateState !== null}
profileName={gateState?.req.profile.name ?? ""}
+40
View File
@@ -9,6 +9,8 @@ import {
LuChevronsUpDown,
LuHand,
LuMessageCircleQuestion,
LuMessageSquareHeart,
LuPaperclip,
LuSend,
LuUndo2,
} from "react-icons/lu";
@@ -500,6 +502,44 @@ function ThreadEntry({
);
}
if (item.kind === "feedback") {
return (
<div className="flex max-w-[85%] flex-col gap-1">
<span className="flex flex-wrap items-center gap-x-1.5 text-[11px] text-muted-foreground">
<span className="font-medium text-foreground">
{nameOf(item.session_id)}
</span>
<span>{t("agent.thread.feedbackSent")}</span>
<span aria-hidden="true">·</span>
{time}
</span>
<div className="flex items-start gap-2 rounded-xl bg-foreground/5 px-3 py-2 text-sm text-foreground">
<LuMessageSquareHeart
className="mt-0.5 size-3.5 shrink-0 text-muted-foreground"
aria-hidden="true"
/>
<div className="flex min-w-0 flex-col gap-1">
<p className="break-words whitespace-pre-wrap">{item.text}</p>
{item.feedback && (
<span className="flex flex-wrap items-center gap-x-1.5 text-[11px] text-muted-foreground">
<span>{t(`feedback.kinds.${item.feedback.kind}.label`)}</span>
{item.feedback.logs && (
<>
<span aria-hidden="true">·</span>
<span className="inline-flex items-center gap-1">
<LuPaperclip className="size-3" aria-hidden="true" />
{t("agent.thread.feedbackLogs")}
</span>
</>
)}
</span>
)}
</div>
</div>
</div>
);
}
if (item.kind === "note") {
const delivery =
item.state === "delivered"
+2
View File
@@ -13,6 +13,7 @@ import {
LuInfo,
LuKeyboard,
LuLightbulb,
LuMessageSquareHeart,
LuPlay,
LuPlug,
LuPlus,
@@ -67,6 +68,7 @@ const ICONS: Record<ShortcutId, React.ComponentType<{ className?: string }>> = {
openPalette: LuKeyboard,
openShortcuts: LuKeyboard,
openTips: LuLightbulb,
sendFeedback: LuMessageSquareHeart,
importProfile: FaDownload,
goProfiles: LuUser,
goProxies: FiWifi,
+468
View File
@@ -0,0 +1,468 @@
"use client";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { invoke } from "@tauri-apps/api/core";
import { motion, useReducedMotion } from "motion/react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
LuBug,
LuCheck,
LuChevronRight,
LuFileText,
LuHeart,
LuLightbulb,
LuMail,
LuMessageSquareMore,
LuSend,
} from "react-icons/lu";
import { LoadingButton } from "@/components/loading-button";
import { AnimatedDisclosureChevron } from "@/components/ui/animated-disclosure";
import { AnimatedSwitch } from "@/components/ui/animated-switch";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { SoftFields } from "@/components/ui/field-variant";
import { Input } from "@/components/ui/input";
import { StepTransition } from "@/components/ui/step-transition";
import { Textarea } from "@/components/ui/textarea";
import { useInputModality } from "@/hooks/use-input-modality";
import type { FeedbackKind } from "@/lib/agent-console";
import { translateBackendError } from "@/lib/backend-errors";
import { formatBytes } from "@/lib/format-bytes";
import { MOTION_SPRING_POSITION } from "@/lib/motion";
import { isMacOS } from "@/lib/platform";
import { cn } from "@/lib/utils";
import { RippleButton } from "./ui/ripple";
/** Mirrors `feedback::MAX_MESSAGE_CHARS`. */
const MAX_MESSAGE_CHARS = 5000;
/** The counter shows once the message gets this close to the limit. */
const COUNTER_FROM = MAX_MESSAGE_CHARS - 500;
const KINDS: {
id: FeedbackKind;
Icon: React.ComponentType<{ className?: string }>;
}[] = [
{ id: "bug", Icon: LuBug },
{ id: "idea", Icon: LuLightbulb },
{ id: "praise", Icon: LuHeart },
{ id: "other", Icon: LuMessageSquareMore },
];
interface SystemInfo {
app_version: string;
os: string;
arch: string;
}
interface FeedbackReceipt {
id: string;
}
interface FeedbackDialogProps {
isOpen: boolean;
onClose: () => void;
/** The signed-in account's email; replies go there. */
accountEmail: string | null;
}
/**
* Feedback to the Donut team. A draft survives closing the dialog and is only
* cleared once it has been sent. Logs are on by default for a bug, the one
* kind where they nearly always matter, and the person can read exactly what
* would be attached before sending.
*/
export function FeedbackDialog({
isOpen,
onClose,
accountEmail,
}: FeedbackDialogProps) {
const { t, i18n } = useTranslation();
const reduceMotion = useReducedMotion();
const inputModality = useInputModality();
const animate = !reduceMotion && inputModality === "pointer";
const [kind, setKind] = useState<FeedbackKind>("bug");
const [message, setMessage] = useState("");
const [includeLogs, setIncludeLogs] = useState(true);
const [logsChosen, setLogsChosen] = useState(false);
const [email, setEmail] = useState("");
const [previewOpen, setPreviewOpen] = useState(false);
const [logs, setLogs] = useState<string | null>(null);
const [logsLoading, setLogsLoading] = useState(false);
const [sending, setSending] = useState(false);
const [error, setError] = useState<string | null>(null);
const [sent, setSent] = useState<{ logs: boolean; replyTo: string | null }>();
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
const messageRef = useRef<HTMLTextAreaElement>(null);
const previewRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
if (!isOpen || systemInfo) return;
invoke<SystemInfo>("get_system_info")
.then(setSystemInfo)
.catch((err: unknown) => {
console.warn("Failed to read system info:", err);
});
}, [isOpen, systemInfo]);
const chooseKind = useCallback(
(next: FeedbackKind) => {
setKind(next);
if (!logsChosen) setIncludeLogs(next === "bug");
},
[logsChosen],
);
const loadLogs = useCallback(async () => {
setLogsLoading(true);
try {
setLogs(await invoke<string>("preview_feedback_logs"));
} catch (err) {
setError(translateBackendError(t, err));
} finally {
setLogsLoading(false);
}
}, [t]);
const togglePreview = useCallback(() => {
const opening = !previewOpen;
setPreviewOpen(opening);
if (opening) void loadLogs();
}, [loadLogs, previewOpen]);
// The newest lines are at the end; that is what the reader wants to see.
useEffect(() => {
if (!previewOpen || logs === null || !previewRef.current) return;
previewRef.current.scrollTop = previewRef.current.scrollHeight;
}, [logs, previewOpen]);
const handleClose = useCallback(() => {
if (sent) {
setSent(undefined);
setMessage("");
setKind("bug");
setIncludeLogs(true);
setLogsChosen(false);
setPreviewOpen(false);
setLogs(null);
}
setError(null);
onClose();
}, [onClose, sent]);
const canSend = message.trim().length > 0 && !sending;
const send = useCallback(async () => {
if (!message.trim() || sending) return;
setSending(true);
setError(null);
try {
await invoke<FeedbackReceipt>("send_feedback", {
kind,
message,
includeLogs,
email: accountEmail ? null : email.trim() || null,
locale: i18n.language,
});
setSent({
logs: includeLogs,
replyTo: accountEmail ?? (email.trim() || null),
});
} catch (err) {
console.error("Failed to send feedback:", err);
setError(translateBackendError(t, err));
} finally {
setSending(false);
}
}, [
accountEmail,
email,
i18n.language,
includeLogs,
kind,
message,
sending,
t,
]);
const modKey = isMacOS() ? "⌘" : "Ctrl";
const logsBytes = useMemo(
() => (logs === null ? null : new Blob([logs]).size),
[logs],
);
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) handleClose();
}}
>
<DialogContent
className="max-w-lg"
data-dialog="feedback"
onOpenAutoFocus={(event) => {
event.preventDefault();
messageRef.current?.focus();
}}
onKeyDown={(event) => {
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
if (!sent) void send();
}
}}
>
<StepTransition
transitionKey={sent ? "sent" : "compose"}
direction={sent ? 1 : -1}
className="grid min-w-0 gap-4"
>
{sent ? (
<div
data-slot="feedback-sent"
className="grid justify-items-center gap-2 py-6 text-center"
>
<motion.div
initial={animate ? { scale: 0.6 } : false}
animate={{ scale: 1 }}
transition={animate ? MOTION_SPRING_POSITION : { duration: 0 }}
className="mb-2 grid size-14 place-items-center rounded-full bg-success/15 text-success-text"
>
<LuCheck className="size-7" aria-hidden="true" />
</motion.div>
<DialogTitle>{t("feedback.success.title")}</DialogTitle>
<DialogDescription className="max-w-sm">
{sent.logs
? t("feedback.success.bodyWithLogs")
: t("feedback.success.body")}
</DialogDescription>
{sent.replyTo && (
<p className="max-w-sm text-xs break-words text-muted-foreground">
{t("feedback.success.reply", { email: sent.replyTo })}
</p>
)}
<RippleButton
autoFocus
className="mt-4 min-w-28"
onClick={handleClose}
>
{t("feedback.success.done")}
</RippleButton>
</div>
) : (
<>
<DialogHeader>
<DialogTitle>{t("feedback.title")}</DialogTitle>
<DialogDescription>
{t("feedback.description")}
</DialogDescription>
</DialogHeader>
<SoftFields>
<RadioGroupPrimitive.Root
value={kind}
onValueChange={(value) => {
chooseKind(value as FeedbackKind);
}}
aria-label={t("feedback.kindLabel")}
className="grid grid-cols-4 gap-2"
>
{KINDS.map(({ id, Icon }) => (
<RadioGroupPrimitive.Item
key={id}
value={id}
data-slot="feedback-kind"
data-kind={id}
className={cn(
"flex cursor-pointer flex-col items-center gap-1.5 rounded-lg px-2 py-2.5 text-xs font-medium transition-[color,background-color,box-shadow] duration-150 outline-none",
"bg-foreground/5 text-muted-foreground hover:bg-foreground/8 hover:text-foreground",
"focus-visible:ring-2 focus-visible:ring-ring",
"data-[state=checked]:bg-primary/10 data-[state=checked]:text-foreground data-[state=checked]:inset-ring data-[state=checked]:inset-ring-primary/50",
)}
>
<Icon className="size-4" aria-hidden="true" />
<span className="max-w-full truncate">
{t(`feedback.kinds.${id}.label`)}
</span>
</RadioGroupPrimitive.Item>
))}
</RadioGroupPrimitive.Root>
<div className="grid gap-1">
<Textarea
ref={messageRef}
data-slot="feedback-message"
aria-label={t("feedback.messageLabel")}
placeholder={t(`feedback.kinds.${kind}.placeholder`)}
value={message}
maxLength={MAX_MESSAGE_CHARS}
rows={6}
className="min-h-32 resize-none"
onChange={(event) => {
setMessage(event.target.value);
}}
/>
{message.length >= COUNTER_FROM && (
<p className="text-right text-[11px] text-muted-foreground tabular-nums">
{t("feedback.counter", {
count: message.length,
max: MAX_MESSAGE_CHARS,
})}
</p>
)}
</div>
<div className="grid gap-2 rounded-lg bg-foreground/5 p-3">
<div className="flex items-start gap-3">
<LuFileText
className="mt-0.5 size-4 shrink-0 text-muted-foreground"
aria-hidden="true"
/>
<div className="grid min-w-0 flex-1 gap-0.5">
<label
htmlFor="feedback-include-logs"
className="cursor-pointer text-sm font-medium"
>
{t("feedback.logs.label")}
</label>
<p className="text-xs text-muted-foreground">
{t("feedback.logs.description")}
</p>
</div>
<AnimatedSwitch
id="feedback-include-logs"
data-slot="feedback-include-logs"
checked={includeLogs}
onCheckedChange={(checked) => {
setIncludeLogs(checked);
setLogsChosen(true);
}}
/>
</div>
{includeLogs && (
<div className="pl-7">
<button
type="button"
data-slot="feedback-preview-logs"
aria-expanded={previewOpen}
onClick={togglePreview}
className="inline-flex cursor-pointer items-center gap-1 rounded-sm text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
>
<AnimatedDisclosureChevron open={previewOpen}>
<LuChevronRight className="size-3" />
</AnimatedDisclosureChevron>
{t("feedback.logs.preview")}
{logsBytes !== null && logsBytes > 0 && (
<span className="tabular-nums">
· {formatBytes(logsBytes)}
</span>
)}
</button>
{previewOpen && (
<textarea
ref={previewRef}
readOnly
data-slot="feedback-logs-preview"
aria-label={t("feedback.logs.preview")}
rows={10}
spellCheck={false}
value={
logsLoading && logs === null
? t("feedback.logs.loading")
: logs
? logs
: t("feedback.logs.empty")
}
className="mt-2 block h-44 w-full resize-none overflow-auto rounded-md bg-background p-2 font-mono text-[10px] leading-relaxed break-all whitespace-pre-wrap text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
/>
)}
</div>
)}
</div>
{accountEmail ? (
<p className="flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground">
<LuMail className="size-3.5 shrink-0" aria-hidden="true" />
<span className="min-w-0 truncate">
{t("feedback.email.signedIn", { email: accountEmail })}
</span>
</p>
) : (
<div className="grid gap-1.5">
<label
htmlFor="feedback-email"
className="flex items-baseline gap-1.5 text-sm font-medium"
>
{t("feedback.email.label")}
<span className="text-xs font-normal text-muted-foreground">
{t("feedback.email.optional")}
</span>
</label>
<Input
id="feedback-email"
data-slot="feedback-email"
type="email"
autoComplete="email"
placeholder={t("feedback.email.placeholder")}
value={email}
onChange={(event) => {
setEmail(event.target.value);
}}
/>
</div>
)}
{error && (
<p
role="alert"
className="rounded-md bg-destructive/10 p-3 text-sm break-words text-destructive-text"
>
{error}
</p>
)}
</SoftFields>
<DialogFooter className="items-center">
{systemInfo && (
<p className="mr-auto min-w-0 truncate text-[11px] text-muted-foreground">
{t("feedback.included", {
version: systemInfo.app_version,
os: systemInfo.os,
arch: systemInfo.arch,
})}
</p>
)}
<RippleButton
variant="outline"
onClick={handleClose}
disabled={sending}
>
{t("common.buttons.cancel")}
</RippleButton>
<LoadingButton
data-slot="feedback-send"
isLoading={sending}
disabled={!canSend}
onClick={() => void send()}
>
<LuSend className="size-3.5" aria-hidden="true" />
{t("feedback.send")}
<kbd className="ml-1 hidden rounded bg-primary-foreground/15 px-1 font-sans text-[10px] leading-4 sm:inline">
{modKey}↵
</kbd>
</LoadingButton>
</DialogFooter>
</>
)}
</StepTransition>
</DialogContent>
</Dialog>
);
}
+25
View File
@@ -13,6 +13,7 @@ import {
LuInfo,
LuKeyboard,
LuLightbulb,
LuMessageSquareHeart,
LuPlug,
LuPuzzle,
LuTrash2,
@@ -221,6 +222,7 @@ interface RailNavProps {
onOpenAbout: () => void;
/** Opens the feature tips catalog. */
onOpenTips: () => void;
onOpenFeedback: () => void;
/**
* A remote session is running right now. The Cookie Bot item carries a dot so
* the state is legible from every other page — an overnight job you cannot
@@ -297,6 +299,7 @@ export function RailNav({
onNavigate,
onOpenAbout,
onOpenTips,
onOpenFeedback,
cookieBotRunning = false,
agentRequests = 0,
}: RailNavProps) {
@@ -537,6 +540,28 @@ export function RailNav({
</span>
</span>
</button>
<button
type="button"
role="menuitem"
data-slot="rail-open-feedback"
onClick={() => {
setMoreOpen(false);
onOpenFeedback();
}}
className="flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors duration-100 hover:bg-accent hover:text-accent-foreground"
>
<span className="grid size-5 shrink-0 place-items-center text-muted-foreground">
<LuMessageSquareHeart className="size-3" />
</span>
<span className="flex min-w-0 flex-col">
<span className="truncate text-xs font-medium text-foreground">
{t("rail.more.feedback")}
</span>
<span className="truncate text-[10px] text-muted-foreground">
{t("rail.more.feedbackHint")}
</span>
</span>
</button>
<button
type="button"
role="menuitem"
+3
View File
@@ -7,6 +7,7 @@ import {
ConsistencyScene,
DnsScene,
ExtensionsScene,
FeedbackScene,
GroupsScene,
ImportScene,
LinkRouteScene,
@@ -55,6 +56,8 @@ export function TipScene({ id }: { id: TipId }) {
return <ApiScene variant="api" />;
case "importProfiles":
return <ImportScene />;
case "feedback":
return <FeedbackScene />;
case "cloudBackup":
return <CloudSyncScene />;
case "cookieBot":
+85
View File
@@ -743,3 +743,88 @@ export function ImportScene() {
</Scene>
);
}
const FEEDBACK_ARC = travel(
bezier(
{ x: 116, y: 64 },
{ x: 150, y: 10 },
{ x: 214, y: 10 },
{ x: 250, y: 62 },
16,
),
0.5,
0.78,
);
/** A message is written, the logs clip on, and it flies to the Donut team. */
export function FeedbackScene() {
const { kf, tr } = useScene(5);
return (
<Scene>
<Window x={16} y={30} width={104} height={100} lines={0} />
<path
d="M30 60 h72 a6 6 0 0 1 6 6 v26 a6 6 0 0 1 -6 6 h-52 l-10 8 v-8 h-10 a6 6 0 0 1 -6 -6 v-26 a6 6 0 0 1 6 -6 z"
className="text-foreground"
/>
{[52, 40, 28].map((width, index) => (
<motion.path
key={width}
d={`M36 ${70 + index * 9} h${width}`}
strokeWidth={2}
className="text-foreground"
initial={false}
animate={{ pathLength: kf([0, 0, 1, 1]) }}
transition={tr([0, 0.06 + index * 0.1, 0.15 + index * 0.1, 1], {
ease: "linear",
})}
/>
))}
<motion.g
initial={false}
animate={{ opacity: kf([0, 0, 1, 1]), y: kf([8, 8, 0, 0]) }}
transition={tr([0, 0.36, 0.46, 1])}
>
<rect
x={92}
y={88}
width={22}
height={28}
rx={3}
className="fill-background"
/>
<path d="M97 96 h12 M97 102 h12 M97 108 h7" />
</motion.g>
<path d="M126 58 C154 14 210 14 238 56" strokeDasharray="2 5" />
<motion.g
initial={false}
animate={{ opacity: kf([0, 0, 1, 1, 0, 0]) }}
transition={tr([0, 0.48, 0.5, 0.76, 0.8, 1])}
>
<motion.g
className="text-foreground"
initial={false}
animate={{ x: kf(FEEDBACK_ARC.cx), y: kf(FEEDBACK_ARC.cy) }}
transition={tr(FEEDBACK_ARC.times, { ease: "linear" })}
>
<path d="M-7 1 L8 -6 L3 8 L0 2 Z M0 2 L8 -6" />
</motion.g>
</motion.g>
<motion.g
className="text-foreground"
initial={false}
animate={{ scale: kf([1, 1, 1.12, 1, 1]) }}
transition={tr([0, 0.78, 0.84, 0.9, 1])}
>
<circle cx={263} cy={84} r={18} />
<circle cx={263} cy={84} r={7} />
</motion.g>
<Check
x={286}
y={50}
className="text-success-text"
animate={{ pathLength: kf([0, 0, 1, 1]) }}
transition={tr([0, 0.82, 0.92, 1])}
/>
</Scene>
);
}
+10 -1
View File
@@ -43,6 +43,7 @@ interface UsageSettings {
api_enabled?: boolean;
mcp_remote_enabled?: boolean;
fingerprint_gate_disabled?: boolean;
feedback_sent_at?: number | null;
}
/** A slow read must not hold the tip back for long. */
@@ -96,7 +97,13 @@ async function loadFeatureUsage(
const wants = (...ids: TipId[]) => ids.some((id) => candidates.has(id));
const [settings, proxyChecked, isDefaultBrowser, trashUsed, agentUsed] =
await Promise.all([
wants("localApi", "automation", "remoteControl", "fingerprintGate")
wants(
"localApi",
"automation",
"remoteControl",
"fingerprintGate",
"feedback",
)
? known(invoke<UsageSettings>("get_app_settings"))
: undefined,
wants("proxyCheck") ? known(anyProxyChecked(source.proxyIds)) : undefined,
@@ -131,6 +138,8 @@ async function loadFeatureUsage(
isDefaultBrowser,
trashUsed,
agentUsed,
feedbackSent:
settings === undefined ? undefined : settings.feedback_sent_at != null,
};
}
+69 -4
View File
@@ -2419,7 +2419,14 @@
"wayfernLaunchArgsLimit": "Verwende höchstens {{count}} Startargumente mit jeweils bis zu {{length}} Zeichen.",
"tablePreferencesInvalid": "Diese Tabelleneinstellungen sind ungültig.",
"tablePreferencesLoadFailed": "Die Tabelleneinstellungen konnten nicht geladen werden. Versuchen Sie es erneut.",
"tablePreferencesSaveFailed": "Die Tabelleneinstellungen konnten nicht gespeichert werden. Versuchen Sie es erneut."
"tablePreferencesSaveFailed": "Die Tabelleneinstellungen konnten nicht gespeichert werden. Versuchen Sie es erneut.",
"feedbackMessageEmpty": "Schreiben Sie zuerst eine Nachricht.",
"feedbackMessageTooLong": "Die Nachricht ist zu lang. Das Limit liegt bei {{max}} Zeichen.",
"feedbackEmailInvalid": "Diese E-Mail-Adresse sieht nicht richtig aus.",
"feedbackInvalid": "Das Feedback wurde nicht angenommen. Prüfen Sie die Nachricht und versuchen Sie es erneut.",
"feedbackRateLimited": "Sie haben in kurzer Zeit viel Feedback gesendet. Versuchen Sie es in ein paar Minuten erneut.",
"feedbackSendFailed": "Das Feedback konnte nicht gesendet werden. Versuchen Sie es später erneut.",
"feedbackLogsUnreadable": "Die Protokolle konnten nicht gelesen werden. Schalten Sie „Aktuelle Protokolle anhängen“ aus und senden Sie erneut."
},
"rail": {
"profiles": "Profile",
@@ -2438,7 +2445,9 @@
"trash": "Papierkorb",
"trashHint": "Gelöschte Profile wiederherstellen",
"tips": "Tipps",
"tipsHint": "Funktionen Schritt für Schritt"
"tipsHint": "Funktionen Schritt für Schritt",
"feedback": "Feedback senden",
"feedbackHint": "Fehler melden oder Idee teilen"
},
"network": "Netzwerk",
"integrations": "Integrationen",
@@ -2557,7 +2566,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "Zum Papierkorb wechseln",
"goAgent": "Zu „Agent“ wechseln",
"openTips": "Tipps öffnen"
"openTips": "Tipps öffnen",
"sendFeedback": "Feedback senden"
},
"closeConfirm": {
"title": "Donut Browser schließen?",
@@ -3290,7 +3300,9 @@
"noteSeenAt": "Gesehen um {{time}}",
"noteSeen": "Gesehen",
"joined": "{{agent}} ist beigetreten",
"left": "{{agent}} hat die Sitzung verlassen"
"left": "{{agent}} hat die Sitzung verlassen",
"feedbackSent": "hat dem Donut-Team Feedback gesendet",
"feedbackLogs": "Protokolle angehängt"
},
"composer": {
"label": "Notiz für Ihren Agenten",
@@ -3570,6 +3582,12 @@
"title": "Diesen Desktop von donutbrowser.com aus steuern",
"body": "Bei aktivierter Fernsteuerung erreichen Agenten auf der Website die Profile dieses Rechners über eine ausgehende Verbindung. Aktivieren Sie sie unter „Integrationen“.",
"action": "Integrationen öffnen"
},
"feedback": {
"label": "Feedback",
"title": "Sagen Sie dem Team, was als Nächstes besser werden soll",
"body": "Funktioniert etwas nicht oder fehlt etwas? Senden Sie Feedback über das Menü „Mehr“. Ein Schalter hängt Ihre Protokolle an, damit das Team genau sieht, was schiefging.",
"action": "Feedback senden"
}
}
},
@@ -3613,5 +3631,52 @@
"noTags": "Keine Tags",
"noProxy": "Kein Proxy",
"encrypted": "Verschlüsselt"
},
"feedback": {
"title": "Feedback senden",
"description": "Sagen Sie dem Donut-Team, was Sie beschäftigt. Wir lesen jede Nachricht.",
"kindLabel": "Art des Feedbacks",
"kinds": {
"bug": {
"label": "Fehler",
"placeholder": "Was ist passiert, und was haben Sie erwartet? Schritte zum Nachstellen helfen sehr."
},
"idea": {
"label": "Idee",
"placeholder": "Was würde Donut für Sie besser machen?"
},
"praise": {
"label": "Lob",
"placeholder": "Was gefällt Ihnen? Das Team freut sich darüber."
},
"other": {
"label": "Sonstiges",
"placeholder": "Schreiben Sie uns, was Sie möchten."
}
},
"messageLabel": "Nachricht",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "Aktuelle Protokolle anhängen",
"description": "Protokolle helfen uns, Fehler zu finden. E-Mail-Adressen, IP-Adressen, Webadressen und Geheimnisse werden vorher entfernt.",
"preview": "Protokolle ansehen",
"loading": "Protokolle werden gelesen…",
"empty": "Es gibt noch keine Protokolle."
},
"email": {
"label": "E-Mail für eine Antwort",
"optional": "Optional",
"placeholder": "sie@example.com",
"signedIn": "Antworten gehen an {{email}}."
},
"included": "Wird mitgesendet: Donut {{version}}, {{os}} {{arch}}",
"send": "Senden",
"success": {
"title": "Vielen Dank!",
"body": "Ihr Feedback ist beim Donut-Team angekommen.",
"bodyWithLogs": "Ihr Feedback und Ihre Protokolle sind beim Donut-Team angekommen.",
"reply": "Wenn wir mehr Details brauchen, schreiben wir an {{email}}.",
"done": "Fertig"
}
}
}
+69 -4
View File
@@ -2419,7 +2419,14 @@
"wayfernLaunchArgsLimit": "Use at most {{count}} launch arguments, each up to {{length}} characters.",
"tablePreferencesInvalid": "These table settings are invalid.",
"tablePreferencesLoadFailed": "Could not load the table settings. Try again.",
"tablePreferencesSaveFailed": "Could not save the table settings. Try again."
"tablePreferencesSaveFailed": "Could not save the table settings. Try again.",
"feedbackMessageEmpty": "Write a message first.",
"feedbackMessageTooLong": "The message is too long. The limit is {{max}} characters.",
"feedbackEmailInvalid": "This email address does not look right.",
"feedbackInvalid": "The feedback was not accepted. Check the message and try again.",
"feedbackRateLimited": "You sent a lot of feedback in a short time. Try again in a few minutes.",
"feedbackSendFailed": "The feedback could not be sent. Try again later.",
"feedbackLogsUnreadable": "The logs could not be read. Turn off \"Include recent logs\" and send again."
},
"rail": {
"profiles": "Profiles",
@@ -2438,7 +2445,9 @@
"trash": "Trash",
"trashHint": "Restore deleted profiles",
"tips": "Tips",
"tipsHint": "Feature walkthroughs"
"tipsHint": "Feature walkthroughs",
"feedback": "Send feedback",
"feedbackHint": "Report a bug or share an idea"
},
"network": "Network",
"integrations": "Integrations",
@@ -2557,7 +2566,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "Go to Trash",
"goAgent": "Go to Agent",
"openTips": "Open tips"
"openTips": "Open tips",
"sendFeedback": "Send feedback"
},
"closeConfirm": {
"title": "Close Donut Browser?",
@@ -3290,7 +3300,9 @@
"noteSeenAt": "Seen at {{time}}",
"noteSeen": "Seen",
"joined": "{{agent}} joined",
"left": "{{agent}} left"
"left": "{{agent}} left",
"feedbackSent": "sent feedback to the Donut team",
"feedbackLogs": "Logs attached"
},
"composer": {
"label": "Note for your agent",
@@ -3570,6 +3582,12 @@
"title": "Drive this desktop from donutbrowser.com",
"body": "With remote control on, agents on the website reach this machine's profiles over an outbound bridge. Turn it on under Integrations.",
"action": "Open Integrations"
},
"feedback": {
"label": "Feedback",
"title": "Tell the team what to fix next",
"body": "Something broken or missing? Send feedback from the More menu. One switch attaches your logs, so the team sees exactly what went wrong.",
"action": "Send feedback"
}
}
},
@@ -3613,5 +3631,52 @@
"noTags": "No tags",
"noProxy": "No proxy",
"encrypted": "Encrypted"
},
"feedback": {
"title": "Send feedback",
"description": "Tell the Donut team what is on your mind. We read every message.",
"kindLabel": "Type of feedback",
"kinds": {
"bug": {
"label": "Bug",
"placeholder": "What happened, and what did you expect? Steps to repeat it help a lot."
},
"idea": {
"label": "Idea",
"placeholder": "What would make Donut better for you?"
},
"praise": {
"label": "Praise",
"placeholder": "What do you like? The team will be glad to hear it."
},
"other": {
"label": "Other",
"placeholder": "Tell us anything."
}
},
"messageLabel": "Message",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "Include recent logs",
"description": "Logs help us find bugs. Email addresses, IP addresses, web addresses and secrets are removed first.",
"preview": "Preview logs",
"loading": "Reading logs…",
"empty": "There are no logs yet."
},
"email": {
"label": "Email for a reply",
"optional": "Optional",
"placeholder": "you@example.com",
"signedIn": "Replies go to {{email}}."
},
"included": "Also sent: Donut {{version}}, {{os}} {{arch}}",
"send": "Send",
"success": {
"title": "Thank you!",
"body": "Your feedback is with the Donut team.",
"bodyWithLogs": "Your feedback and logs are with the Donut team.",
"reply": "If we need more details, we will write to {{email}}.",
"done": "Done"
}
}
}
+69 -4
View File
@@ -2429,7 +2429,14 @@
"wayfernLaunchArgsLimit": "Usa como máximo {{count}} argumentos de inicio, de hasta {{length}} caracteres cada uno.",
"tablePreferencesInvalid": "La configuración de la tabla no es válida.",
"tablePreferencesLoadFailed": "No se pudo cargar la configuración de la tabla. Inténtalo de nuevo.",
"tablePreferencesSaveFailed": "No se pudo guardar la configuración de la tabla. Inténtalo de nuevo."
"tablePreferencesSaveFailed": "No se pudo guardar la configuración de la tabla. Inténtalo de nuevo.",
"feedbackMessageEmpty": "Escribe un mensaje primero.",
"feedbackMessageTooLong": "El mensaje es demasiado largo. El límite es de {{max}} caracteres.",
"feedbackEmailInvalid": "Esta dirección de correo no parece correcta.",
"feedbackInvalid": "Los comentarios no se aceptaron. Revisa el mensaje e inténtalo de nuevo.",
"feedbackRateLimited": "Enviaste muchos comentarios en poco tiempo. Inténtalo de nuevo en unos minutos.",
"feedbackSendFailed": "No se pudieron enviar los comentarios. Inténtalo más tarde.",
"feedbackLogsUnreadable": "No se pudieron leer los registros. Desactiva «Incluir registros recientes» y envía de nuevo."
},
"rail": {
"profiles": "Perfiles",
@@ -2448,7 +2455,9 @@
"trash": "Papelera",
"trashHint": "Restaurar perfiles eliminados",
"tips": "Consejos",
"tipsHint": "Recorridos por las funciones"
"tipsHint": "Recorridos por las funciones",
"feedback": "Enviar comentarios",
"feedbackHint": "Informa de un error o comparte una idea"
},
"network": "Red",
"integrations": "Integraciones",
@@ -2567,7 +2576,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "Ir a la Papelera",
"goAgent": "Ir a Agente",
"openTips": "Abrir consejos"
"openTips": "Abrir consejos",
"sendFeedback": "Enviar comentarios"
},
"closeConfirm": {
"title": "¿Cerrar Donut Browser?",
@@ -3323,7 +3333,9 @@
"noteSeenAt": "Vista a las {{time}}",
"noteSeen": "Vista",
"joined": "{{agent}} se unió",
"left": "{{agent}} se fue"
"left": "{{agent}} se fue",
"feedbackSent": "envió comentarios al equipo de Donut",
"feedbackLogs": "Registros adjuntos"
},
"composer": {
"label": "Nota para tu agente",
@@ -3603,6 +3615,12 @@
"title": "Controla este equipo desde donutbrowser.com",
"body": "Con el control remoto activado, los agentes del sitio web llegan a los perfiles de esta máquina a través de un puente saliente. Actívalo en Integraciones.",
"action": "Abrir Integraciones"
},
"feedback": {
"label": "Comentarios",
"title": "Dile al equipo qué arreglar después",
"body": "¿Algo falla o echas algo en falta? Envía comentarios desde el menú Más. Un interruptor adjunta tus registros para que el equipo vea exactamente qué salió mal.",
"action": "Enviar comentarios"
}
}
},
@@ -3646,5 +3664,52 @@
"noTags": "Sin etiquetas",
"noProxy": "Sin proxy",
"encrypted": "Cifrado"
},
"feedback": {
"title": "Enviar comentarios",
"description": "Cuéntale al equipo de Donut lo que piensas. Leemos todos los mensajes.",
"kindLabel": "Tipo de comentario",
"kinds": {
"bug": {
"label": "Error",
"placeholder": "¿Qué pasó y qué esperabas? Los pasos para reproducirlo ayudan mucho."
},
"idea": {
"label": "Idea",
"placeholder": "¿Qué haría que Donut fuera mejor para ti?"
},
"praise": {
"label": "Elogio",
"placeholder": "¿Qué te gusta? Al equipo le encantará leerlo."
},
"other": {
"label": "Otro",
"placeholder": "Cuéntanos lo que quieras."
}
},
"messageLabel": "Mensaje",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "Incluir registros recientes",
"description": "Los registros nos ayudan a encontrar errores. Antes se eliminan las direcciones de correo, las direcciones IP, las direcciones web y los secretos.",
"preview": "Ver registros",
"loading": "Leyendo registros…",
"empty": "Todavía no hay registros."
},
"email": {
"label": "Correo para responderte",
"optional": "Opcional",
"placeholder": "tu@example.com",
"signedIn": "Las respuestas irán a {{email}}."
},
"included": "También se envía: Donut {{version}}, {{os}} {{arch}}",
"send": "Enviar",
"success": {
"title": "¡Gracias!",
"body": "Tus comentarios ya están con el equipo de Donut.",
"bodyWithLogs": "Tus comentarios y registros ya están con el equipo de Donut.",
"reply": "Si necesitamos más detalles, te escribiremos a {{email}}.",
"done": "Listo"
}
}
}
+69 -4
View File
@@ -2429,7 +2429,14 @@
"wayfernLaunchArgsLimit": "Utilisez au maximum {{count}} arguments de lancement, de {{length}} caractères chacun au plus.",
"tablePreferencesInvalid": "Ces paramètres du tableau sont invalides.",
"tablePreferencesLoadFailed": "Impossible de charger les paramètres du tableau. Réessayez.",
"tablePreferencesSaveFailed": "Impossible d’enregistrer les paramètres du tableau. Réessayez."
"tablePreferencesSaveFailed": "Impossible d’enregistrer les paramètres du tableau. Réessayez.",
"feedbackMessageEmpty": "Écrivez d'abord un message.",
"feedbackMessageTooLong": "Le message est trop long. La limite est de {{max}} caractères.",
"feedbackEmailInvalid": "Cette adresse e-mail ne semble pas correcte.",
"feedbackInvalid": "L'avis n'a pas été accepté. Vérifiez le message et réessayez.",
"feedbackRateLimited": "Vous avez envoyé beaucoup d'avis en peu de temps. Réessayez dans quelques minutes.",
"feedbackSendFailed": "L'avis n'a pas pu être envoyé. Réessayez plus tard.",
"feedbackLogsUnreadable": "Les journaux n'ont pas pu être lus. Désactivez « Joindre les journaux récents » et renvoyez."
},
"rail": {
"profiles": "Profils",
@@ -2448,7 +2455,9 @@
"trash": "Corbeille",
"trashHint": "Restaurer des profils supprimés",
"tips": "Astuces",
"tipsHint": "Découverte des fonctions"
"tipsHint": "Découverte des fonctions",
"feedback": "Envoyer un avis",
"feedbackHint": "Signaler un bug ou partager une idée"
},
"network": "Réseau",
"integrations": "Intégrations",
@@ -2567,7 +2576,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "Aller à la Corbeille",
"goAgent": "Aller à Agent",
"openTips": "Ouvrir les astuces"
"openTips": "Ouvrir les astuces",
"sendFeedback": "Envoyer un avis"
},
"closeConfirm": {
"title": "Fermer Donut Browser ?",
@@ -3323,7 +3333,9 @@
"noteSeenAt": "Vue à {{time}}",
"noteSeen": "Vue",
"joined": "{{agent}} a rejoint la conversation",
"left": "{{agent}} est parti"
"left": "{{agent}} est parti",
"feedbackSent": "a envoyé un avis à l'équipe Donut",
"feedbackLogs": "Journaux joints"
},
"composer": {
"label": "Note pour votre agent",
@@ -3603,6 +3615,12 @@
"title": "Pilotez ce poste depuis donutbrowser.com",
"body": "Avec le contrôle à distance activé, les agents du site web atteignent les profils de cette machine par un pont sortant. Activez-le dans Intégrations.",
"action": "Ouvrir Intégrations"
},
"feedback": {
"label": "Avis",
"title": "Dites à l'équipe quoi corriger ensuite",
"body": "Quelque chose ne marche pas ou vous manque ? Envoyez un avis depuis le menu Plus. Un interrupteur joint vos journaux, pour que l'équipe voie exactement ce qui s'est passé.",
"action": "Envoyer un avis"
}
}
},
@@ -3646,5 +3664,52 @@
"noTags": "Aucune étiquette",
"noProxy": "Aucun proxy",
"encrypted": "Chiffré"
},
"feedback": {
"title": "Envoyer un avis",
"description": "Dites à l'équipe Donut ce que vous en pensez. Nous lisons chaque message.",
"kindLabel": "Type d'avis",
"kinds": {
"bug": {
"label": "Bug",
"placeholder": "Que s'est-il passé, et à quoi vous attendiez-vous ? Les étapes pour le reproduire aident beaucoup."
},
"idea": {
"label": "Idée",
"placeholder": "Qu'est-ce qui rendrait Donut meilleur pour vous ?"
},
"praise": {
"label": "Compliment",
"placeholder": "Qu'est-ce qui vous plaît ? L'équipe sera ravie de le lire."
},
"other": {
"label": "Autre",
"placeholder": "Dites-nous ce que vous voulez."
}
},
"messageLabel": "Message",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "Joindre les journaux récents",
"description": "Les journaux nous aident à trouver les bugs. Les adresses e-mail, adresses IP, adresses web et secrets sont retirés avant l'envoi.",
"preview": "Voir les journaux",
"loading": "Lecture des journaux…",
"empty": "Il n'y a pas encore de journaux."
},
"email": {
"label": "E-mail pour la réponse",
"optional": "Facultatif",
"placeholder": "vous@example.com",
"signedIn": "Les réponses iront à {{email}}."
},
"included": "Envoyé aussi : Donut {{version}}, {{os}} {{arch}}",
"send": "Envoyer",
"success": {
"title": "Merci !",
"body": "Votre avis est bien arrivé chez l'équipe Donut.",
"bodyWithLogs": "Votre avis et vos journaux sont bien arrivés chez l'équipe Donut.",
"reply": "Si nous avons besoin de détails, nous vous écrirons à {{email}}.",
"done": "Terminé"
}
}
}
+69 -4
View File
@@ -2419,7 +2419,14 @@
"wayfernLaunchArgsLimit": "起動引数は {{count}} 個まで、1 つあたり {{length}} 文字までにしてください。",
"tablePreferencesInvalid": "テーブル設定が無効です。",
"tablePreferencesLoadFailed": "テーブル設定を読み込めませんでした。再試行してください。",
"tablePreferencesSaveFailed": "テーブル設定を保存できませんでした。再試行してください。"
"tablePreferencesSaveFailed": "テーブル設定を保存できませんでした。再試行してください。",
"feedbackMessageEmpty": "先にメッセージを入力してください。",
"feedbackMessageTooLong": "メッセージが長すぎます。上限は {{max}} 文字です。",
"feedbackEmailInvalid": "このメールアドレスは正しくないようです。",
"feedbackInvalid": "フィードバックを受け付けられませんでした。メッセージを確認して、もう一度お試しください。",
"feedbackRateLimited": "短時間に多くのフィードバックが送信されました。数分後にもう一度お試しください。",
"feedbackSendFailed": "フィードバックを送信できませんでした。しばらくしてからもう一度お試しください。",
"feedbackLogsUnreadable": "ログを読み込めませんでした。「最近のログを添付」をオフにして、もう一度送信してください。"
},
"rail": {
"profiles": "プロファイル",
@@ -2438,7 +2445,9 @@
"trash": "ゴミ箱",
"trashHint": "削除したプロファイルを復元",
"tips": "ヒント",
"tipsHint": "機能の使い方"
"tipsHint": "機能の使い方",
"feedback": "フィードバックを送信",
"feedbackHint": "不具合の報告やアイデアの共有"
},
"network": "ネットワーク",
"integrations": "連携",
@@ -2557,7 +2566,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "ゴミ箱へ移動",
"goAgent": "エージェントへ移動",
"openTips": "ヒントを開く"
"openTips": "ヒントを開く",
"sendFeedback": "フィードバックを送信"
},
"closeConfirm": {
"title": "Donut Browser を閉じますか?",
@@ -3290,7 +3300,9 @@
"noteSeenAt": "{{time}} に既読",
"noteSeen": "既読",
"joined": "{{agent}} が参加しました",
"left": "{{agent}} が退出しました"
"left": "{{agent}} が退出しました",
"feedbackSent": "が Donut チームにフィードバックを送信しました",
"feedbackLogs": "ログ添付あり"
},
"composer": {
"label": "エージェントへのメモ",
@@ -3570,6 +3582,12 @@
"title": "donutbrowser.com からこのデスクトップを操作",
"body": "リモートコントロールを有効にすると、ウェブサイト上のエージェントが送信方向のブリッジ経由でこのマシンのプロファイルにアクセスできます。「連携」で有効にしてください。",
"action": "連携を開く"
},
"feedback": {
"label": "フィードバック",
"title": "次に直すべきことをチームに伝える",
"body": "不具合や足りない機能はありませんか?「その他」メニューからフィードバックを送れます。スイッチ 1 つでログを添付でき、何が起きたかをチームが正確に把握できます。",
"action": "フィードバックを送信"
}
}
},
@@ -3613,5 +3631,52 @@
"noTags": "タグなし",
"noProxy": "プロキシなし",
"encrypted": "暗号化"
},
"feedback": {
"title": "フィードバックを送信",
"description": "Donut チームにご意見をお聞かせください。すべてのメッセージに目を通しています。",
"kindLabel": "フィードバックの種類",
"kinds": {
"bug": {
"label": "不具合",
"placeholder": "何が起きて、本来はどうなるべきでしたか?再現手順があると助かります。"
},
"idea": {
"label": "アイデア",
"placeholder": "Donut がどうなれば、もっと使いやすくなりますか?"
},
"praise": {
"label": "称賛",
"placeholder": "気に入っている点を教えてください。チームの励みになります。"
},
"other": {
"label": "その他",
"placeholder": "どんなことでもお聞かせください。"
}
},
"messageLabel": "メッセージ",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "最近のログを添付",
"description": "ログは不具合の調査に役立ちます。メールアドレス、IP アドレス、Web アドレス、シークレットは事前に削除されます。",
"preview": "ログを確認",
"loading": "ログを読み込んでいます…",
"empty": "まだログはありません。"
},
"email": {
"label": "返信用メールアドレス",
"optional": "任意",
"placeholder": "you@example.com",
"signedIn": "返信は {{email}} に届きます。"
},
"included": "あわせて送信: Donut {{version}}、{{os}} {{arch}}",
"send": "送信",
"success": {
"title": "ありがとうございます!",
"body": "フィードバックは Donut チームに届きました。",
"bodyWithLogs": "フィードバックとログは Donut チームに届きました。",
"reply": "詳細が必要な場合は {{email}} にご連絡します。",
"done": "完了"
}
}
}
+69 -4
View File
@@ -2419,7 +2419,14 @@
"wayfernLaunchArgsLimit": "실행 인수는 최대 {{count}}개, 각각 {{length}}자 이하로 입력하세요.",
"tablePreferencesInvalid": "테이블 설정이 올바르지 않습니다.",
"tablePreferencesLoadFailed": "테이블 설정을 불러올 수 없습니다. 다시 시도하세요.",
"tablePreferencesSaveFailed": "테이블 설정을 저장할 수 없습니다. 다시 시도하세요."
"tablePreferencesSaveFailed": "테이블 설정을 저장할 수 없습니다. 다시 시도하세요.",
"feedbackMessageEmpty": "먼저 메시지를 입력하세요.",
"feedbackMessageTooLong": "메시지가 너무 깁니다. 최대 {{max}}자까지 입력할 수 있습니다.",
"feedbackEmailInvalid": "이메일 주소가 올바르지 않은 것 같습니다.",
"feedbackInvalid": "피드백이 접수되지 않았습니다. 메시지를 확인하고 다시 시도하세요.",
"feedbackRateLimited": "짧은 시간에 피드백을 너무 많이 보냈습니다. 몇 분 후에 다시 시도하세요.",
"feedbackSendFailed": "피드백을 보낼 수 없습니다. 나중에 다시 시도하세요.",
"feedbackLogsUnreadable": "로그를 읽을 수 없습니다. '최근 로그 첨부'를 끄고 다시 보내세요."
},
"rail": {
"profiles": "프로필",
@@ -2438,7 +2445,9 @@
"trash": "휴지통",
"trashHint": "삭제한 프로필 복원",
"tips": "팁",
"tipsHint": "기능 안내"
"tipsHint": "기능 안내",
"feedback": "피드백 보내기",
"feedbackHint": "버그 신고 또는 아이디어 공유"
},
"network": "네트워크",
"integrations": "통합",
@@ -2557,7 +2566,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "휴지통으로 이동",
"goAgent": "에이전트로 이동",
"openTips": "팁 열기"
"openTips": "팁 열기",
"sendFeedback": "피드백 보내기"
},
"closeConfirm": {
"title": "Donut Browser를 닫으시겠습니까?",
@@ -3290,7 +3300,9 @@
"noteSeenAt": "{{time}}에 확인함",
"noteSeen": "확인함",
"joined": "{{agent}} 참가",
"left": "{{agent}} 나감"
"left": "{{agent}} 나감",
"feedbackSent": "Donut 팀에 피드백을 보냈습니다",
"feedbackLogs": "로그 첨부됨"
},
"composer": {
"label": "에이전트에게 보낼 메모",
@@ -3570,6 +3582,12 @@
"title": "donutbrowser.com에서 이 데스크톱 제어",
"body": "원격 제어를 켜면 웹사이트의 에이전트가 아웃바운드 브리지를 통해 이 컴퓨터의 프로필에 접근합니다. 통합에서 켜세요.",
"action": "통합 열기"
},
"feedback": {
"label": "피드백",
"title": "다음에 고칠 점을 팀에 알려 주세요",
"body": "고장 난 기능이나 아쉬운 점이 있나요? 더 보기 메뉴에서 피드백을 보내세요. 스위치 하나로 로그를 첨부하면 팀이 무엇이 잘못되었는지 정확히 알 수 있습니다.",
"action": "피드백 보내기"
}
}
},
@@ -3613,5 +3631,52 @@
"noTags": "태그 없음",
"noProxy": "프록시 없음",
"encrypted": "암호화됨"
},
"feedback": {
"title": "피드백 보내기",
"description": "Donut 팀에 의견을 들려주세요. 모든 메시지를 읽습니다.",
"kindLabel": "피드백 유형",
"kinds": {
"bug": {
"label": "버그",
"placeholder": "무슨 일이 있었고, 원래는 어떻게 되어야 했나요? 재현 단계가 있으면 큰 도움이 됩니다."
},
"idea": {
"label": "아이디어",
"placeholder": "Donut이 어떻게 바뀌면 더 좋을까요?"
},
"praise": {
"label": "칭찬",
"placeholder": "어떤 점이 마음에 드시나요? 팀에게 큰 힘이 됩니다."
},
"other": {
"label": "기타",
"placeholder": "무엇이든 알려 주세요."
}
},
"messageLabel": "메시지",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "최근 로그 첨부",
"description": "로그는 버그를 찾는 데 도움이 됩니다. 이메일 주소, IP 주소, 웹 주소, 비밀 정보는 먼저 제거됩니다.",
"preview": "로그 미리 보기",
"loading": "로그를 읽는 중…",
"empty": "아직 로그가 없습니다."
},
"email": {
"label": "답장 받을 이메일",
"optional": "선택 사항",
"placeholder": "you@example.com",
"signedIn": "답장은 {{email}}(으)로 보내 드립니다."
},
"included": "함께 전송: Donut {{version}}, {{os}} {{arch}}",
"send": "보내기",
"success": {
"title": "감사합니다!",
"body": "피드백이 Donut 팀에 전달되었습니다.",
"bodyWithLogs": "피드백과 로그가 Donut 팀에 전달되었습니다.",
"reply": "더 자세한 내용이 필요하면 {{email}}(으)로 연락드리겠습니다.",
"done": "완료"
}
}
}
+69 -4
View File
@@ -2429,7 +2429,14 @@
"wayfernLaunchArgsLimit": "Use no máximo {{count}} argumentos de inicialização, com até {{length}} caracteres cada.",
"tablePreferencesInvalid": "Estas configurações da tabela são inválidas.",
"tablePreferencesLoadFailed": "Não foi possível carregar as configurações da tabela. Tente novamente.",
"tablePreferencesSaveFailed": "Não foi possível salvar as configurações da tabela. Tente novamente."
"tablePreferencesSaveFailed": "Não foi possível salvar as configurações da tabela. Tente novamente.",
"feedbackMessageEmpty": "Escreva uma mensagem primeiro.",
"feedbackMessageTooLong": "A mensagem é longa demais. O limite é de {{max}} caracteres.",
"feedbackEmailInvalid": "Este endereço de e-mail não parece correto.",
"feedbackInvalid": "O feedback não foi aceito. Verifique a mensagem e tente novamente.",
"feedbackRateLimited": "Você enviou muito feedback em pouco tempo. Tente novamente em alguns minutos.",
"feedbackSendFailed": "Não foi possível enviar o feedback. Tente novamente mais tarde.",
"feedbackLogsUnreadable": "Não foi possível ler os logs. Desative \"Incluir logs recentes\" e envie novamente."
},
"rail": {
"profiles": "Perfis",
@@ -2448,7 +2455,9 @@
"trash": "Lixeira",
"trashHint": "Restaurar perfis excluídos",
"tips": "Dicas",
"tipsHint": "Passo a passo dos recursos"
"tipsHint": "Passo a passo dos recursos",
"feedback": "Enviar feedback",
"feedbackHint": "Relatar um erro ou compartilhar uma ideia"
},
"network": "Rede",
"integrations": "Integrações",
@@ -2567,7 +2576,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "Ir para a Lixeira",
"goAgent": "Ir para Agente",
"openTips": "Abrir dicas"
"openTips": "Abrir dicas",
"sendFeedback": "Enviar feedback"
},
"closeConfirm": {
"title": "Fechar Donut Browser?",
@@ -3323,7 +3333,9 @@
"noteSeenAt": "Vista às {{time}}",
"noteSeen": "Vista",
"joined": "{{agent}} entrou",
"left": "{{agent}} saiu"
"left": "{{agent}} saiu",
"feedbackSent": "enviou feedback à equipe do Donut",
"feedbackLogs": "Logs anexados"
},
"composer": {
"label": "Nota para o seu agente",
@@ -3603,6 +3615,12 @@
"title": "Controle este computador pelo donutbrowser.com",
"body": "Com o controle remoto ativado, os agentes do site alcançam os perfis desta máquina por uma ponte de saída. Ative-o em Integrações.",
"action": "Abrir Integrações"
},
"feedback": {
"label": "Feedback",
"title": "Diga à equipe o que corrigir a seguir",
"body": "Algo quebrado ou faltando? Envie feedback pelo menu Mais. Um botão anexa seus logs, para a equipe ver exatamente o que deu errado.",
"action": "Enviar feedback"
}
}
},
@@ -3646,5 +3664,52 @@
"noTags": "Sem etiquetas",
"noProxy": "Sem proxy",
"encrypted": "Criptografado"
},
"feedback": {
"title": "Enviar feedback",
"description": "Conte à equipe do Donut o que você pensa. Lemos todas as mensagens.",
"kindLabel": "Tipo de feedback",
"kinds": {
"bug": {
"label": "Erro",
"placeholder": "O que aconteceu e o que você esperava? Os passos para reproduzir ajudam muito."
},
"idea": {
"label": "Ideia",
"placeholder": "O que deixaria o Donut melhor para você?"
},
"praise": {
"label": "Elogio",
"placeholder": "Do que você gosta? A equipe vai adorar saber."
},
"other": {
"label": "Outro",
"placeholder": "Conte o que quiser."
}
},
"messageLabel": "Mensagem",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "Incluir logs recentes",
"description": "Os logs nos ajudam a encontrar erros. Endereços de e-mail, endereços IP, endereços web e segredos são removidos antes.",
"preview": "Ver logs",
"loading": "Lendo os logs…",
"empty": "Ainda não há logs."
},
"email": {
"label": "E-mail para resposta",
"optional": "Opcional",
"placeholder": "voce@example.com",
"signedIn": "As respostas vão para {{email}}."
},
"included": "Também enviado: Donut {{version}}, {{os}} {{arch}}",
"send": "Enviar",
"success": {
"title": "Obrigado!",
"body": "Seu feedback chegou à equipe do Donut.",
"bodyWithLogs": "Seu feedback e seus logs chegaram à equipe do Donut.",
"reply": "Se precisarmos de mais detalhes, escreveremos para {{email}}.",
"done": "Concluir"
}
}
}
+69 -4
View File
@@ -2439,7 +2439,14 @@
"wayfernLaunchArgsLimit": "Используйте не более {{count}} аргументов запуска, каждый не длиннее {{length}} символов.",
"tablePreferencesInvalid": "Недопустимые настройки таблицы.",
"tablePreferencesLoadFailed": "Не удалось загрузить настройки таблицы. Повторите попытку.",
"tablePreferencesSaveFailed": "Не удалось сохранить настройки таблицы. Повторите попытку."
"tablePreferencesSaveFailed": "Не удалось сохранить настройки таблицы. Повторите попытку.",
"feedbackMessageEmpty": "Сначала напишите сообщение.",
"feedbackMessageTooLong": "Сообщение слишком длинное. Максимум — {{max}} символов.",
"feedbackEmailInvalid": "Похоже, адрес почты указан неверно.",
"feedbackInvalid": "Отзыв не принят. Проверьте сообщение и попробуйте снова.",
"feedbackRateLimited": "Вы отправили много отзывов за короткое время. Попробуйте снова через несколько минут.",
"feedbackSendFailed": "Не удалось отправить отзыв. Попробуйте позже.",
"feedbackLogsUnreadable": "Не удалось прочитать логи. Выключите «Приложить последние логи» и отправьте снова."
},
"rail": {
"profiles": "Профили",
@@ -2458,7 +2465,9 @@
"trash": "Корзина",
"trashHint": "Восстановить удалённые профили",
"tips": "Подсказки",
"tipsHint": "Обзор возможностей"
"tipsHint": "Обзор возможностей",
"feedback": "Отправить отзыв",
"feedbackHint": "Сообщить об ошибке или предложить идею"
},
"network": "Сеть",
"integrations": "Интеграции",
@@ -2577,7 +2586,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "Перейти в Корзину",
"goAgent": "Перейти к агенту",
"openTips": "Открыть подсказки"
"openTips": "Открыть подсказки",
"sendFeedback": "Отправить отзыв"
},
"closeConfirm": {
"title": "Закрыть Donut Browser?",
@@ -3356,7 +3366,9 @@
"noteSeenAt": "Прочитано в {{time}}",
"noteSeen": "Прочитано",
"joined": "{{agent}}: подключился",
"left": "{{agent}}: отключился"
"left": "{{agent}}: отключился",
"feedbackSent": "отправил отзыв команде Donut",
"feedbackLogs": "Логи приложены"
},
"composer": {
"label": "Заметка для агента",
@@ -3636,6 +3648,12 @@
"title": "Управляйте этим компьютером с donutbrowser.com",
"body": "При включённом удалённом управлении агенты на сайте получают доступ к профилям этой машины через исходящий мост. Включите его в разделе «Интеграции».",
"action": "Открыть Интеграции"
},
"feedback": {
"label": "Отзыв",
"title": "Подскажите команде, что исправить дальше",
"body": "Что-то сломалось или чего-то не хватает? Отправьте отзыв из меню «Ещё». Один переключатель прикладывает логи, и команда точно увидит, что пошло не так.",
"action": "Отправить отзыв"
}
}
},
@@ -3679,5 +3697,52 @@
"noTags": "Без тегов",
"noProxy": "Без прокси",
"encrypted": "Зашифровано"
},
"feedback": {
"title": "Отправить отзыв",
"description": "Расскажите команде Donut, что вы думаете. Мы читаем каждое сообщение.",
"kindLabel": "Тип отзыва",
"kinds": {
"bug": {
"label": "Ошибка",
"placeholder": "Что произошло и чего вы ожидали? Шаги для воспроизведения очень помогают."
},
"idea": {
"label": "Идея",
"placeholder": "Что сделало бы Donut удобнее для вас?"
},
"praise": {
"label": "Похвала",
"placeholder": "Что вам нравится? Команде будет приятно это прочитать."
},
"other": {
"label": "Другое",
"placeholder": "Напишите что угодно."
}
},
"messageLabel": "Сообщение",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "Приложить последние логи",
"description": "Логи помогают нам находить ошибки. Адреса почты, IP-адреса, веб-адреса и секреты удаляются заранее.",
"preview": "Посмотреть логи",
"loading": "Читаем логи…",
"empty": "Логов пока нет."
},
"email": {
"label": "Почта для ответа",
"optional": "Необязательно",
"placeholder": "you@example.com",
"signedIn": "Ответы придут на {{email}}."
},
"included": "Также отправляется: Donut {{version}}, {{os}} {{arch}}",
"send": "Отправить",
"success": {
"title": "Спасибо!",
"body": "Ваш отзыв получен командой Donut.",
"bodyWithLogs": "Ваш отзыв и логи получены командой Donut.",
"reply": "Если понадобятся подробности, мы напишем на {{email}}.",
"done": "Готово"
}
}
}
+69 -4
View File
@@ -2419,7 +2419,14 @@
"wayfernLaunchArgsLimit": "En fazla {{count}} başlatma argümanı kullanın; her biri en fazla {{length}} karakter olsun.",
"tablePreferencesInvalid": "Bu tablo ayarları geçersiz.",
"tablePreferencesLoadFailed": "Tablo ayarları yüklenemedi. Tekrar deneyin.",
"tablePreferencesSaveFailed": "Tablo ayarları kaydedilemedi. Tekrar deneyin."
"tablePreferencesSaveFailed": "Tablo ayarları kaydedilemedi. Tekrar deneyin.",
"feedbackMessageEmpty": "Önce bir mesaj yazın.",
"feedbackMessageTooLong": "Mesaj çok uzun. Sınır {{max}} karakter.",
"feedbackEmailInvalid": "Bu e-posta adresi doğru görünmüyor.",
"feedbackInvalid": "Geri bildirim kabul edilmedi. Mesajı kontrol edip tekrar deneyin.",
"feedbackRateLimited": "Kısa sürede çok fazla geri bildirim gönderdiniz. Birkaç dakika sonra tekrar deneyin.",
"feedbackSendFailed": "Geri bildirim gönderilemedi. Daha sonra tekrar deneyin.",
"feedbackLogsUnreadable": "Günlükler okunamadı. \"Son günlükleri ekle\" seçeneğini kapatıp tekrar gönderin."
},
"rail": {
"profiles": "Profiller",
@@ -2438,7 +2445,9 @@
"trash": "Çöp Kutusu",
"trashHint": "Silinen profilleri geri yükle",
"tips": "İpuçları",
"tipsHint": "Özellik turları"
"tipsHint": "Özellik turları",
"feedback": "Geri bildirim gönder",
"feedbackHint": "Hata bildirin veya fikir paylaşın"
},
"network": "Ağ",
"integrations": "Entegrasyonlar",
@@ -2557,7 +2566,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "Çöp Kutusuna git",
"goAgent": "Ajan'a git",
"openTips": "İpuçlarını aç"
"openTips": "İpuçlarını aç",
"sendFeedback": "Geri bildirim gönder"
},
"closeConfirm": {
"title": "Donut Browser kapatılsın mı?",
@@ -3290,7 +3300,9 @@
"noteSeenAt": "{{time}} saatinde görüldü",
"noteSeen": "Görüldü",
"joined": "{{agent}} katıldı",
"left": "{{agent}} ayrıldı"
"left": "{{agent}} ayrıldı",
"feedbackSent": "Donut ekibine geri bildirim gönderdi",
"feedbackLogs": "Günlükler eklendi"
},
"composer": {
"label": "Ajanınız için not",
@@ -3570,6 +3582,12 @@
"title": "Bu masaüstünü donutbrowser.com'dan yönetin",
"body": "Uzaktan denetim açıkken web sitesindeki ajanlar bu makinenin profillerine giden yönlü bir köprü üzerinden ulaşır. Entegrasyonlar'dan açın.",
"action": "Entegrasyonları aç"
},
"feedback": {
"label": "Geri bildirim",
"title": "Ekibe sırada neyin düzeltileceğini söyleyin",
"body": "Bir şey bozuk mu ya da eksik mi? Diğer menüsünden geri bildirim gönderin. Tek bir anahtar günlüklerinizi ekler, böylece ekip neyin ters gittiğini tam olarak görür.",
"action": "Geri bildirim gönder"
}
}
},
@@ -3613,5 +3631,52 @@
"noTags": "Etiket yok",
"noProxy": "Proxy yok",
"encrypted": "Şifreli"
},
"feedback": {
"title": "Geri bildirim gönder",
"description": "Donut ekibine düşüncelerinizi anlatın. Her mesajı okuyoruz.",
"kindLabel": "Geri bildirim türü",
"kinds": {
"bug": {
"label": "Hata",
"placeholder": "Ne oldu ve ne bekliyordunuz? Tekrarlama adımları çok yardımcı olur."
},
"idea": {
"label": "Fikir",
"placeholder": "Donut'u sizin için ne daha iyi yapardı?"
},
"praise": {
"label": "Övgü",
"placeholder": "Neyi seviyorsunuz? Ekip bunu duymaktan mutlu olur."
},
"other": {
"label": "Diğer",
"placeholder": "Bize her şeyi yazabilirsiniz."
}
},
"messageLabel": "Mesaj",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "Son günlükleri ekle",
"description": "Günlükler hataları bulmamıza yardım eder. E-posta adresleri, IP adresleri, web adresleri ve gizli bilgiler önceden kaldırılır.",
"preview": "Günlükleri gör",
"loading": "Günlükler okunuyor…",
"empty": "Henüz günlük yok."
},
"email": {
"label": "Yanıt için e-posta",
"optional": "İsteğe bağlı",
"placeholder": "siz@example.com",
"signedIn": "Yanıtlar {{email}} adresine gider."
},
"included": "Ayrıca gönderilir: Donut {{version}}, {{os}} {{arch}}",
"send": "Gönder",
"success": {
"title": "Teşekkürler!",
"body": "Geri bildiriminiz Donut ekibine ulaştı.",
"bodyWithLogs": "Geri bildiriminiz ve günlükleriniz Donut ekibine ulaştı.",
"reply": "Daha fazla ayrıntıya ihtiyacımız olursa {{email}} adresine yazarız.",
"done": "Tamam"
}
}
}
+69 -4
View File
@@ -2419,7 +2419,14 @@
"wayfernLaunchArgsLimit": "Dùng tối đa {{count}} tham số khởi chạy, mỗi tham số tối đa {{length}} ký tự.",
"tablePreferencesInvalid": "Cài đặt bảng không hợp lệ.",
"tablePreferencesLoadFailed": "Không thể tải cài đặt bảng. Hãy thử lại.",
"tablePreferencesSaveFailed": "Không thể lưu cài đặt bảng. Hãy thử lại."
"tablePreferencesSaveFailed": "Không thể lưu cài đặt bảng. Hãy thử lại.",
"feedbackMessageEmpty": "Hãy viết tin nhắn trước.",
"feedbackMessageTooLong": "Tin nhắn quá dài. Giới hạn là {{max}} ký tự.",
"feedbackEmailInvalid": "Địa chỉ email này có vẻ không đúng.",
"feedbackInvalid": "Phản hồi không được chấp nhận. Hãy kiểm tra tin nhắn và thử lại.",
"feedbackRateLimited": "Bạn đã gửi nhiều phản hồi trong thời gian ngắn. Hãy thử lại sau vài phút.",
"feedbackSendFailed": "Không thể gửi phản hồi. Hãy thử lại sau.",
"feedbackLogsUnreadable": "Không thể đọc nhật ký. Hãy tắt \"Đính kèm nhật ký gần đây\" rồi gửi lại."
},
"rail": {
"profiles": "Profile",
@@ -2438,7 +2445,9 @@
"trash": "Thùng rác",
"trashHint": "Khôi phục hồ sơ đã xóa",
"tips": "Mẹo",
"tipsHint": "Hướng dẫn tính năng"
"tipsHint": "Hướng dẫn tính năng",
"feedback": "Gửi phản hồi",
"feedbackHint": "Báo lỗi hoặc chia sẻ ý tưởng"
},
"network": "Mạng",
"integrations": "Tích hợp",
@@ -2557,7 +2566,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "Đi tới Thùng rác",
"goAgent": "Đến Tác nhân",
"openTips": "Mở mẹo"
"openTips": "Mở mẹo",
"sendFeedback": "Gửi phản hồi"
},
"closeConfirm": {
"title": "Đóng Donut Browser?",
@@ -3290,7 +3300,9 @@
"noteSeenAt": "Đã xem lúc {{time}}",
"noteSeen": "Đã xem",
"joined": "{{agent}} đã tham gia",
"left": "{{agent}} đã rời đi"
"left": "{{agent}} đã rời đi",
"feedbackSent": "đã gửi phản hồi cho đội ngũ Donut",
"feedbackLogs": "Đã đính kèm nhật ký"
},
"composer": {
"label": "Ghi chú cho tác nhân",
@@ -3570,6 +3582,12 @@
"title": "Điều khiển máy này từ donutbrowser.com",
"body": "Khi bật điều khiển từ xa, các tác nhân trên trang web tiếp cận hồ sơ của máy này qua một cầu nối đi ra. Bật trong Tích hợp.",
"action": "Mở Tích hợp"
},
"feedback": {
"label": "Phản hồi",
"title": "Cho đội ngũ biết nên sửa gì tiếp theo",
"body": "Có gì bị hỏng hoặc còn thiếu? Gửi phản hồi từ menu Thêm. Chỉ một công tắc là đính kèm nhật ký, để đội ngũ thấy chính xác điều gì đã sai.",
"action": "Gửi phản hồi"
}
}
},
@@ -3613,5 +3631,52 @@
"noTags": "Không có thẻ",
"noProxy": "Không có proxy",
"encrypted": "Đã mã hóa"
},
"feedback": {
"title": "Gửi phản hồi",
"description": "Hãy cho đội ngũ Donut biết bạn nghĩ gì. Chúng tôi đọc mọi tin nhắn.",
"kindLabel": "Loại phản hồi",
"kinds": {
"bug": {
"label": "Lỗi",
"placeholder": "Chuyện gì đã xảy ra, và bạn mong đợi điều gì? Các bước tái hiện lỗi sẽ giúp ích rất nhiều."
},
"idea": {
"label": "Ý tưởng",
"placeholder": "Điều gì sẽ giúp Donut tốt hơn cho bạn?"
},
"praise": {
"label": "Lời khen",
"placeholder": "Bạn thích điều gì? Đội ngũ sẽ rất vui khi đọc được."
},
"other": {
"label": "Khác",
"placeholder": "Hãy cho chúng tôi biết bất cứ điều gì."
}
},
"messageLabel": "Tin nhắn",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "Đính kèm nhật ký gần đây",
"description": "Nhật ký giúp chúng tôi tìm lỗi. Địa chỉ email, địa chỉ IP, địa chỉ web và thông tin bí mật được xóa trước.",
"preview": "Xem nhật ký",
"loading": "Đang đọc nhật ký…",
"empty": "Chưa có nhật ký nào."
},
"email": {
"label": "Email để nhận phản hồi",
"optional": "Không bắt buộc",
"placeholder": "ban@example.com",
"signedIn": "Phản hồi sẽ được gửi tới {{email}}."
},
"included": "Cũng được gửi: Donut {{version}}, {{os}} {{arch}}",
"send": "Gửi",
"success": {
"title": "Cảm ơn bạn!",
"body": "Phản hồi của bạn đã đến đội ngũ Donut.",
"bodyWithLogs": "Phản hồi và nhật ký của bạn đã đến đội ngũ Donut.",
"reply": "Nếu cần thêm chi tiết, chúng tôi sẽ viết thư tới {{email}}.",
"done": "Xong"
}
}
}
+69 -4
View File
@@ -2419,7 +2419,14 @@
"wayfernLaunchArgsLimit": "最多使用 {{count}} 个启动参数,每个最多 {{length}} 个字符。",
"tablePreferencesInvalid": "这些表格设置无效。",
"tablePreferencesLoadFailed": "无法加载表格设置。请重试。",
"tablePreferencesSaveFailed": "无法保存表格设置。请重试。"
"tablePreferencesSaveFailed": "无法保存表格设置。请重试。",
"feedbackMessageEmpty": "请先写一条消息。",
"feedbackMessageTooLong": "消息太长。上限为 {{max}} 个字符。",
"feedbackEmailInvalid": "这个邮箱地址似乎不正确。",
"feedbackInvalid": "反馈未被接受。请检查消息后重试。",
"feedbackRateLimited": "你在短时间内发送了大量反馈。请几分钟后再试。",
"feedbackSendFailed": "无法发送反馈。请稍后再试。",
"feedbackLogsUnreadable": "无法读取日志。请关闭“附上最近的日志”后重新发送。"
},
"rail": {
"profiles": "配置文件",
@@ -2438,7 +2445,9 @@
"trash": "回收站",
"trashHint": "恢复已删除的配置文件",
"tips": "小贴士",
"tipsHint": "功能演示"
"tipsHint": "功能演示",
"feedback": "发送反馈",
"feedbackHint": "报告问题或分享想法"
},
"network": "网络",
"integrations": "集成",
@@ -2557,7 +2566,8 @@
"goCookieBot": "Cookie Bot",
"goTrash": "前往回收站",
"goAgent": "前往智能体",
"openTips": "打开小贴士"
"openTips": "打开小贴士",
"sendFeedback": "发送反馈"
},
"closeConfirm": {
"title": "关闭 Donut Browser?",
@@ -3290,7 +3300,9 @@
"noteSeenAt": "已于 {{time}} 查看",
"noteSeen": "已查看",
"joined": "{{agent}} 已加入",
"left": "{{agent}} 已离开"
"left": "{{agent}} 已离开",
"feedbackSent": "向 Donut 团队发送了反馈",
"feedbackLogs": "已附日志"
},
"composer": {
"label": "给智能体的备注",
@@ -3570,6 +3582,12 @@
"title": "从 donutbrowser.com 控制这台电脑",
"body": "开启远程控制后,网站上的智能体可通过出站桥接访问这台电脑的配置文件。在“集成”中开启。",
"action": "打开集成"
},
"feedback": {
"label": "反馈",
"title": "告诉团队下一步该修什么",
"body": "有东西坏了或缺少功能?从“更多”菜单发送反馈。只需一个开关即可附上日志,让团队准确看到出了什么问题。",
"action": "发送反馈"
}
}
},
@@ -3613,5 +3631,52 @@
"noTags": "无标签",
"noProxy": "无代理",
"encrypted": "已加密"
},
"feedback": {
"title": "发送反馈",
"description": "告诉 Donut 团队你的想法。我们会阅读每一条消息。",
"kindLabel": "反馈类型",
"kinds": {
"bug": {
"label": "问题",
"placeholder": "发生了什么?你原本期望的结果是什么?提供复现步骤会很有帮助。"
},
"idea": {
"label": "建议",
"placeholder": "怎样能让 Donut 更好用?"
},
"praise": {
"label": "称赞",
"placeholder": "你喜欢哪些地方?团队会很高兴听到。"
},
"other": {
"label": "其他",
"placeholder": "想说什么都可以。"
}
},
"messageLabel": "消息",
"counter": "{{count}} / {{max}}",
"logs": {
"label": "附上最近的日志",
"description": "日志可以帮助我们找到问题。发送前会移除电子邮件地址、IP 地址、网址和密钥。",
"preview": "查看日志",
"loading": "正在读取日志…",
"empty": "暂无日志。"
},
"email": {
"label": "用于回复的邮箱",
"optional": "可选",
"placeholder": "you@example.com",
"signedIn": "回复将发送到 {{email}}。"
},
"included": "同时发送:Donut {{version}},{{os}} {{arch}}",
"send": "发送",
"success": {
"title": "谢谢!",
"body": "你的反馈已送达 Donut 团队。",
"bodyWithLogs": "你的反馈和日志已送达 Donut 团队。",
"reply": "如需更多细节,我们会写信到 {{email}}。",
"done": "完成"
}
}
}
+6 -1
View File
@@ -45,7 +45,8 @@ export type AgentThreadKind =
| "note"
| "progress"
| "joined"
| "left";
| "left"
| "feedback";
export type AgentThreadState =
| "open"
@@ -70,8 +71,12 @@ export interface AgentThreadItem {
delivered_to: string[];
done: number | null;
total: number | null;
/** Set on a `feedback` item: what the agent sent to the Donut team. */
feedback: { kind: FeedbackKind; logs: boolean } | null;
}
export type FeedbackKind = "bug" | "idea" | "praise" | "other";
export interface AgentHold {
profile_id: string;
since: number;
+23
View File
@@ -199,6 +199,13 @@ export type BackendErrorCode =
| "AGENT_NOTE_EMPTY"
| "AGENT_TEXT_TOO_LONG"
| "AGENT_SESSION_NOT_FOUND"
| "FEEDBACK_MESSAGE_EMPTY"
| "FEEDBACK_MESSAGE_TOO_LONG"
| "FEEDBACK_EMAIL_INVALID"
| "FEEDBACK_INVALID"
| "FEEDBACK_RATE_LIMITED"
| "FEEDBACK_SEND_FAILED"
| "FEEDBACK_LOGS_UNREADABLE"
| "FINGERPRINT_EXIT_MISMATCH"
// The launch refuses instead of opening a window on an unmanaged device: a
// silent fallback would leave the user browsing a random fingerprint while
@@ -746,6 +753,22 @@ export function translateBackendError(t: TFunction, err: unknown): string {
});
case "AGENT_SESSION_NOT_FOUND":
return t("backendErrors.agentSessionNotFound");
case "FEEDBACK_MESSAGE_EMPTY":
return t("backendErrors.feedbackMessageEmpty");
case "FEEDBACK_MESSAGE_TOO_LONG":
return t("backendErrors.feedbackMessageTooLong", {
max: parsed.params?.max ?? "5000",
});
case "FEEDBACK_EMAIL_INVALID":
return t("backendErrors.feedbackEmailInvalid");
case "FEEDBACK_INVALID":
return t("backendErrors.feedbackInvalid");
case "FEEDBACK_RATE_LIMITED":
return t("backendErrors.feedbackRateLimited");
case "FEEDBACK_SEND_FAILED":
return t("backendErrors.feedbackSendFailed");
case "FEEDBACK_LOGS_UNREADABLE":
return t("backendErrors.feedbackLogsUnreadable");
// The launch gate's block. The dialog renders the mismatch detail from
// `params` itself; this string is the fallback for anywhere that only has
// room for one sentence.
+10
View File
@@ -32,6 +32,7 @@ export type ShortcutId =
| "openPalette"
| "openShortcuts"
| "openTips"
| "sendFeedback"
| "importProfile"
| "goProfiles"
| "goProxies"
@@ -69,6 +70,15 @@ export const SHORTCUTS: ShortcutDef[] = [
mod: true,
shift: true,
},
{
// Mod+Shift+F, "feedback". Plain Mod+F stays free for find.
id: "sendFeedback",
labelKey: "shortcuts.sendFeedback",
group: "actions",
key: "f",
mod: true,
shift: true,
},
{
id: "importProfile",
labelKey: "shortcuts.importProfile",
+1
View File
@@ -192,6 +192,7 @@ test("a tip for a feature already in use is skipped", () => {
"defaultBrowser",
]);
assert.deepEqual(skipped({ ...IDLE, trashUsed: true }), ["trash"]);
assert.deepEqual(skipped({ ...IDLE, feedbackSent: true }), ["feedback"]);
assert.deepEqual(skipped({ ...IDLE, apiEnabled: true }), [
"localApi",
"automation",
+10 -1
View File
@@ -20,7 +20,8 @@ export type TipRequirement = Extract<
export type TipAction =
| { kind: "page"; page: AppPage }
| { kind: "settings"; section: string }
| { kind: "palette" };
| { kind: "palette" }
| { kind: "feedback" };
export type TipId =
| "dnsBlocklist"
@@ -36,6 +37,7 @@ export type TipId =
| "trash"
| "localApi"
| "importProfiles"
| "feedback"
| "cloudBackup"
| "cookieBot"
| "crossOs"
@@ -80,6 +82,8 @@ export interface FeatureUsage {
trashUsed?: boolean;
/** An agent has connected over remote control during this app session. */
agentUsed?: boolean;
/** The person has sent feedback from the app. */
feedbackSent?: boolean;
}
export interface TipDefinition {
@@ -181,6 +185,11 @@ export const TIPS: readonly TipDefinition[] = [
inUse: (usage) => usage.apiEnabled === true,
},
{ id: "importProfiles", action: { kind: "page", page: "import" } },
{
id: "feedback",
action: { kind: "feedback" },
inUse: (usage) => usage.feedbackSent === true,
},
{
id: "cloudBackup",
action: { kind: "page", page: "account" },