Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions apps/app/src/app/components/mcp-auth-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
statusPoll = window.setInterval(async () => {
if (Date.now() - startedAt >= MCP_AUTH_TIMEOUT_MS) {
stopStatusPolling();
setError("Request timed out.");
setError(translate("mcp.auth.request_timed_out"));
return;
}

Expand Down Expand Up @@ -694,7 +694,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
<CheckCircle2 size={24} class="text-green-11" />
</div>
<div>
<p class="text-sm font-medium text-gray-12">Already Connected</p>
<p class="text-sm font-medium text-gray-12">{translate("mcp.auth.already_connected")}</p>
<p class="text-xs text-gray-11">
{translate("mcp.auth.already_connected_description", { server: serverName() })}
</p>
Expand Down Expand Up @@ -804,7 +804,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
</div>
<div class="rounded-xl border border-gray-6/70 bg-gray-2/40 px-3 py-2 flex items-center gap-3">
<div class="flex-1 min-w-0">
<div class="text-[10px] uppercase tracking-wide text-gray-8">Authorization link</div>
<div class="text-[10px] uppercase tracking-wide text-gray-8">{translate("mcp.auth.authorization_link")}</div>
<div class="text-[11px] text-gray-11 font-mono truncate">
{authorizationUrl()}
</div>
Expand All @@ -814,7 +814,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
class="text-xs"
onClick={handleCopyAuthorizationUrl}
>
{authUrlCopied() ? "Copied" : "Copy link"}
{authUrlCopied() ? translate("mcp.auth.copied") : translate("mcp.auth.copy_link")}
</Button>
</div>
<TextInput
Expand Down Expand Up @@ -851,7 +851,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
1
</div>
<div>
<p class="text-sm font-medium text-gray-12">Opening your browser</p>
<p class="text-sm font-medium text-gray-12">{translate("mcp.auth.step1_title")}</p>
<p class="text-xs text-gray-10 mt-1">
{translate("mcp.auth.step1_description", { server: serverName() })}
</p>
Expand All @@ -863,7 +863,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
2
</div>
<div>
<p class="text-sm font-medium text-gray-12">Authorize OpenWork</p>
<p class="text-sm font-medium text-gray-12">{translate("mcp.auth.step2_title")}</p>
<p class="text-xs text-gray-10 mt-1">
{translate("mcp.auth.step2_description")}
</p>
Expand All @@ -875,7 +875,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
3
</div>
<div>
<p class="text-sm font-medium text-gray-12">Return here when you're done</p>
<p class="text-sm font-medium text-gray-12">{translate("mcp.auth.step3_title")}</p>
<p class="text-xs text-gray-10 mt-1">
{translate("mcp.auth.step3_description")}
</p>
Expand Down
15 changes: 8 additions & 7 deletions apps/app/src/app/components/question-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { QuestionInfo } from "@opencode-ai/sdk/v2/client";
import { Check, ChevronRight, HelpCircle } from "lucide-solid";

import Button from "./button";
import { t } from "../../i18n";

export type QuestionModalProps = {
open: boolean;
Expand Down Expand Up @@ -138,10 +139,10 @@ export default function QuestionModal(props: QuestionModalProps) {
</div>
<div>
<h3 class="text-lg font-semibold text-gray-12">
{currentQuestion()!.header || "Question"}
{currentQuestion()!.header || t("common.question")}
</h3>
<div class="text-xs text-gray-11 font-medium">
Question {currentIndex() + 1} of {props.questions.length}
{t("question_modal.question_counter", undefined, { current: currentIndex() + 1, total: props.questions.length })}
</div>
</div>
</div>
Expand Down Expand Up @@ -186,14 +187,14 @@ export default function QuestionModal(props: QuestionModalProps) {
<Show when={currentQuestion()!.custom}>
<div class="mt-4 pt-4 border-t border-dls-border">
<label class="block text-xs font-semibold text-dls-secondary mb-2 uppercase tracking-wide">
Or type a custom answer
{t("question_modal.custom_answer_label")}
</label>
<input
type="text"
value={customInput()}
onInput={(e) => setCustomInput(e.currentTarget.value)}
class="w-full px-4 py-3 rounded-xl bg-dls-surface border border-dls-border focus:border-dls-accent focus:ring-4 focus:ring-[rgba(var(--dls-accent-rgb),0.2)] focus:outline-none text-sm text-dls-text placeholder:text-dls-secondary transition-shadow"
placeholder="Type your answer here..."
placeholder={t("question_modal.custom_answer_placeholder")}
onKeyDown={(e) => {
if (e.key === "Enter") {
if (e.isComposing || e.keyCode === 229) return;
Expand All @@ -209,15 +210,15 @@ export default function QuestionModal(props: QuestionModalProps) {
<div class="p-6 border-t border-dls-border bg-dls-hover flex justify-between items-center">
<div class="text-xs text-dls-secondary flex items-center gap-2">
<span class="px-1.5 py-0.5 rounded border border-dls-border bg-dls-active font-mono">↑↓</span>
<span>navigate</span>
<span>{t("common.navigate")}</span>
<span class="px-1.5 py-0.5 rounded border border-gray-6 bg-gray-3 font-mono ml-2">↵</span>
<span>select</span>
<span>{t("common.select")}</span>
</div>

<div class="flex gap-2">
<Show when={currentQuestion()?.multiple || currentQuestion()?.custom}>
<Button onClick={handleNext} disabled={!canProceed() || props.busy} class="!px-6">
{isLastQuestion() ? "Submit" : "Next"}
{isLastQuestion() ? t("common.submit") : t("common.next")}
<Show when={!isLastQuestion()}>
<ChevronRight size={16} class="ml-1 -mr-1 opacity-60" />
</Show>
Expand Down
21 changes: 11 additions & 10 deletions apps/app/src/app/context/automations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { schedulerDeleteJob, schedulerListJobs } from "../lib/tauri";
import { isTauriRuntime } from "../utils";
import { createWorkspaceContextKey } from "./workspace-context";
import type { OpenworkServerStore } from "../connections/openwork-server-store";
import { t } from "../../i18n";

export type AutomationsStore = ReturnType<typeof createAutomationsStore>;

Expand Down Expand Up @@ -33,10 +34,10 @@ const buildCreateAutomationPrompt = (
const schedule = input.schedule.trim();
const prompt = normalizeSentence(input.prompt);
if (!schedule) {
return { ok: false, error: "Schedule is required." };
return { ok: false, error: t("automations.schedule_required") };
}
if (!prompt) {
return { ok: false, error: "Prompt is required." };
return { ok: false, error: t("automations.prompt_required") };
}
const workdir = (input.workdir ?? "").trim();
const nameSegment = name ? ` named \"${name}\"` : "";
Expand All @@ -58,7 +59,7 @@ const buildRunAutomationPrompt = (
if (job.run?.prompt || job.prompt) {
const promptBody = (job.run?.prompt ?? job.prompt ?? "").trim();
if (!promptBody) {
return { ok: false, error: "Automation prompt is empty." };
return { ok: false, error: t("automations.prompt_empty") };
}
return {
ok: true,
Expand Down Expand Up @@ -136,10 +137,10 @@ export function createAutomationsStore(options: {
if (scheduledJobsContextKey() !== requestContextKey) return "skipped";
const status =
options.openworkServer.openworkServerStatus() === "disconnected"
? "OpenWork server unavailable. Connect to sync scheduled tasks."
? t("automations.server_unavailable")
: options.openworkServer.openworkServerStatus() === "limited"
? "OpenWork server needs a token to load scheduled tasks."
: "OpenWork server not ready.";
? t("automations.server_needs_token")
: t("automations.server_not_ready");
setScheduledJobsStatus(status);
return "unavailable";
}
Expand All @@ -155,7 +156,7 @@ export function createAutomationsStore(options: {
} catch (error) {
if (scheduledJobsContextKey() !== requestContextKey) return "skipped";
const message = error instanceof Error ? error.message : String(error);
setScheduledJobsStatus(message || "Failed to load scheduled tasks.");
setScheduledJobsStatus(message || t("automations.failed_to_load"));
return "error";
} finally {
setScheduledJobsBusy(false);
Expand All @@ -180,7 +181,7 @@ export function createAutomationsStore(options: {
} catch (error) {
if (scheduledJobsContextKey() !== requestContextKey) return "skipped";
const message = error instanceof Error ? error.message : String(error);
setScheduledJobsStatus(message || "Failed to load scheduled tasks.");
setScheduledJobsStatus(message || t("automations.failed_to_load"));
return "error";
} finally {
setScheduledJobsBusy(false);
Expand All @@ -192,15 +193,15 @@ export function createAutomationsStore(options: {
const client = options.openworkServer.openworkServerClient();
const workspaceId = (options.runtimeWorkspaceId() ?? "").trim();
if (!client || !workspaceId) {
throw new Error("OpenWork server unavailable. Connect to sync scheduled tasks.");
throw new Error(t("automations.server_unavailable"));
}
const response = await client.deleteScheduledJob(workspaceId, name);
setScheduledJobs((current) => current.filter((entry) => entry.slug !== response.job.slug));
return;
}

if (!isTauriRuntime()) {
throw new Error("Scheduled tasks require the desktop app.");
throw new Error(t("automations.desktop_required"));
}
const root = options.selectedWorkspaceRoot().trim();
const job = await schedulerDeleteJob(name, root || undefined);
Expand Down
Loading
Loading