@agent-native/core 0.114.11 → 0.114.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/application-state/index.ts +3 -0
  5. package/corpus/core/src/application-state/script-helpers.ts +12 -1
  6. package/corpus/core/src/application-state/store.ts +216 -15
  7. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +89 -1
  8. package/corpus/core/src/db/client.ts +44 -1
  9. package/corpus/templates/clips/changelog/2026-07-21-live-dictation-text-now-appears-above-the-desktop-recording-.md +6 -0
  10. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +14 -3
  11. package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +25 -0
  12. package/corpus/templates/clips/desktop/src/styles.css +20 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +3 -4
  14. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +11 -4
  15. package/corpus/templates/design/AGENTS.md +7 -3
  16. package/corpus/templates/design/actions/begin-node-rewrite-request.ts +78 -0
  17. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +17 -7
  18. package/corpus/templates/design/actions/propose-node-rewrite.ts +26 -18
  19. package/corpus/templates/design/actions/resolve-node-rewrite.ts +67 -19
  20. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +1 -4
  21. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +2 -6
  22. package/corpus/templates/design/shared/node-rewrite.ts +32 -0
  23. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +6 -3
  24. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -18
  25. package/corpus/templates/forms/app/pages/FormsListPage.tsx +9 -5
  26. package/corpus/templates/slides/app/context/DeckContext.tsx +18 -0
  27. package/corpus/templates/slides/changelog/2026-07-21-decks-list-now-refreshes-when-you-switch-organizations.md +6 -0
  28. package/corpus/templates/tasks/app/components/custom-fields/FieldCreateBar.tsx +13 -19
  29. package/corpus/templates/tasks/app/components/custom-fields/FieldListRow.tsx +27 -22
  30. package/corpus/templates/tasks/app/components/custom-fields/FieldsList.tsx +26 -22
  31. package/corpus/templates/tasks/app/components/custom-fields/FieldsListSkeleton.tsx +4 -1
  32. package/corpus/templates/tasks/app/components/custom-fields/FieldsPage.tsx +4 -2
  33. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldEditorSidebar.tsx +12 -3
  34. package/corpus/templates/tasks/app/components/custom-fields/editor/FieldTitleSection.tsx +6 -3
  35. package/corpus/templates/tasks/app/components/custom-fields/editor/config/CurrencyConfigControl.tsx +5 -2
  36. package/corpus/templates/tasks/app/components/custom-fields/editor/config/NumberConfigControl.tsx +5 -2
  37. package/corpus/templates/tasks/app/components/custom-fields/editor/config/PercentConfigControl.tsx +4 -1
  38. package/corpus/templates/tasks/app/components/custom-fields/editor/config/SelectConfigControl.tsx +17 -7
  39. package/corpus/templates/tasks/app/components/custom-fields/editor/config/select-colors.ts +37 -9
  40. package/corpus/templates/tasks/app/components/custom-fields/field-types.ts +23 -0
  41. package/corpus/templates/tasks/app/components/inbox/InboxList.tsx +24 -16
  42. package/corpus/templates/tasks/app/components/inbox/InboxListPage.tsx +5 -3
  43. package/corpus/templates/tasks/app/components/inbox/InboxListRow.tsx +7 -5
  44. package/corpus/templates/tasks/app/components/layout/Header.tsx +10 -10
  45. package/corpus/templates/tasks/app/components/layout/Layout.tsx +11 -7
  46. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +17 -10
  47. package/corpus/templates/tasks/app/components/shared/AddListItemInput.tsx +14 -8
  48. package/corpus/templates/tasks/app/components/shared/AlertDialog.tsx +6 -2
  49. package/corpus/templates/tasks/app/components/shared/BulkDeleteDialog.tsx +13 -6
  50. package/corpus/templates/tasks/app/components/shared/ChipSelect.tsx +12 -5
  51. package/corpus/templates/tasks/app/components/shared/DeleteItemDialog.tsx +6 -6
  52. package/corpus/templates/tasks/app/components/shared/ListViewHeader.tsx +3 -1
  53. package/corpus/templates/tasks/app/components/shared/SidePanel.tsx +5 -2
  54. package/corpus/templates/tasks/app/components/shared/list/ListRowDragHandle.tsx +3 -1
  55. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionBar.tsx +46 -20
  56. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionHeaderToggle.tsx +3 -1
  57. package/corpus/templates/tasks/app/components/shared/selection/ListSelectionToolbar.tsx +10 -6
  58. package/corpus/templates/tasks/app/components/tasks/TaskList.tsx +24 -13
  59. package/corpus/templates/tasks/app/components/tasks/TaskListHeaderRow.tsx +4 -1
  60. package/corpus/templates/tasks/app/components/tasks/TaskListPage.tsx +5 -3
  61. package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +15 -6
  62. package/corpus/templates/tasks/app/components/tasks/fields/TaskFieldsSidebar.tsx +11 -6
  63. package/corpus/templates/tasks/app/components/tasks/fields/TaskTitleSection.tsx +8 -3
  64. package/corpus/templates/tasks/app/components/tasks/fields/controls/RichTextValueControl.tsx +12 -6
  65. package/corpus/templates/tasks/app/components/tasks/fields/controls/SingleSelectValueControl.tsx +6 -1
  66. package/corpus/templates/tasks/app/i18n/en-US.ts +209 -0
  67. package/corpus/templates/tasks/app/i18n/index.ts +34 -0
  68. package/corpus/templates/tasks/app/root.tsx +34 -2
  69. package/corpus/templates/tasks/app/routes/extensions.$id.tsx +2 -1
  70. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -1
  71. package/corpus/templates/tasks/app/routes/fields.tsx +2 -1
  72. package/corpus/templates/tasks/app/routes/inbox.tsx +2 -1
  73. package/corpus/templates/tasks/app/routes/tasks.tsx +2 -1
  74. package/corpus/templates/tasks/app/routes/team.tsx +6 -3
  75. package/corpus/templates/tasks/changelog/2026-07-20-tasks-is-now-available-in-10-additional-languages.md +6 -0
  76. package/dist/application-state/index.d.ts +2 -2
  77. package/dist/application-state/index.d.ts.map +1 -1
  78. package/dist/application-state/index.js +2 -2
  79. package/dist/application-state/index.js.map +1 -1
  80. package/dist/application-state/script-helpers.d.ts +3 -1
  81. package/dist/application-state/script-helpers.d.ts.map +1 -1
  82. package/dist/application-state/script-helpers.js +7 -1
  83. package/dist/application-state/script-helpers.js.map +1 -1
  84. package/dist/application-state/store.d.ts +7 -1
  85. package/dist/application-state/store.d.ts.map +1 -1
  86. package/dist/application-state/store.js +143 -17
  87. package/dist/application-state/store.js.map +1 -1
  88. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  89. package/dist/client/extensions/EmbeddedExtension.js +65 -1
  90. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  91. package/dist/collab/awareness.d.ts +2 -2
  92. package/dist/collab/awareness.d.ts.map +1 -1
  93. package/dist/collab/routes.d.ts +1 -1
  94. package/dist/db/client.d.ts +7 -0
  95. package/dist/db/client.d.ts.map +1 -1
  96. package/dist/db/client.js +39 -1
  97. package/dist/db/client.js.map +1 -1
  98. package/dist/notifications/routes.d.ts +1 -1
  99. package/dist/observability/routes.d.ts +3 -3
  100. package/dist/progress/routes.d.ts +1 -1
  101. package/dist/secrets/routes.d.ts +6 -6
  102. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  103. package/dist/server/transcribe-voice.d.ts +1 -1
  104. package/package.json +1 -1
  105. package/src/application-state/index.ts +3 -0
  106. package/src/application-state/script-helpers.ts +12 -1
  107. package/src/application-state/store.ts +216 -15
  108. package/src/client/extensions/EmbeddedExtension.tsx +89 -1
  109. package/src/db/client.ts +44 -1
@@ -2,17 +2,45 @@ import type { SelectColorToken } from "@/hooks/use-custom-fields";
2
2
 
3
3
  export const SELECT_COLOR_OPTIONS: Array<{
4
4
  value: SelectColorToken;
5
- label: string;
5
+ labelKey: string;
6
6
  className: string;
7
7
  }> = [
8
- { value: "red", label: "Red", className: "bg-red-500" },
9
- { value: "orange", label: "Orange", className: "bg-orange-500" },
10
- { value: "yellow", label: "Yellow", className: "bg-yellow-500" },
11
- { value: "green", label: "Green", className: "bg-green-500" },
12
- { value: "blue", label: "Blue", className: "bg-blue-500" },
13
- { value: "purple", label: "Purple", className: "bg-purple-500" },
14
- { value: "pink", label: "Pink", className: "bg-pink-500" },
15
- { value: "gray", label: "Gray", className: "bg-muted-foreground" },
8
+ { value: "red", labelKey: "fieldEditor.colors.red", className: "bg-red-500" },
9
+ {
10
+ value: "orange",
11
+ labelKey: "fieldEditor.colors.orange",
12
+ className: "bg-orange-500",
13
+ },
14
+ {
15
+ value: "yellow",
16
+ labelKey: "fieldEditor.colors.yellow",
17
+ className: "bg-yellow-500",
18
+ },
19
+ {
20
+ value: "green",
21
+ labelKey: "fieldEditor.colors.green",
22
+ className: "bg-green-500",
23
+ },
24
+ {
25
+ value: "blue",
26
+ labelKey: "fieldEditor.colors.blue",
27
+ className: "bg-blue-500",
28
+ },
29
+ {
30
+ value: "purple",
31
+ labelKey: "fieldEditor.colors.purple",
32
+ className: "bg-purple-500",
33
+ },
34
+ {
35
+ value: "pink",
36
+ labelKey: "fieldEditor.colors.pink",
37
+ className: "bg-pink-500",
38
+ },
39
+ {
40
+ value: "gray",
41
+ labelKey: "fieldEditor.colors.gray",
42
+ className: "bg-muted-foreground",
43
+ },
16
44
  ];
17
45
 
18
46
  export function selectColorClass(color?: SelectColorToken) {
@@ -0,0 +1,23 @@
1
+ import type { FieldType } from "@/hooks/use-custom-fields";
2
+
3
+ export const FIELD_TYPE_LABEL_KEYS: Record<FieldType, string> = {
4
+ text: "fields.types.text",
5
+ rich_text: "fields.types.richText",
6
+ number: "fields.types.number",
7
+ percent: "fields.types.percent",
8
+ currency: "fields.types.currency",
9
+ single_select: "fields.types.singleSelect",
10
+ multi_select: "fields.types.multiSelect",
11
+ date: "fields.types.date",
12
+ };
13
+
14
+ export const FIELD_TYPE_VALUES: FieldType[] = [
15
+ "text",
16
+ "rich_text",
17
+ "number",
18
+ "percent",
19
+ "currency",
20
+ "single_select",
21
+ "multi_select",
22
+ "date",
23
+ ];
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { useEffect, useState } from "react";
2
3
  import { toast } from "sonner";
3
4
 
@@ -34,6 +35,7 @@ export function InboxList({
34
35
  isPending,
35
36
  selectedInboxItemId,
36
37
  }: InboxListProps) {
38
+ const t = useT();
37
39
  const createInboxItem = useCreateInboxItem();
38
40
  const updateInboxItem = useUpdateInboxItem();
39
41
  const deleteInboxItem = useDeleteInboxItem();
@@ -78,20 +80,26 @@ export function InboxList({
78
80
  try {
79
81
  await handleBulkDelete(ids);
80
82
  toast.success(
81
- `Deleted ${ids.length} ${ids.length === 1 ? "inbox item" : "inbox items"}`,
83
+ t("dialogs.deletedCount", {
84
+ count: ids.length,
85
+ entity:
86
+ ids.length === 1
87
+ ? t("inbox.entitySingular")
88
+ : t("inbox.entityPlural"),
89
+ }),
82
90
  );
83
91
  selection.actions.clearSelection();
84
92
  setBulkDeleteOpen(false);
85
93
  } catch {
86
- toast.error("Could not delete selected inbox items.");
94
+ toast.error(t("inbox.bulkDeleteError"));
87
95
  }
88
96
  }
89
97
 
90
98
  return (
91
99
  <>
92
100
  <ListViewHeader
93
- title="Inbox"
94
- description="Capture rough ideas here, then mark ready when they become tasks."
101
+ title={t("inbox.pageTitle")}
102
+ description={t("inbox.pageDescription")}
95
103
  isPending={isPending}
96
104
  showSelectToggle={items.length > 0}
97
105
  selection={selection}
@@ -112,29 +120,29 @@ export function InboxList({
112
120
  <AddListItemInput
113
121
  disabled={createInboxItem.isPending}
114
122
  onCreate={(title) => createInboxItem.mutateAsync({ title })}
115
- placeholder="Add to inbox..."
116
- buttonLabel="Add item"
117
- inputAriaLabel="New inbox item title"
118
- errorMessage="Failed to add inbox item. Please try again."
123
+ placeholder={t("inbox.addPlaceholder")}
124
+ buttonLabel={t("inbox.addButtonLabel")}
125
+ inputAriaLabel={t("inbox.addInputAriaLabel")}
126
+ errorMessage={t("inbox.addErrorMessage")}
119
127
  />
120
128
  )}
121
129
  </div>
122
130
 
123
131
  {isPending ? (
124
132
  <div
125
- aria-label="Inbox list"
133
+ aria-label={t("inbox.listAriaLabel")}
126
134
  className="min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1 outline-none"
127
135
  >
128
136
  <ListSkeletonRows />
129
137
  </div>
130
138
  ) : items.length === 0 ? (
131
139
  <div
132
- aria-label="Inbox list"
140
+ aria-label={t("inbox.listAriaLabel")}
133
141
  className="min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1 outline-none"
134
142
  >
135
143
  <ListEmptyState
136
- heading="Inbox is empty"
137
- description="Add an item above or ask chat to capture something for triage."
144
+ heading={t("inbox.emptyHeading")}
145
+ description={t("inbox.emptyDescription")}
138
146
  />
139
147
  </div>
140
148
  ) : (
@@ -142,7 +150,7 @@ export function InboxList({
142
150
  <List
143
151
  items={items}
144
152
  selection={selection}
145
- ariaLabel="Inbox list"
153
+ ariaLabel={t("inbox.listAriaLabel")}
146
154
  listClassName="flex flex-col gap-3 pb-6"
147
155
  onReorder={handleReorder}
148
156
  renderItem={({ item, sortable }) => (
@@ -194,7 +202,7 @@ export function InboxList({
194
202
  onOpenChange={(open) => {
195
203
  if (!open) setPendingDeleteId(null);
196
204
  }}
197
- entityLabel="inbox item"
205
+ entityLabel={t("inbox.entitySingular")}
198
206
  itemTitle={pendingDeleteItem?.title ?? null}
199
207
  pending={deleteInboxItem.isPending}
200
208
  onConfirm={async () => {
@@ -211,8 +219,8 @@ export function InboxList({
211
219
  open={bulkDeleteOpen}
212
220
  onOpenChange={setBulkDeleteOpen}
213
221
  selectedItems={selection.state.selectedItems}
214
- entitySingular="inbox item"
215
- entityPlural="inbox items"
222
+ entitySingular={t("inbox.entitySingular")}
223
+ entityPlural={t("inbox.entityPlural")}
216
224
  pending={bulkDeleteInboxItems.isPending}
217
225
  onConfirm={() => void confirmBulkDelete()}
218
226
  />
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { useSearchParams } from "react-router";
2
3
 
3
4
  import { InboxList } from "@/components/inbox/InboxList";
@@ -6,6 +7,7 @@ import { ListViewHeader } from "@/components/shared/ListViewHeader";
6
7
  import { useInboxItems } from "@/hooks/use-inbox-items";
7
8
 
8
9
  export function InboxListPage() {
10
+ const t = useT();
9
11
  const [searchParams] = useSearchParams();
10
12
  const selectedInboxItemId = searchParams.get("inboxItem");
11
13
  const { items: serverItems, isPending, isError, error } = useInboxItems();
@@ -15,8 +17,8 @@ export function InboxListPage() {
15
17
  {isError ? (
16
18
  <>
17
19
  <ListViewHeader
18
- title="Inbox"
19
- description="Capture rough ideas here, then mark ready when they become tasks."
20
+ title={t("inbox.pageTitle")}
21
+ description={t("inbox.pageDescription")}
20
22
  isPending={false}
21
23
  showSelectToggle={false}
22
24
  selection={null}
@@ -24,7 +26,7 @@ export function InboxListPage() {
24
26
  />
25
27
  <ListErrorMessage
26
28
  error={error}
27
- fallbackMessage="Failed to load inbox items."
29
+ fallbackMessage={t("inbox.loadError")}
28
30
  />
29
31
  </>
30
32
  ) : (
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { IconChecks, IconTrash } from "@tabler/icons-react";
2
3
  import { useState } from "react";
3
4
 
@@ -33,6 +34,7 @@ export function InboxListRow({
33
34
  onRequestDelete,
34
35
  onMarkReady,
35
36
  }: InboxListRowProps) {
37
+ const t = useT();
36
38
  const [displayTitle, setDisplayTitle] = useState(item.title);
37
39
  const [markReadyPending, setMarkReadyPending] = useState(false);
38
40
  const busy = markReadyPending;
@@ -79,7 +81,7 @@ export function InboxListRow({
79
81
  value={item.title}
80
82
  onSave={onUpdateTitle}
81
83
  onDisplayTitleChange={setDisplayTitle}
82
- ariaLabel="Edit title"
84
+ ariaLabel={t("common.editTitleAriaLabel")}
83
85
  disabled={busy}
84
86
  titleDragProps={rowDrag.titleDragProps}
85
87
  />
@@ -93,11 +95,11 @@ export function InboxListRow({
93
95
  disabled={busy || rowSelection.selectionMode}
94
96
  onClick={() => void handleMarkReady()}
95
97
  >
96
- Mark ready
98
+ {t("common.markReady")}
97
99
  </Button>
98
100
 
99
101
  <RowActionsMenu
100
- ariaLabel={`Actions for ${displayTitle}`}
102
+ ariaLabel={t("common.rowActionsAriaLabel", { title: displayTitle })}
101
103
  disabled={busy || rowSelection.selectionMode}
102
104
  >
103
105
  <DropdownMenuItem
@@ -105,14 +107,14 @@ export function InboxListRow({
105
107
  onSelect={() => selection.actions.startSelection(item.id)}
106
108
  >
107
109
  <IconChecks className="size-4" />
108
- Select
110
+ {t("selection.select")}
109
111
  </DropdownMenuItem>
110
112
  <DropdownMenuItem
111
113
  className="gap-2 text-destructive focus:bg-destructive focus:text-destructive-foreground"
112
114
  onSelect={onRequestDelete}
113
115
  >
114
116
  <IconTrash className="size-4" />
115
- Delete
117
+ {t("common.delete")}
116
118
  </DropdownMenuItem>
117
119
  </RowActionsMenu>
118
120
  </>
@@ -1,4 +1,5 @@
1
1
  import { AgentToggleButton } from "@agent-native/core/client/agent-chat";
2
+ import { useT } from "@agent-native/core/client/i18n";
2
3
  import { IconMenu2 } from "@tabler/icons-react";
3
4
  import { useLocation } from "react-router";
4
5
 
@@ -6,15 +7,13 @@ import { APP_TITLE } from "@/lib/app-config";
6
7
 
7
8
  import { useHeaderTitle, useHeaderActions } from "./HeaderActions";
8
9
 
9
- const pageTitles: Record<string, string> = {
10
- "/tasks": "Tasks",
11
- "/settings": "Settings",
12
- "/team": "Team",
13
- };
10
+ type Translate = ReturnType<typeof useT>;
14
11
 
15
- function resolveTitle(pathname: string): string {
16
- if (pageTitles[pathname]) return pageTitles[pathname];
17
- if (pathname.startsWith("/extensions")) return "Extensions";
12
+ function resolveTitle(t: Translate, pathname: string): string {
13
+ if (pathname === "/tasks") return t("header.pageTasks");
14
+ if (pathname === "/settings") return t("header.pageSettings");
15
+ if (pathname === "/team") return t("header.pageTeam");
16
+ if (pathname.startsWith("/extensions")) return t("header.pageExtensions");
18
17
  return APP_TITLE;
19
18
  }
20
19
 
@@ -23,6 +22,7 @@ interface HeaderProps {
23
22
  }
24
23
 
25
24
  export function Header({ onOpenMobileSidebar }: HeaderProps) {
25
+ const t = useT();
26
26
  const location = useLocation();
27
27
  const title = useHeaderTitle();
28
28
  const actions = useHeaderActions();
@@ -33,7 +33,7 @@ export function Header({ onOpenMobileSidebar }: HeaderProps) {
33
33
  <button
34
34
  type="button"
35
35
  onClick={onOpenMobileSidebar}
36
- aria-label="Open navigation"
36
+ aria-label={t("header.openNavigation")}
37
37
  className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent md:hidden"
38
38
  >
39
39
  <IconMenu2 className="h-4 w-4" />
@@ -42,7 +42,7 @@ export function Header({ onOpenMobileSidebar }: HeaderProps) {
42
42
  <div className="flex items-center gap-3 flex-1 min-w-0">
43
43
  {title ?? (
44
44
  <h1 className="text-lg font-semibold tracking-tight truncate">
45
- {resolveTitle(location.pathname)}
45
+ {resolveTitle(t, location.pathname)}
46
46
  </h1>
47
47
  )}
48
48
  </div>
@@ -2,6 +2,7 @@ import {
2
2
  AgentSidebar,
3
3
  focusAgentChat,
4
4
  } from "@agent-native/core/client/agent-chat";
5
+ import { useT } from "@agent-native/core/client/i18n";
5
6
  import { IconMenu2 } from "@tabler/icons-react";
6
7
  import { useState, useEffect } from "react";
7
8
  import { useLocation } from "react-router";
@@ -35,6 +36,7 @@ function routeOwnsToolbar(pathname: string): boolean {
35
36
  }
36
37
 
37
38
  export function Layout({ children }: LayoutProps) {
39
+ const t = useT();
38
40
  const location = useLocation();
39
41
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
40
42
  const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
@@ -71,7 +73,7 @@ export function Layout({ children }: LayoutProps) {
71
73
  <button
72
74
  type="button"
73
75
  onClick={() => setMobileSidebarOpen(true)}
74
- aria-label="Open navigation"
76
+ aria-label={t("header.openNavigation")}
75
77
  className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
76
78
  >
77
79
  <IconMenu2 className="h-4 w-4" />
@@ -97,9 +99,11 @@ export function Layout({ children }: LayoutProps) {
97
99
  </div>
98
100
  <Sheet open={mobileSidebarOpen} onOpenChange={setMobileSidebarOpen}>
99
101
  <SheetContent side="left" className="p-0 w-[260px]">
100
- <SheetTitle className="sr-only">Navigation</SheetTitle>
102
+ <SheetTitle className="sr-only">
103
+ {t("sidebar.navigationTitle")}
104
+ </SheetTitle>
101
105
  <SheetDescription className="sr-only">
102
- App navigation links
106
+ {t("sidebar.navigationDescription")}
103
107
  </SheetDescription>
104
108
  <Sidebar collapsed={false} collapsible={false} />
105
109
  </SheetContent>
@@ -110,12 +114,12 @@ export function Layout({ children }: LayoutProps) {
110
114
  storageKey="tasks"
111
115
  browserTabId={TAB_ID}
112
116
  onFullscreenRequest={() => focusAgentChat()}
113
- emptyStateText="Ask the agent to inspect or change this app."
117
+ emptyStateText={t("agent.emptyState")}
114
118
  dynamicSuggestions={false}
115
119
  suggestions={[
116
- "Check my calendar and mails - any tasks for today?",
117
- "Prioritise my tasks",
118
- "I need to clean my house, create a list of tasks for it",
120
+ t("agent.suggestionCalendar"),
121
+ t("agent.suggestionPrioritize"),
122
+ t("agent.suggestionCleanHouse"),
119
123
  ]}
120
124
  >
121
125
  {contentFrame}
@@ -1,5 +1,6 @@
1
1
  import { appPath } from "@agent-native/core/client/api-path";
2
2
  import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
3
+ import { useT } from "@agent-native/core/client/i18n";
3
4
  import { OrgSwitcher } from "@agent-native/core/client/org";
4
5
  import { FeedbackButton } from "@agent-native/core/client/ui";
5
6
  import {
@@ -19,10 +20,10 @@ import {
19
20
  import { APP_TITLE } from "@/lib/app-config";
20
21
  import { cn } from "@/lib/utils";
21
22
 
22
- const navItems = [
23
- { icon: IconInbox, label: "Inbox", href: "/inbox" },
24
- { icon: IconCheckbox, label: "Tasks", href: "/tasks" },
25
- { icon: IconForms, label: "Fields", href: "/fields" },
23
+ const NAV_ITEMS = [
24
+ { icon: IconInbox, labelKey: "sidebar.navInbox", href: "/inbox" },
25
+ { icon: IconCheckbox, labelKey: "sidebar.navTasks", href: "/tasks" },
26
+ { icon: IconForms, labelKey: "sidebar.navFields", href: "/fields" },
26
27
  ];
27
28
 
28
29
  interface SidebarProps {
@@ -36,6 +37,7 @@ export function Sidebar({
36
37
  collapsible = true,
37
38
  onCollapsedChange,
38
39
  }: SidebarProps) {
40
+ const t = useT();
39
41
  const location = useLocation();
40
42
  const ToggleIcon = collapsed
41
43
  ? IconLayoutSidebarLeftExpand
@@ -64,13 +66,17 @@ export function Sidebar({
64
66
  "flex shrink-0 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
65
67
  collapsed ? "size-8" : "size-7",
66
68
  )}
67
- aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
69
+ aria-label={
70
+ collapsed
71
+ ? t("sidebar.expandSidebar")
72
+ : t("sidebar.collapseSidebar")
73
+ }
68
74
  >
69
75
  <ToggleIcon className="size-4" />
70
76
  </button>
71
77
  </TooltipTrigger>
72
78
  <TooltipContent side="right">
73
- {collapsed ? "Expand sidebar" : "Collapse sidebar"}
79
+ {collapsed ? t("sidebar.expandSidebar") : t("sidebar.collapseSidebar")}
74
80
  </TooltipContent>
75
81
  </Tooltip>
76
82
  ) : null;
@@ -124,26 +130,27 @@ export function Sidebar({
124
130
  )}
125
131
  >
126
132
  <div className={cn("grid", collapsed ? "gap-0" : "gap-1")}>
127
- {navItems.map((item) => {
133
+ {NAV_ITEMS.map((item) => {
128
134
  const Icon = item.icon;
129
135
  const isActive = location.pathname.startsWith(item.href);
136
+ const label = t(item.labelKey);
130
137
  const link = (
131
138
  <Link
132
139
  to={item.href}
133
140
  className={navClass({ isActive })}
134
141
  aria-current={isActive ? "page" : undefined}
135
- aria-label={collapsed ? item.label : undefined}
142
+ aria-label={collapsed ? label : undefined}
136
143
  >
137
144
  <Icon className="size-4 shrink-0" />
138
145
  <span className={collapsed ? "sr-only" : "truncate"}>
139
- {item.label}
146
+ {label}
140
147
  </span>
141
148
  </Link>
142
149
  );
143
150
  return collapsed ? (
144
151
  <Tooltip key={item.href}>
145
152
  <TooltipTrigger asChild>{link}</TooltipTrigger>
146
- <TooltipContent side="right">{item.label}</TooltipContent>
153
+ <TooltipContent side="right">{label}</TooltipContent>
147
154
  </Tooltip>
148
155
  ) : (
149
156
  <div key={item.href}>{link}</div>
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import { useRef, useState, type FormEvent } from "react";
2
3
  import { toast } from "sonner";
3
4
 
@@ -16,11 +17,16 @@ interface AddListItemInputProps {
16
17
  export function AddListItemInput({
17
18
  disabled = false,
18
19
  onCreate,
19
- placeholder = "Add a task...",
20
- buttonLabel = "Add task",
21
- inputAriaLabel = "New task title",
22
- errorMessage = "Failed to create task. Please try again.",
20
+ placeholder,
21
+ buttonLabel,
22
+ inputAriaLabel,
23
+ errorMessage,
23
24
  }: AddListItemInputProps) {
25
+ const t = useT();
26
+ const resolvedPlaceholder = placeholder ?? t("tasks.addPlaceholder");
27
+ const resolvedButtonLabel = buttonLabel ?? t("tasks.addButtonLabel");
28
+ const resolvedInputAriaLabel = inputAriaLabel ?? t("tasks.addInputAriaLabel");
29
+ const resolvedErrorMessage = errorMessage ?? t("tasks.addErrorMessage");
24
30
  const [title, setTitle] = useState("");
25
31
  const inputRef = useRef<HTMLInputElement>(null);
26
32
 
@@ -32,7 +38,7 @@ export function AddListItemInput({
32
38
  void onCreate(trimmed)
33
39
  .catch(() => {
34
40
  setTitle(trimmed);
35
- toast.error(errorMessage);
41
+ toast.error(resolvedErrorMessage);
36
42
  })
37
43
  .finally(() => {
38
44
  inputRef.current?.focus();
@@ -45,12 +51,12 @@ export function AddListItemInput({
45
51
  ref={inputRef}
46
52
  value={title}
47
53
  onChange={(event) => setTitle(event.target.value)}
48
- placeholder={placeholder}
49
- aria-label={inputAriaLabel}
54
+ placeholder={resolvedPlaceholder}
55
+ aria-label={resolvedInputAriaLabel}
50
56
  disabled={disabled}
51
57
  />
52
58
  <Button type="submit" disabled={disabled || !title.trim()}>
53
- {buttonLabel}
59
+ {resolvedButtonLabel}
54
60
  </Button>
55
61
  </form>
56
62
  );
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import type { ReactNode } from "react";
2
3
 
3
4
  import {
@@ -28,6 +29,7 @@ export function AlertDialog({
28
29
  pending = false,
29
30
  onConfirm,
30
31
  }: AlertDialogProps) {
32
+ const t = useT();
31
33
  return (
32
34
  <AlertDialogRoot open={open} onOpenChange={onOpenChange}>
33
35
  <AlertDialogContent>
@@ -38,13 +40,15 @@ export function AlertDialog({
38
40
  </AlertDialogDescription>
39
41
  </AlertDialogHeader>
40
42
  <AlertDialogFooter>
41
- <AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
43
+ <AlertDialogCancel disabled={pending}>
44
+ {t("common.cancel")}
45
+ </AlertDialogCancel>
42
46
  <AlertDialogAction
43
47
  className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
44
48
  disabled={pending}
45
49
  onClick={() => void onConfirm()}
46
50
  >
47
- Delete
51
+ {t("common.delete")}
48
52
  </AlertDialogAction>
49
53
  </AlertDialogFooter>
50
54
  </AlertDialogContent>
@@ -1,3 +1,5 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+
1
3
  import {
2
4
  AlertDialog,
3
5
  type AlertDialogProps,
@@ -19,16 +21,18 @@ function BulkDeleteDialogDescription({
19
21
  BulkDeleteDialogProps,
20
22
  "selectedItems" | "entitySingular" | "entityPlural"
21
23
  >) {
24
+ const t = useT();
22
25
  const selectedCount = selectedItems.length;
23
26
 
24
27
  return (
25
28
  <div className="space-y-2 text-sm text-muted-foreground">
26
29
  <p>
27
- This permanently removes{" "}
28
30
  {selectedCount === 1
29
- ? `the selected ${entitySingular}`
30
- : `all ${selectedCount} selected ${entityPlural}`}
31
- .
31
+ ? t("dialogs.bulkDeleteDescriptionOne", { entity: entitySingular })
32
+ : t("dialogs.bulkDeleteDescriptionOther", {
33
+ count: selectedCount,
34
+ entity: entityPlural,
35
+ })}
32
36
  </p>
33
37
  {selectedItems.length > 0 ? (
34
38
  <ul className="list-disc space-y-1 pl-5 text-foreground">
@@ -39,7 +43,7 @@ function BulkDeleteDialogDescription({
39
43
  ))}
40
44
  {selectedItems.length > 5 ? (
41
45
  <li className="text-muted-foreground">
42
- and {selectedItems.length - 5} more
46
+ {t("dialogs.andMore", { count: selectedItems.length - 5 })}
43
47
  </li>
44
48
  ) : null}
45
49
  </ul>
@@ -57,12 +61,15 @@ export function BulkDeleteDialog({
57
61
  pending,
58
62
  onConfirm,
59
63
  }: BulkDeleteDialogProps) {
64
+ const t = useT();
60
65
  return (
61
66
  <AlertDialog
62
67
  open={open}
63
68
  onOpenChange={onOpenChange}
64
69
  pending={pending}
65
- title={`Delete ${selectedItems.length === 1 ? entitySingular : entityPlural}?`}
70
+ title={t("dialogs.deleteEntityTitle", {
71
+ entity: selectedItems.length === 1 ? entitySingular : entityPlural,
72
+ })}
66
73
  description={
67
74
  <BulkDeleteDialogDescription
68
75
  selectedItems={selectedItems}
@@ -1,3 +1,4 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
1
2
  import {
2
3
  closestCenter,
3
4
  DndContext,
@@ -56,6 +57,7 @@ function ChipSelectChip({
56
57
  sortable: boolean;
57
58
  onRemove: () => void;
58
59
  }) {
60
+ const t = useT();
59
61
  const {
60
62
  attributes,
61
63
  listeners,
@@ -89,7 +91,7 @@ function ChipSelectChip({
89
91
  variant="ghost"
90
92
  size="icon"
91
93
  disabled={disabled}
92
- aria-label={`Remove ${option.label}`}
94
+ aria-label={t("common.removeAriaLabel", { label: option.label })}
93
95
  onPointerDown={(event) => event.stopPropagation()}
94
96
  onClick={(event) => {
95
97
  event.stopPropagation();
@@ -110,10 +112,13 @@ export function ChipSelect({
110
112
  onSelectedIdsChange,
111
113
  disabled = false,
112
114
  limit,
113
- addButtonLabel = "Add",
114
- emptyLabel = "None selected",
115
+ addButtonLabel,
116
+ emptyLabel,
115
117
  sortable = true,
116
118
  }: ChipSelectProps) {
119
+ const t = useT();
120
+ const resolvedAddButtonLabel = addButtonLabel ?? t("common.add");
121
+ const resolvedEmptyLabel = emptyLabel ?? t("common.noneSelected");
117
122
  const optionsById = useMemo(
118
123
  () => new Map(options.map((option) => [option.id, option])),
119
124
  [options],
@@ -192,7 +197,9 @@ export function ChipSelect({
192
197
  </div>
193
198
  <div className="flex min-w-0 flex-wrap items-center gap-2">
194
199
  {selectedOptions.length === 0 ? (
195
- <span className="text-sm text-muted-foreground">{emptyLabel}</span>
200
+ <span className="text-sm text-muted-foreground">
201
+ {resolvedEmptyLabel}
202
+ </span>
196
203
  ) : sortable ? (
197
204
  <DndContext
198
205
  sensors={sensors}
@@ -218,7 +225,7 @@ export function ChipSelect({
218
225
  disabled={disabled || availableOptions.length === 0 || atLimit}
219
226
  className="h-8 gap-1.5 px-2"
220
227
  >
221
- {addButtonLabel}
228
+ {resolvedAddButtonLabel}
222
229
  <IconChevronDown className="size-4" />
223
230
  </Button>
224
231
  </DropdownMenuTrigger>