Skip to content

Commit 3ff8063

Browse files
johnnyshieldsclaude
andcommitted
feat(i18n): extract en translations for context/lib/misc
Extract hardcoded English strings to i18n keys for: - automations context, providers store, shared-bundles - mcp-auth-modal, onboarding-workspace-selector, question-modal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d624597 commit 3ff8063

8 files changed

Lines changed: 909 additions & 55 deletions

File tree

apps/app/src/app/components/mcp-auth-modal.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
694694
<CheckCircle2 size={24} class="text-green-11" />
695695
</div>
696696
<div>
697-
<p class="text-sm font-medium text-gray-12">Already Connected</p>
697+
<p class="text-sm font-medium text-gray-12">{translate("mcp.auth.already_connected")}</p>
698698
<p class="text-xs text-gray-11">
699699
{translate("mcp.auth.already_connected_description", { server: serverName() })}
700700
</p>
@@ -804,7 +804,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
804804
</div>
805805
<div class="rounded-xl border border-gray-6/70 bg-gray-2/40 px-3 py-2 flex items-center gap-3">
806806
<div class="flex-1 min-w-0">
807-
<div class="text-[10px] uppercase tracking-wide text-gray-8">Authorization link</div>
807+
<div class="text-[10px] uppercase tracking-wide text-gray-8">{translate("mcp.auth.authorization_link")}</div>
808808
<div class="text-[11px] text-gray-11 font-mono truncate">
809809
{authorizationUrl()}
810810
</div>
@@ -814,7 +814,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
814814
class="text-xs"
815815
onClick={handleCopyAuthorizationUrl}
816816
>
817-
{authUrlCopied() ? "Copied" : "Copy link"}
817+
{authUrlCopied() ? translate("mcp.auth.copied") : translate("mcp.auth.copy_link")}
818818
</Button>
819819
</div>
820820
<TextInput
@@ -851,7 +851,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
851851
1
852852
</div>
853853
<div>
854-
<p class="text-sm font-medium text-gray-12">Opening your browser</p>
854+
<p class="text-sm font-medium text-gray-12">{translate("mcp.auth.step1_title")}</p>
855855
<p class="text-xs text-gray-10 mt-1">
856856
{translate("mcp.auth.step1_description", { server: serverName() })}
857857
</p>
@@ -863,7 +863,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
863863
2
864864
</div>
865865
<div>
866-
<p class="text-sm font-medium text-gray-12">Authorize OpenWork</p>
866+
<p class="text-sm font-medium text-gray-12">{translate("mcp.auth.step2_title")}</p>
867867
<p class="text-xs text-gray-10 mt-1">
868868
{translate("mcp.auth.step2_description")}
869869
</p>
@@ -875,7 +875,7 @@ export default function McpAuthModal(props: McpAuthModalProps) {
875875
3
876876
</div>
877877
<div>
878-
<p class="text-sm font-medium text-gray-12">Return here when you're done</p>
878+
<p class="text-sm font-medium text-gray-12">{translate("mcp.auth.step3_title")}</p>
879879
<p class="text-xs text-gray-10 mt-1">
880880
{translate("mcp.auth.step3_description")}
881881
</p>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { For, Show, createEffect, createSignal } from "solid-js";
2+
3+
import { CheckCircle2, FolderPlus, Loader2 } from "lucide-solid";
4+
5+
import Button from "./button";
6+
import { t } from "../../i18n";
7+
8+
export default function OnboardingWorkspaceSelector(props: {
9+
defaultPath: string;
10+
onConfirm: (preset: "automation" | "minimal", folder: string | null) => void;
11+
onPickFolder: () => Promise<string | null>;
12+
}) {
13+
const [preset, setPreset] = createSignal<"automation" | "minimal">("minimal");
14+
const [selectedFolder, setSelectedFolder] = createSignal(props.defaultPath);
15+
const [pickingFolder, setPickingFolder] = createSignal(false);
16+
17+
const options = () => [
18+
{
19+
id: "minimal" as const,
20+
name: t("onboarding.empty_worker"),
21+
desc: t("onboarding.empty_worker_desc"),
22+
},
23+
];
24+
25+
const canContinue = () => Boolean(selectedFolder().trim());
26+
27+
createEffect(() => {
28+
if (!selectedFolder().trim()) {
29+
setSelectedFolder(props.defaultPath);
30+
}
31+
});
32+
33+
const handlePickFolder = async () => {
34+
if (pickingFolder()) return;
35+
setPickingFolder(true);
36+
try {
37+
await new Promise((resolve) => requestAnimationFrame(() => resolve(null)));
38+
const next = await props.onPickFolder();
39+
if (next) {
40+
setSelectedFolder(next);
41+
}
42+
} finally {
43+
setPickingFolder(false);
44+
}
45+
};
46+
47+
return (
48+
<div class="bg-gray-2 border border-gray-6 rounded-2xl overflow-hidden flex flex-col max-h-[90vh]">
49+
<div class="p-6 flex-1 overflow-y-auto space-y-8">
50+
<div class="space-y-4">
51+
<div class="flex items-center gap-3 text-sm font-medium text-gray-12">
52+
<div class="w-6 h-6 rounded-full bg-gray-4 flex items-center justify-center text-xs">1</div>
53+
{t("dashboard.select_folder")}
54+
</div>
55+
<div class="ml-9">
56+
<div
57+
class={`w-full border border-dashed border-gray-6 bg-gray-1/40 rounded-xl p-4 text-left transition ${
58+
pickingFolder() ? "opacity-70" : "hover:border-dls-active"
59+
}`.trim()}
60+
>
61+
<div class="flex items-center gap-3 text-dls-text">
62+
<FolderPlus size={20} class="text-dls-secondary" />
63+
<input
64+
class="flex-1 min-w-0 bg-transparent text-sm font-medium text-dls-text placeholder:text-dls-secondary focus:outline-none"
65+
value={selectedFolder()}
66+
onInput={(e) => setSelectedFolder(e.currentTarget.value)}
67+
placeholder={props.defaultPath}
68+
/>
69+
<button
70+
type="button"
71+
onClick={handlePickFolder}
72+
disabled={pickingFolder()}
73+
class="text-xs text-dls-secondary hover:text-dls-text transition-colors"
74+
>
75+
<Show
76+
when={pickingFolder()}
77+
fallback={<span>{t("common.choose")}</span>}
78+
>
79+
<span class="inline-flex items-center gap-2">
80+
<Loader2 size={12} class="animate-spin" />
81+
{t("dashboard.opening")}
82+
</span>
83+
</Show>
84+
</button>
85+
</div>
86+
</div>
87+
</div>
88+
</div>
89+
90+
<div class="space-y-4">
91+
<div class="flex items-center gap-3 text-sm font-medium text-gray-12">
92+
<div class="w-6 h-6 rounded-full bg-gray-4 flex items-center justify-center text-xs">2</div>
93+
{t("dashboard.choose_preset")}
94+
</div>
95+
<div class={`ml-9 grid gap-3 ${!canContinue() ? "opacity-50" : ""}`.trim()}>
96+
<For each={options()}>
97+
{(opt) => (
98+
<div
99+
onClick={() => {
100+
if (!canContinue()) return;
101+
setPreset(opt.id);
102+
}}
103+
class={`p-4 rounded-xl border cursor-pointer transition-all ${
104+
preset() === opt.id
105+
? "bg-gray-4 border-gray-6 hover:border-gray-7"
106+
: "bg-gray-1/40 border-gray-6 hover:border-gray-7"
107+
} ${!canContinue() ? "pointer-events-none" : ""}`.trim()}
108+
>
109+
<div class="flex justify-between items-start">
110+
<div>
111+
<div
112+
class={`font-medium text-sm ${
113+
preset() === opt.id ? "text-indigo-400" : "text-zinc-200"
114+
}`}
115+
>
116+
{opt.name}
117+
</div>
118+
<div class="text-xs text-zinc-500 mt-1">{opt.desc}</div>
119+
</div>
120+
<Show when={preset() === opt.id}>
121+
<CheckCircle2 size={16} class="text-indigo-500" />
122+
</Show>
123+
</div>
124+
</div>
125+
)}
126+
</For>
127+
</div>
128+
</div>
129+
</div>
130+
131+
</div>
132+
);
133+
}

apps/app/src/app/components/question-modal.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { QuestionInfo } from "@opencode-ai/sdk/v2/client";
44
import { Check, ChevronRight, HelpCircle } from "lucide-solid";
55

66
import Button from "./button";
7+
import { t } from "../../i18n";
78

89
export type QuestionModalProps = {
910
open: boolean;
@@ -138,10 +139,10 @@ export default function QuestionModal(props: QuestionModalProps) {
138139
</div>
139140
<div>
140141
<h3 class="text-lg font-semibold text-gray-12">
141-
{currentQuestion()!.header || "Question"}
142+
{currentQuestion()!.header || t("common.question")}
142143
</h3>
143144
<div class="text-xs text-gray-11 font-medium">
144-
Question {currentIndex() + 1} of {props.questions.length}
145+
{t("question_modal.question_counter", undefined, { current: currentIndex() + 1, total: props.questions.length })}
145146
</div>
146147
</div>
147148
</div>
@@ -186,14 +187,14 @@ export default function QuestionModal(props: QuestionModalProps) {
186187
<Show when={currentQuestion()!.custom}>
187188
<div class="mt-4 pt-4 border-t border-dls-border">
188189
<label class="block text-xs font-semibold text-dls-secondary mb-2 uppercase tracking-wide">
189-
Or type a custom answer
190+
{t("question_modal.custom_answer_label")}
190191
</label>
191192
<input
192193
type="text"
193194
value={customInput()}
194195
onInput={(e) => setCustomInput(e.currentTarget.value)}
195196
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"
196-
placeholder="Type your answer here..."
197+
placeholder={t("question_modal.custom_answer_placeholder")}
197198
onKeyDown={(e) => {
198199
if (e.key === "Enter") {
199200
if (e.isComposing || e.keyCode === 229) return;
@@ -209,9 +210,9 @@ export default function QuestionModal(props: QuestionModalProps) {
209210
<div class="p-6 border-t border-dls-border bg-dls-hover flex justify-between items-center">
210211
<div class="text-xs text-dls-secondary flex items-center gap-2">
211212
<span class="px-1.5 py-0.5 rounded border border-dls-border bg-dls-active font-mono">↑↓</span>
212-
<span>navigate</span>
213+
<span>{t("common.navigate")}</span>
213214
<span class="px-1.5 py-0.5 rounded border border-gray-6 bg-gray-3 font-mono ml-2"></span>
214-
<span>select</span>
215+
<span>{t("common.select")}</span>
215216
</div>
216217

217218
<div class="flex gap-2">

apps/app/src/app/context/automations.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { schedulerDeleteJob, schedulerListJobs } from "../lib/tauri";
55
import { isTauriRuntime } from "../utils";
66
import { createWorkspaceContextKey } from "./workspace-context";
77
import type { OpenworkServerStore } from "../connections/openwork-server-store";
8+
import { t } from "../../i18n";
89

910
export type AutomationsStore = ReturnType<typeof createAutomationsStore>;
1011

@@ -136,10 +137,10 @@ export function createAutomationsStore(options: {
136137
if (scheduledJobsContextKey() !== requestContextKey) return "skipped";
137138
const status =
138139
options.openworkServer.openworkServerStatus() === "disconnected"
139-
? "OpenWork server unavailable. Connect to sync scheduled tasks."
140+
? t("automations.server_unavailable")
140141
: options.openworkServer.openworkServerStatus() === "limited"
141-
? "OpenWork server needs a token to load scheduled tasks."
142-
: "OpenWork server not ready.";
142+
? t("automations.server_needs_token")
143+
: t("automations.server_not_ready");
143144
setScheduledJobsStatus(status);
144145
return "unavailable";
145146
}
@@ -155,7 +156,7 @@ export function createAutomationsStore(options: {
155156
} catch (error) {
156157
if (scheduledJobsContextKey() !== requestContextKey) return "skipped";
157158
const message = error instanceof Error ? error.message : String(error);
158-
setScheduledJobsStatus(message || "Failed to load scheduled tasks.");
159+
setScheduledJobsStatus(message || t("automations.failed_to_load"));
159160
return "error";
160161
} finally {
161162
setScheduledJobsBusy(false);

0 commit comments

Comments
 (0)