From a68e5a1c17b675ba54e06c09ae76ae555a2aeea3 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Tue, 20 Jan 2026 07:16:02 -0600
Subject: wip(app): i18n
---
packages/app/src/pages/home.tsx | 24 ++--
packages/app/src/pages/layout.tsx | 218 ++++++++++++++++++++-----------------
packages/app/src/pages/session.tsx | 88 ++++++++-------
3 files changed, 182 insertions(+), 148 deletions(-)
(limited to 'packages/app/src/pages')
diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx
index a9e89a7f5..efed2e658 100644
--- a/packages/app/src/pages/home.tsx
+++ b/packages/app/src/pages/home.tsx
@@ -12,6 +12,7 @@ import { DialogSelectDirectory } from "@/components/dialog-select-directory"
import { DialogSelectServer } from "@/components/dialog-select-server"
import { useServer } from "@/context/server"
import { useGlobalSync } from "@/context/global-sync"
+import { useLanguage } from "@/context/language"
export default function Home() {
const sync = useGlobalSync()
@@ -20,6 +21,7 @@ export default function Home() {
const dialog = useDialog()
const navigate = useNavigate()
const server = useServer()
+ const language = useLanguage()
const homedir = createMemo(() => sync.data.path.home)
function openProject(directory: string) {
@@ -41,7 +43,7 @@ export default function Home() {
if (platform.openDirectoryPickerDialog && server.isLocal()) {
const result = await platform.openDirectoryPickerDialog?.({
- title: "Open project",
+ title: language.t("command.project.open"),
multiple: true,
})
resolve(result)
@@ -74,13 +76,13 @@ export default function Home() {
0}>
-
-
-
Recent projects
-
-
+
+
+
{language.t("home.recentProjects")}
+
+
-
No recent projects
-
Get started by opening a local project
+
{language.t("home.empty.title")}
+
{language.t("home.empty.description")}
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index f5910b88b..2cbc97a55 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -300,18 +300,18 @@ export default function Layout(props: ParentProps) {
toastId = showToast({
persistent: true,
icon: "download",
- title: "Update available",
- description: `A new version of OpenCode (${version}) is now available to install.`,
+ title: language.t("toast.update.title"),
+ description: language.t("toast.update.description", { version: version ?? "" }),
actions: [
{
- label: "Install and restart",
+ label: language.t("toast.update.action.installRestart"),
onClick: async () => {
await platform.update!()
await platform.restart!()
},
},
{
- label: "Not yet",
+ label: language.t("toast.update.action.notYet"),
onClick: "dismiss",
},
],
@@ -325,27 +325,17 @@ export default function Layout(props: ParentProps) {
})
onMount(() => {
- const alerts = {
- "permission.asked": {
- title: "Permission required",
- icon: "checklist" as const,
- description: (sessionTitle: string, projectName: string) =>
- `${sessionTitle} in ${projectName} needs permission`,
- },
- "question.asked": {
- title: "Question",
- icon: "bubble-5" as const,
- description: (sessionTitle: string, projectName: string) => `${sessionTitle} in ${projectName} has a question`,
- },
- }
-
const toastBySession = new Map
()
const alertedAtBySession = new Map()
const cooldownMs = 5000
const unsub = globalSDK.event.listen((e) => {
if (e.details?.type !== "permission.asked" && e.details?.type !== "question.asked") return
- const config = alerts[e.details.type]
+ const title =
+ e.details.type === "permission.asked"
+ ? language.t("notification.permission.title")
+ : language.t("notification.question.title")
+ const icon = e.details.type === "permission.asked" ? ("checklist" as const) : ("bubble-5" as const)
const directory = e.name
const props = e.details.properties
if (e.details.type === "permission.asked" && permission.autoResponds(e.details.properties, directory)) return
@@ -354,9 +344,12 @@ export default function Layout(props: ParentProps) {
const session = store.session.find((s) => s.id === props.sessionID)
const sessionKey = `${directory}:${props.sessionID}`
- const sessionTitle = session?.title ?? "New session"
+ const sessionTitle = session?.title ?? language.t("command.session.new")
const projectName = getFilename(directory)
- const description = config.description(sessionTitle, projectName)
+ const description =
+ e.details.type === "permission.asked"
+ ? language.t("notification.permission.description", { sessionTitle, projectName })
+ : language.t("notification.question.description", { sessionTitle, projectName })
const href = `/${base64Encode(directory)}/session/${props.sessionID}`
const now = Date.now()
@@ -367,13 +360,13 @@ export default function Layout(props: ParentProps) {
if (e.details.type === "permission.asked") {
playSound(soundSrc(settings.sounds.permissions()))
if (settings.notifications.permissions()) {
- void platform.notify(config.title, description, href)
+ void platform.notify(title, description, href)
}
}
if (e.details.type === "question.asked") {
if (settings.notifications.agent()) {
- void platform.notify(config.title, description, href)
+ void platform.notify(title, description, href)
}
}
@@ -387,16 +380,16 @@ export default function Layout(props: ParentProps) {
const toastId = showToast({
persistent: true,
- icon: config.icon,
- title: config.title,
+ icon,
+ title,
description,
actions: [
{
- label: "Go to session",
+ label: language.t("notification.action.goToSession"),
onClick: () => navigate(href),
},
{
- label: "Dismiss",
+ label: language.t("common.dismiss"),
onClick: "dismiss",
},
],
@@ -1024,7 +1017,7 @@ export default function Layout(props: ParentProps) {
if (platform.openDirectoryPickerDialog && server.isLocal()) {
const result = await platform.openDirectoryPickerDialog?.({
- title: "Open project",
+ title: language.t("command.project.open"),
multiple: true,
})
resolve(result)
@@ -1042,7 +1035,7 @@ export default function Layout(props: ParentProps) {
if (data?.message) return data.message
}
if (err instanceof Error) return err.message
- return "Request failed"
+ return language.t("common.requestFailed")
}
const deleteWorkspace = async (directory: string) => {
@@ -1057,7 +1050,7 @@ export default function Layout(props: ParentProps) {
.then((x) => x.data)
.catch((err) => {
showToast({
- title: "Failed to delete workspace",
+ title: language.t("workspace.delete.failed.title"),
description: errorMessage(err),
})
return false
@@ -1079,9 +1072,15 @@ export default function Layout(props: ParentProps) {
const current = currentProject()
if (!current) return
if (directory === current.worktree) return
-
setBusy(directory, true)
+ const progress = showToast({
+ persistent: true,
+ title: language.t("workspace.resetting.title"),
+ description: language.t("workspace.resetting.description"),
+ })
+ const dismiss = () => toaster.dismiss(progress)
+
const sessions = await globalSDK.client.session
.list({ directory })
.then((x) => x.data ?? [])
@@ -1092,7 +1091,7 @@ export default function Layout(props: ParentProps) {
.then((x) => x.data)
.catch((err) => {
showToast({
- title: "Failed to reset workspace",
+ title: language.t("workspace.reset.failed.title"),
description: errorMessage(err),
})
return false
@@ -1100,6 +1099,7 @@ export default function Layout(props: ParentProps) {
if (!result) {
setBusy(directory, false)
+ dismiss()
return
}
@@ -1121,14 +1121,15 @@ export default function Layout(props: ParentProps) {
await globalSDK.client.instance.dispose({ directory }).catch(() => undefined)
setBusy(directory, false)
+ dismiss()
const href = `/${base64Encode(directory)}/session`
navigate(href)
layout.mobileSidebar.hide()
showToast({
- title: "Workspace reset",
- description: "Workspace now matches the default branch.",
+ title: language.t("workspace.reset.success.title"),
+ description: language.t("workspace.reset.success.description"),
})
}
@@ -1164,25 +1165,27 @@ export default function Layout(props: ParentProps) {
}
const description = () => {
- if (data.status === "loading") return "Checking for unmerged changes..."
- if (data.status === "error") return "Unable to verify git status."
- if (!data.dirty) return "No unmerged changes detected."
- return "Unmerged changes detected in this workspace."
+ if (data.status === "loading") return language.t("workspace.status.checking")
+ if (data.status === "error") return language.t("workspace.status.error")
+ if (!data.dirty) return language.t("workspace.status.clean")
+ return language.t("workspace.status.dirty")
}
return (
- }>
+ {language.t("session.messages.loading")}}
+ >
@@ -1604,7 +1612,8 @@ export default function Layout(props: ParentProps) {
if (!directory) return
const [workspaceStore] = globalSync.child(directory)
- const kind = directory === project.worktree ? "local" : "sandbox"
+ const kind =
+ directory === project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
const name = workspaceLabel(directory, workspaceStore.vcs?.branch, project.id)
return `${kind} : ${name}`
})
@@ -1671,7 +1680,9 @@ export default function Layout(props: ParentProps) {
- {local() ? "local" : "sandbox"} :
+
+ {local() ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")} :
+
-
+
@@ -1750,7 +1761,7 @@ export default function Layout(props: ParentProps) {
}}
>
navigate(`/${slug()}/session`)}>
- New session
+ {language.t("command.session.new")}
- Rename
+ {language.t("common.rename")}
dialog.show(() => )}
>
- Reset
+ {language.t("common.reset")}
dialog.show(() => )}
>
- Delete
+ {language.t("common.delete")}
-
+
- New session
+ {language.t("command.session.new")}
@@ -1818,7 +1833,7 @@ export default function Layout(props: ParentProps) {
;(e.currentTarget as HTMLButtonElement).blur()
}}
>
- Load more
+ {language.t("common.loadMore")}
@@ -1842,7 +1857,8 @@ export default function Layout(props: ParentProps) {
const label = (directory: string) => {
const [data] = globalSync.child(directory)
- const kind = directory === props.project.worktree ? "local" : "sandbox"
+ const kind =
+ directory === props.project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox")
const name = workspaceLabel(directory, data.vcs?.branch, props.project.id)
return `${kind} : ${name}`
}
@@ -1893,9 +1909,9 @@ export default function Layout(props: ParentProps) {
trigger={trigger}
onOpenChange={setOpen}
>
-
-
{displayName(props.project)}
-
Recent sessions
+
+
{displayName(props.project)}
+
{language.t("sidebar.project.recentSessions")}
- View all sessions
+ {language.t("sidebar.project.viewAllSessions")}
@@ -2002,7 +2018,7 @@ export default function Layout(props: ParentProps) {
;(e.currentTarget as HTMLButtonElement).blur()
}}
>
- Load more
+ {language.t("common.loadMore")}
@@ -2033,7 +2049,7 @@ export default function Layout(props: ParentProps) {
.then((x) => x.data)
.catch((err) => {
showToast({
- title: "Failed to create workspace",
+ title: language.t("workspace.create.failed.title"),
description: errorMessage(err),
})
return undefined
@@ -2080,7 +2096,7 @@ export default function Layout(props: ParentProps) {
placement={sidebarProps.mobile ? "bottom" : "right"}
value={
-
Open project
+
{language.t("command.project.open")}
{command.keybind("project.open")}
@@ -2098,12 +2114,12 @@ export default function Layout(props: ParentProps) {
-
+
-
- dialog.show(() => )}>
- Edit
-
- layout.sidebar.toggleWorkspaces(p.worktree)}>
-
- {layout.sidebar.workspaces(p.worktree)() ? "Disable workspaces" : "Enable workspaces"}
-
-
-
- closeProject(p.worktree)}>
- Close
-
-
+
+ dialog.show(() => )}>
+ {language.t("common.edit")}
+
+ layout.sidebar.toggleWorkspaces(p.worktree)}>
+
+ {layout.sidebar.workspaces(p.worktree)()
+ ? language.t("sidebar.workspaces.disable")
+ : language.t("sidebar.workspaces.enable")}
+
+
+
+ closeProject(p.worktree)}>
+ {language.t("common.close")}
+
+
@@ -2185,7 +2203,11 @@ export default function Layout(props: ParentProps) {
fallback={
<>
-
+
@@ -2208,12 +2230,12 @@ export default function Layout(props: ParentProps) {
<>
@@ -2255,9 +2277,9 @@ export default function Layout(props: ParentProps) {
-
Getting started
-
OpenCode includes free models so you can start immediately.
-
Connect any provider to use models, inc. Claude, GPT, Gemini etc.
+
{language.t("sidebar.gettingStarted.title")}
+
{language.t("sidebar.gettingStarted.line1")}
+
{language.t("sidebar.gettingStarted.line2")}
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 8803b063e..f98b02e7e 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -1205,7 +1205,7 @@ export default function Page() {
classes={{ button: "w-full" }}
onClick={() => setStore("mobileTab", "session")}
>
- Session
+ {language.t("session.tab.session")}
setStore("mobileTab", "review")}
>
- {reviewCount()} Files Changed
- Review
+
+ {language.t("session.review.filesChanged", { count: reviewCount() })}
+
+ {language.t("session.tab.review")}
@@ -1245,7 +1247,9 @@ export default function Page() {
Loading changes... }
+ fallback={
+ {language.t("session.review.loadingChanges")}
+ }
>
-
-
-
No changes in this session yet
-
-
-
-
+
+
+
{language.t("session.review.empty")}
+
+
+
+
}
>
@@ -1334,7 +1338,7 @@ export default function Page() {
class="text-12-medium opacity-50"
onClick={() => setStore("turnStart", 0)}
>
- Render earlier messages
+ {language.t("session.messages.renderEarlier")}
@@ -1352,7 +1356,9 @@ export default function Page() {
sync.session.history.loadMore(id)
}}
>
- {historyLoading() ? "Loading earlier messages..." : "Load earlier messages"}
+ {historyLoading()
+ ? language.t("session.messages.loadingEarlier")
+ : language.t("session.messages.loadEarlier")}
@@ -1436,7 +1442,7 @@ export default function Page() {
when={prompt.ready()}
fallback={
- {handoff.prompt || "Loading prompt..."}
+ {handoff.prompt || language.t("prompt.loading")}
}
>
@@ -1482,11 +1488,11 @@ export default function Page() {
-
-
Review
-
-
- {info()?.summary?.files ?? 0}
+
+
{language.t("session.tab.review")}
+
+
+ {info()?.summary?.files ?? 0}
@@ -1497,7 +1503,7 @@ export default function Page() {
+
tabs().close("context")} />
}
@@ -1506,7 +1512,7 @@ export default function Page() {
>
-
Context
+
{language.t("session.tab.context")}
@@ -1515,7 +1521,7 @@ export default function Page() {
@@ -1537,7 +1543,9 @@ export default function Page() {
Loading changes...
}
+ fallback={
+
{language.t("session.review.loadingChanges")}
+ }
>
-
-
-
No changes in this session yet
-
-
-
-
+
+
+
{language.t("session.review.empty")}
+
+
+
+
@@ -1735,7 +1743,9 @@ export default function Page() {
}}
>
- Add {selectionLabel()} to context
+
+ {language.t("session.context.addToContext", { selection: selectionLabel() ?? "" })}
+
)}
@@ -1792,7 +1802,7 @@ export default function Page() {
/>
- Loading...
+ {language.t("common.loading")}...
{(err) => {err()}
}
@@ -1847,13 +1857,13 @@ export default function Page() {
)}
-
- Loading...
-
- Loading terminal...
+
+ {language.t("common.loading")}...
- }
- >
+ {language.t("terminal.loading")}
+
+ }
+ >
--
cgit v1.2.3