@agent-native/core 0.76.10 → 0.76.12

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 (130) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/docs/content/locales/ar-SA/actions.md +7 -7
  4. package/corpus/core/docs/content/locales/ar-SA/template-plan.md +6 -4
  5. package/corpus/core/docs/content/locales/de-DE/actions.md +9 -9
  6. package/corpus/core/docs/content/locales/de-DE/template-plan.md +6 -4
  7. package/corpus/core/docs/content/locales/es-ES/actions.md +9 -9
  8. package/corpus/core/docs/content/locales/es-ES/template-plan.md +6 -4
  9. package/corpus/core/docs/content/locales/fr-FR/actions.md +9 -9
  10. package/corpus/core/docs/content/locales/fr-FR/template-plan.md +6 -4
  11. package/corpus/core/docs/content/locales/hi-IN/actions.md +9 -9
  12. package/corpus/core/docs/content/locales/hi-IN/template-plan.md +4 -4
  13. package/corpus/core/docs/content/locales/ja-JP/actions.md +9 -9
  14. package/corpus/core/docs/content/locales/ja-JP/template-plan.md +4 -4
  15. package/corpus/core/docs/content/locales/ko-KR/actions.md +9 -9
  16. package/corpus/core/docs/content/locales/ko-KR/template-plan.md +4 -4
  17. package/corpus/core/docs/content/locales/pt-BR/actions.md +9 -9
  18. package/corpus/core/docs/content/locales/pt-BR/template-plan.md +6 -4
  19. package/corpus/core/docs/content/locales/zh-CN/actions.md +9 -9
  20. package/corpus/core/docs/content/locales/zh-CN/template-plan.md +4 -4
  21. package/corpus/core/package.json +1 -1
  22. package/corpus/core/src/client/ErrorBoundary.tsx +151 -10
  23. package/corpus/core/src/client/FeedbackButton.tsx +162 -18
  24. package/corpus/core/src/client/blocks/library/AnnotatedCodeBlock.tsx +11 -2
  25. package/corpus/core/src/client/blocks/library/ApiEndpointBlock.tsx +46 -19
  26. package/corpus/core/src/client/blocks/library/MermaidBlock.tsx +7 -5
  27. package/corpus/core/src/client/blocks/library/block-copy.ts +367 -0
  28. package/corpus/core/src/client/blocks/library/diagram.tsx +8 -6
  29. package/corpus/core/src/client/blocks/library/wireframe.tsx +7 -2
  30. package/corpus/core/src/client/i18n.tsx +4 -0
  31. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +51 -31
  32. package/corpus/templates/analytics/app/components/layout/DashboardHeader.tsx +26 -16
  33. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -2
  34. package/corpus/templates/analytics/app/components/layout/HeaderActions.tsx +8 -1
  35. package/corpus/templates/analytics/app/components/layout/Layout.tsx +2 -5
  36. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +172 -107
  37. package/corpus/templates/analytics/app/i18n-data.ts +1076 -5
  38. package/corpus/templates/analytics/app/pages/Settings.tsx +2 -2
  39. package/corpus/templates/analytics/changelog/2026-06-24-analytics-navigation-command-palette-and-settings-now-honor-.md +6 -0
  40. package/corpus/templates/assets/app/components/layout/Header.tsx +0 -2
  41. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +0 -2
  42. package/corpus/templates/calendar/app/pages/CalendarView.tsx +0 -2
  43. package/corpus/templates/chat/app/components/layout/Header.tsx +1 -6
  44. package/corpus/templates/clips/app/components/layout/Header.tsx +1 -6
  45. package/corpus/templates/clips/app/hooks/use-library.ts +2 -0
  46. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +3 -1
  47. package/corpus/templates/clips/changelog/2026-06-24-archive-and-spaces-navigation-is-more-resilient-when-sidebar.md +6 -0
  48. package/corpus/templates/content/app/components/layout/Header.tsx +0 -2
  49. package/corpus/templates/design/app/components/layout/Header.tsx +1 -6
  50. package/corpus/templates/forms/app/components/layout/Header.tsx +1 -6
  51. package/corpus/templates/macros/app/components/layout/Header.tsx +1 -6
  52. package/corpus/templates/mail/app/components/email/CodeBlockLangPicker.tsx +4 -2
  53. package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +18 -12
  54. package/corpus/templates/mail/app/components/email/ComposeEditor.tsx +9 -5
  55. package/corpus/templates/mail/app/components/email/ComposeModal.tsx +36 -19
  56. package/corpus/templates/mail/app/components/email/EmailList.tsx +59 -55
  57. package/corpus/templates/mail/app/components/email/EmailListItem.tsx +7 -3
  58. package/corpus/templates/mail/app/components/email/EmailThread.tsx +25 -16
  59. package/corpus/templates/mail/app/components/email/InlineReplyComposer.tsx +25 -19
  60. package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +32 -17
  61. package/corpus/templates/mail/app/components/email/RecipientInput.tsx +26 -10
  62. package/corpus/templates/mail/app/components/email/SendLaterButton.tsx +5 -3
  63. package/corpus/templates/mail/app/components/email/SnoozeModal.tsx +4 -2
  64. package/corpus/templates/mail/app/components/email/SnoozePopover.tsx +9 -5
  65. package/corpus/templates/mail/app/components/email/extensions/ComposeImageBlock.tsx +4 -2
  66. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +168 -86
  67. package/corpus/templates/mail/app/components/layout/SearchBar.tsx +4 -2
  68. package/corpus/templates/mail/app/i18n/ar-SA.ts +212 -0
  69. package/corpus/templates/mail/app/i18n/de-DE.ts +212 -0
  70. package/corpus/templates/mail/app/i18n/en-US.ts +212 -0
  71. package/corpus/templates/mail/app/i18n/es-ES.ts +212 -0
  72. package/corpus/templates/mail/app/i18n/fr-FR.ts +212 -0
  73. package/corpus/templates/mail/app/i18n/hi-IN.ts +212 -0
  74. package/corpus/templates/mail/app/i18n/ja-JP.ts +212 -0
  75. package/corpus/templates/mail/app/i18n/ko-KR.ts +212 -0
  76. package/corpus/templates/mail/app/i18n/pt-BR.ts +212 -0
  77. package/corpus/templates/mail/app/i18n/zh-CN.ts +211 -0
  78. package/corpus/templates/mail/app/root.tsx +90 -6
  79. package/corpus/templates/mail/changelog/2026-06-24-interface-language-support-now-covers-more-mail-controls-and.md +6 -0
  80. package/corpus/templates/plan/app/components/layout/Header.tsx +1 -6
  81. package/corpus/templates/slides/app/components/layout/Header.tsx +1 -6
  82. package/corpus/templates/videos/app/components/layout/Header.tsx +1 -6
  83. package/dist/client/ErrorBoundary.d.ts.map +1 -1
  84. package/dist/client/ErrorBoundary.js +128 -7
  85. package/dist/client/ErrorBoundary.js.map +1 -1
  86. package/dist/client/FeedbackButton.d.ts.map +1 -1
  87. package/dist/client/FeedbackButton.js +142 -14
  88. package/dist/client/FeedbackButton.js.map +1 -1
  89. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  90. package/dist/client/blocks/library/AnnotatedCodeBlock.js +6 -2
  91. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  92. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  93. package/dist/client/blocks/library/ApiEndpointBlock.js +14 -6
  94. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  95. package/dist/client/blocks/library/MermaidBlock.d.ts.map +1 -1
  96. package/dist/client/blocks/library/MermaidBlock.js +6 -4
  97. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  98. package/dist/client/blocks/library/block-copy.d.ts +33 -0
  99. package/dist/client/blocks/library/block-copy.d.ts.map +1 -0
  100. package/dist/client/blocks/library/block-copy.js +329 -0
  101. package/dist/client/blocks/library/block-copy.js.map +1 -0
  102. package/dist/client/blocks/library/diagram.d.ts.map +1 -1
  103. package/dist/client/blocks/library/diagram.js +7 -5
  104. package/dist/client/blocks/library/diagram.js.map +1 -1
  105. package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
  106. package/dist/client/blocks/library/wireframe.js +4 -2
  107. package/dist/client/blocks/library/wireframe.js.map +1 -1
  108. package/dist/client/i18n.d.ts +1 -0
  109. package/dist/client/i18n.d.ts.map +1 -1
  110. package/dist/client/i18n.js +3 -0
  111. package/dist/client/i18n.js.map +1 -1
  112. package/docs/content/locales/ar-SA/actions.md +7 -7
  113. package/docs/content/locales/ar-SA/template-plan.md +6 -4
  114. package/docs/content/locales/de-DE/actions.md +9 -9
  115. package/docs/content/locales/de-DE/template-plan.md +6 -4
  116. package/docs/content/locales/es-ES/actions.md +9 -9
  117. package/docs/content/locales/es-ES/template-plan.md +6 -4
  118. package/docs/content/locales/fr-FR/actions.md +9 -9
  119. package/docs/content/locales/fr-FR/template-plan.md +6 -4
  120. package/docs/content/locales/hi-IN/actions.md +9 -9
  121. package/docs/content/locales/hi-IN/template-plan.md +4 -4
  122. package/docs/content/locales/ja-JP/actions.md +9 -9
  123. package/docs/content/locales/ja-JP/template-plan.md +4 -4
  124. package/docs/content/locales/ko-KR/actions.md +9 -9
  125. package/docs/content/locales/ko-KR/template-plan.md +4 -4
  126. package/docs/content/locales/pt-BR/actions.md +9 -9
  127. package/docs/content/locales/pt-BR/template-plan.md +6 -4
  128. package/docs/content/locales/zh-CN/actions.md +9 -9
  129. package/docs/content/locales/zh-CN/template-plan.md +4 -4
  130. package/package.json +1 -1
@@ -53,6 +53,7 @@ import {
53
53
  TooltipContent,
54
54
  TooltipTrigger,
55
55
  } from "@/components/ui/tooltip";
56
+ import { useT } from "@agent-native/core/client";
56
57
 
57
58
  type EmailsPage = { emails: EmailMessage[]; nextPageToken?: string };
58
59
  type InfiniteEmails = InfiniteData<EmailsPage, string | undefined>;
@@ -119,34 +120,35 @@ const INBOX_ZERO_PHOTOS = [
119
120
  "photo-1552083375-1447ce886485", // Japanese garden
120
121
  ];
121
122
 
122
- function emptyStateHintForView(view: string): string {
123
+ function emptyStateHintKeyForView(view: string): string {
123
124
  switch (view) {
124
125
  case "snoozed":
125
- return "No snoozed emails right now.";
126
+ return "mail.empty.snoozed";
126
127
  case "drafts":
127
- return "Drafts you save will appear here.";
128
+ return "mail.empty.drafts";
128
129
  case "starred":
129
- return "Star an email to keep it close at hand.";
130
+ return "mail.empty.starred";
130
131
  case "sent":
131
- return "Emails you send will appear here.";
132
+ return "mail.empty.sent";
132
133
  case "scheduled":
133
- return "Scheduled sends will appear here.";
134
+ return "mail.empty.scheduled";
134
135
  case "archive":
135
- return "Archived emails will appear here.";
136
+ return "mail.empty.archive";
136
137
  case "trash":
137
- return "Trashed emails appear here for 30 days.";
138
+ return "mail.empty.trash";
138
139
  case "spam":
139
- return "Spam Gmail flags will appear here.";
140
+ return "mail.empty.spam";
140
141
  case "all":
141
- return "All your mail will appear here.";
142
+ return "mail.empty.all";
142
143
  case "unread":
143
- return "You're caught up — no unread mail.";
144
+ return "mail.empty.unread";
144
145
  default:
145
- return "Emails matching this view will appear here.";
146
+ return "mail.empty.default";
146
147
  }
147
148
  }
148
149
 
149
150
  export function InboxZero() {
151
+ const t = useT();
150
152
  const [loaded, setLoaded] = useState(false);
151
153
  const isEmbedded = isMcpEmbedSurface();
152
154
 
@@ -195,10 +197,10 @@ export function InboxZero() {
195
197
  {/* Bottom text */}
196
198
  <div className="relative mt-auto px-6 pb-6">
197
199
  <p className="text-[15px] font-medium text-white/90 drop-shadow-lg">
198
- You&rsquo;ve hit Inbox Zero
200
+ {t("mail.empty.inboxZeroTitle")}
199
201
  </p>
200
202
  <p className="text-[13px] text-white/60 drop-shadow-lg mt-0.5">
201
- You&rsquo;re all caught up
203
+ {t("mail.empty.inboxZeroSubtitle")}
202
204
  </p>
203
205
  </div>
204
206
  </div>
@@ -261,6 +263,7 @@ function EmailErrorState({
261
263
  onRetry: () => unknown;
262
264
  containerRef: React.RefObject<HTMLDivElement | null>;
263
265
  }) {
266
+ const t = useT();
264
267
  const rateLimitRetryMs = isQuotaError ? getRateLimitRetryMs(message) : 0;
265
268
  const [cooldownRemaining, setCooldownRemaining] = useState(rateLimitRetryMs);
266
269
  const autoRetryFired = useRef(false);
@@ -305,11 +308,11 @@ function EmailErrorState({
305
308
 
306
309
  let buttonLabel: string;
307
310
  if (isFetching) {
308
- buttonLabel = "Retrying…";
311
+ buttonLabel = t("mail.error.retrying");
309
312
  } else if (cooldownRemaining > 0) {
310
- buttonLabel = `Try again in ${cooldownSeconds}s`;
313
+ buttonLabel = t("mail.error.tryAgainIn", { seconds: cooldownSeconds });
311
314
  } else {
312
- buttonLabel = "Try again";
315
+ buttonLabel = t("mail.error.tryAgain");
313
316
  }
314
317
 
315
318
  return (
@@ -321,12 +324,12 @@ function EmailErrorState({
321
324
  </div>
322
325
  <div className="space-y-1">
323
326
  <p className="text-sm font-medium text-foreground">
324
- {isQuotaError ? "Gmail rate limit hit" : "Unable to load emails"}
327
+ {isQuotaError
328
+ ? t("mail.error.rateLimitTitle")
329
+ : t("mail.error.loadTitle")}
325
330
  </p>
326
331
  <p className="text-xs text-muted-foreground">
327
- {isQuotaError
328
- ? "Too many recent requests to Google. Waiting a moment before retrying."
329
- : message}
332
+ {isQuotaError ? t("mail.error.rateLimitDescription") : message}
330
333
  </p>
331
334
  </div>
332
335
  <button
@@ -363,6 +366,7 @@ export function EmailList({
363
366
  onDraftOpen,
364
367
  onNavigateThread,
365
368
  }: EmailListProps) {
369
+ const t = useT();
366
370
  const navigate = useNavigate();
367
371
  const { view = "inbox", threadId } = useParams<{
368
372
  view: string;
@@ -648,9 +652,9 @@ export function EmailList({
648
652
  setUndoAction(undo);
649
653
  toast(
650
654
  threadKeys.length > 1
651
- ? `Archived ${threadKeys.length} conversations.`
652
- : "Archived.",
653
- { action: { label: "UNDO", onClick: undo } },
655
+ ? t("mail.toasts.archivedMany", { count: threadKeys.length })
656
+ : t("mail.toasts.archived"),
657
+ { action: { label: t("mail.actions.undo"), onClick: undo } },
654
658
  );
655
659
  for (const t of targets) {
656
660
  archiveEmail.mutate({
@@ -740,9 +744,9 @@ export function EmailList({
740
744
  setUndoAction(undo);
741
745
  toast(
742
746
  threadKeys.length > 1
743
- ? `Trashed ${threadKeys.length} conversations.`
744
- : "Moved to Trash.",
745
- { action: { label: "UNDO", onClick: undo } },
747
+ ? t("mail.toasts.trashedMany", { count: threadKeys.length })
748
+ : t("mail.toasts.trashed"),
749
+ { action: { label: t("mail.actions.undo"), onClick: undo } },
746
750
  );
747
751
  for (const id of emailIds) trashEmail.mutate(id);
748
752
  setSelectedIds(new Set());
@@ -1131,12 +1135,12 @@ export function EmailList({
1131
1135
  const jobId = getScheduledJobId(thread.latestMessage);
1132
1136
  if (!jobId) return;
1133
1137
  sendScheduledJobNow.mutate(jobId, {
1134
- onSuccess: () => toast("Scheduled email sent."),
1138
+ onSuccess: () => toast(t("mail.toasts.scheduledSent")),
1135
1139
  onError: (error) =>
1136
1140
  toast.error(
1137
1141
  error instanceof Error
1138
1142
  ? error.message
1139
- : "Failed to send scheduled email",
1143
+ : t("mail.toasts.scheduledSendFailed"),
1140
1144
  ),
1141
1145
  });
1142
1146
  };
@@ -1149,8 +1153,8 @@ export function EmailList({
1149
1153
  const jobId = getScheduledJobId(thread.latestMessage);
1150
1154
  if (!jobId) return;
1151
1155
  cancelScheduledJob.mutate(jobId, {
1152
- onSuccess: () => toast("Scheduled email cancelled."),
1153
- onError: () => toast.error("Failed to cancel scheduled email"),
1156
+ onSuccess: () => toast(t("mail.toasts.scheduledCancelled")),
1157
+ onError: () => toast.error(t("mail.toasts.scheduledCancelFailed")),
1154
1158
  });
1155
1159
  };
1156
1160
 
@@ -1202,8 +1206,8 @@ export function EmailList({
1202
1206
  unarchiveEmail.mutate(id);
1203
1207
  };
1204
1208
  setUndoAction(undo);
1205
- toast("Archived.", {
1206
- action: { label: "UNDO", onClick: undo },
1209
+ toast(t("mail.toasts.archived"), {
1210
+ action: { label: t("mail.actions.undo"), onClick: undo },
1207
1211
  });
1208
1212
  archiveEmail.mutate({
1209
1213
  id,
@@ -1253,15 +1257,15 @@ export function EmailList({
1253
1257
  type="button"
1254
1258
  onClick={clearSelection}
1255
1259
  className="flex size-5 items-center justify-center rounded transition-colors hover:bg-accent hover:text-foreground"
1256
- aria-label="Clear selection"
1260
+ aria-label={t("mail.selection.clear")}
1257
1261
  >
1258
1262
  <IconX className="h-3.5 w-3.5" />
1259
1263
  </button>
1260
1264
  </TooltipTrigger>
1261
- <TooltipContent>Clear selection</TooltipContent>
1265
+ <TooltipContent>{t("mail.selection.clear")}</TooltipContent>
1262
1266
  </Tooltip>
1263
1267
  <span className="whitespace-nowrap text-xs font-medium text-foreground/80">
1264
- {selectedIds.size} selected
1268
+ {t("mail.selection.selected", { count: selectedIds.size })}
1265
1269
  </span>
1266
1270
  <DropdownMenu>
1267
1271
  <Tooltip>
@@ -1270,13 +1274,13 @@ export function EmailList({
1270
1274
  <button
1271
1275
  type="button"
1272
1276
  className="flex size-5 items-center justify-center rounded transition-colors hover:bg-accent hover:text-foreground"
1273
- aria-label="Selection actions"
1277
+ aria-label={t("mail.selection.actions")}
1274
1278
  >
1275
1279
  <IconDots className="h-3.5 w-3.5" />
1276
1280
  </button>
1277
1281
  </DropdownMenuTrigger>
1278
1282
  </TooltipTrigger>
1279
- <TooltipContent>Selection actions</TooltipContent>
1283
+ <TooltipContent>{t("mail.selection.actions")}</TooltipContent>
1280
1284
  </Tooltip>
1281
1285
  <DropdownMenuContent align="end" className="w-56">
1282
1286
  <DropdownMenuItem
@@ -1284,27 +1288,27 @@ export function EmailList({
1284
1288
  className="gap-2 text-xs"
1285
1289
  >
1286
1290
  <IconArchive className="h-3.5 w-3.5" />
1287
- Archive
1291
+ {t("mail.actions.archive")}
1288
1292
  </DropdownMenuItem>
1289
1293
  <DropdownMenuItem
1290
1294
  onClick={markFocusedRead}
1291
1295
  className="gap-2 text-xs"
1292
1296
  >
1293
1297
  <IconMailOpened className="h-3.5 w-3.5" />
1294
- Mark read
1298
+ {t("mail.actions.markRead")}
1295
1299
  </DropdownMenuItem>
1296
1300
  <DropdownMenuItem
1297
1301
  onClick={markFocusedUnread}
1298
1302
  className="gap-2 text-xs"
1299
1303
  >
1300
1304
  <IconMail className="h-3.5 w-3.5" />
1301
- Mark unread
1305
+ {t("mail.actions.markUnread")}
1302
1306
  </DropdownMenuItem>
1303
1307
  {movableLabels.length > 0 && (
1304
1308
  <DropdownMenuSub>
1305
1309
  <DropdownMenuSubTrigger className="gap-2 text-xs">
1306
1310
  <IconFolder className="h-3.5 w-3.5" />
1307
- Move to
1311
+ {t("mail.actions.moveTo")}
1308
1312
  </DropdownMenuSubTrigger>
1309
1313
  <DropdownMenuSubContent className="max-h-72 w-56 overflow-y-auto">
1310
1314
  {movableLabels.map((label) => (
@@ -1325,34 +1329,34 @@ export function EmailList({
1325
1329
  className="gap-2 text-xs text-destructive focus:text-destructive"
1326
1330
  >
1327
1331
  <IconTrash className="h-3.5 w-3.5" />
1328
- Move to Trash
1332
+ {t("mail.actions.moveToTrash")}
1329
1333
  </DropdownMenuItem>
1330
1334
  )}
1331
1335
  <DropdownMenuSeparator />
1332
- <DropdownMenuLabel>Select</DropdownMenuLabel>
1336
+ <DropdownMenuLabel>{t("mail.selection.select")}</DropdownMenuLabel>
1333
1337
  <DropdownMenuItem onClick={selectAllThreads} className="text-xs">
1334
- All
1338
+ {t("mail.selection.all")}
1335
1339
  </DropdownMenuItem>
1336
1340
  <DropdownMenuItem onClick={clearSelection} className="text-xs">
1337
- None
1341
+ {t("mail.selection.none")}
1338
1342
  </DropdownMenuItem>
1339
1343
  <DropdownMenuItem onClick={selectReadThreads} className="text-xs">
1340
- Read
1344
+ {t("mail.selection.read")}
1341
1345
  </DropdownMenuItem>
1342
1346
  <DropdownMenuItem onClick={selectUnreadThreads} className="text-xs">
1343
- Unread
1347
+ {t("mail.views.unread")}
1344
1348
  </DropdownMenuItem>
1345
1349
  <DropdownMenuItem
1346
1350
  onClick={selectStarredThreads}
1347
1351
  className="text-xs"
1348
1352
  >
1349
- Starred
1353
+ {t("mail.views.starred")}
1350
1354
  </DropdownMenuItem>
1351
1355
  <DropdownMenuItem
1352
1356
  onClick={selectUnstarredThreads}
1353
1357
  className="text-xs"
1354
1358
  >
1355
- Unstarred
1359
+ {t("mail.selection.unstarred")}
1356
1360
  </DropdownMenuItem>
1357
1361
  </DropdownMenuContent>
1358
1362
  </DropdownMenu>
@@ -1417,10 +1421,10 @@ export function EmailList({
1417
1421
  </svg>
1418
1422
  </div>
1419
1423
  <p className="text-sm font-medium text-foreground/80">
1420
- No results for &ldquo;{searchQuery}&rdquo;
1424
+ {t("mail.empty.noSearchResults", { query: searchQuery })}
1421
1425
  </p>
1422
1426
  <p className="text-xs text-muted-foreground mt-1">
1423
- Try different keywords
1427
+ {t("mail.empty.tryDifferentKeywords")}
1424
1428
  </p>
1425
1429
  </div>
1426
1430
  </div>
@@ -1435,10 +1439,10 @@ export function EmailList({
1435
1439
  <div className="flex flex-1 flex-col items-center justify-center">
1436
1440
  <div className="text-center px-8">
1437
1441
  <p className="text-sm font-medium text-foreground/80">
1438
- Nothing here yet
1442
+ {t("mail.empty.nothingHere")}
1439
1443
  </p>
1440
1444
  <p className="text-xs text-muted-foreground mt-1">
1441
- {emptyStateHintForView(view)}
1445
+ {t(emptyStateHintKeyForView(view))}
1442
1446
  </p>
1443
1447
  </div>
1444
1448
  </div>
@@ -1515,7 +1519,7 @@ export function EmailList({
1515
1519
  {isFetchingNextPage && (
1516
1520
  <div className="flex items-center gap-2 text-xs text-muted-foreground">
1517
1521
  <Spinner className="size-3 text-muted-foreground" />
1518
- Loading more...
1522
+ {t("mail.empty.loadingMore")}
1519
1523
  </div>
1520
1524
  )}
1521
1525
  </div>
@@ -21,6 +21,7 @@ import {
21
21
  TooltipContent,
22
22
  TooltipTrigger,
23
23
  } from "@/components/ui/tooltip";
24
+ import { useT } from "@agent-native/core/client";
24
25
 
25
26
  interface EmailListItemProps {
26
27
  email: EmailMessage;
@@ -158,6 +159,7 @@ export const EmailListItem = memo(function EmailListItem({
158
159
  onSwipeSnooze,
159
160
  highlight,
160
161
  }: EmailListItemProps) {
162
+ const t = useT();
161
163
  const { allAccounts } = useAccountFilter();
162
164
  const isMultiAccount = allAccounts.length > 1;
163
165
 
@@ -597,7 +599,7 @@ export const EmailListItem = memo(function EmailListItem({
597
599
  <IconSend className="h-3.5 w-3.5 rtl:-scale-x-100" />
598
600
  </button>
599
601
  </TooltipTrigger>
600
- <TooltipContent>Send now</TooltipContent>
602
+ <TooltipContent>{t("mail.sendLater.sendNow")}</TooltipContent>
601
603
  </Tooltip>
602
604
  )}
603
605
  {onCancelSchedule && (
@@ -611,7 +613,9 @@ export const EmailListItem = memo(function EmailListItem({
611
613
  <IconX className="h-3.5 w-3.5" />
612
614
  </button>
613
615
  </TooltipTrigger>
614
- <TooltipContent>Cancel scheduled send</TooltipContent>
616
+ <TooltipContent>
617
+ {t("mail.sendLater.cancelScheduledSend")}
618
+ </TooltipContent>
615
619
  </Tooltip>
616
620
  )}
617
621
  {onTrash && (
@@ -625,7 +629,7 @@ export const EmailListItem = memo(function EmailListItem({
625
629
  <IconTrash className="h-3.5 w-3.5" />
626
630
  </button>
627
631
  </TooltipTrigger>
628
- <TooltipContent>Move to Trash</TooltipContent>
632
+ <TooltipContent>{t("mail.actions.moveToTrash")}</TooltipContent>
629
633
  </Tooltip>
630
634
  )}
631
635
  <Tooltip>
@@ -34,7 +34,7 @@ import {
34
34
  import { useQueryClient } from "@tanstack/react-query";
35
35
  import { ensureThread, warmThreads } from "@/lib/thread-cache";
36
36
  import { getResolvedTheme } from "@/lib/theme";
37
- import { appApiPath } from "@agent-native/core/client";
37
+ import { appApiPath, useT } from "@agent-native/core/client";
38
38
  import {
39
39
  decodeHtmlEntities,
40
40
  processHtmlImages,
@@ -111,6 +111,7 @@ export function EmailThread({
111
111
  isMaximized?: boolean;
112
112
  onToggleMaximize?: () => void;
113
113
  }) {
114
+ const t = useT();
114
115
  const { view = "inbox", threadId: routeThreadId } = useParams<{
115
116
  view: string;
116
117
  threadId: string;
@@ -1211,7 +1212,7 @@ export function EmailThread({
1211
1212
  className="inline-flex items-center gap-1.5 text-[12px] text-muted-foreground/50 hover:text-muted-foreground transition-colors"
1212
1213
  >
1213
1214
  <IconExternalLink className="h-3 w-3" />
1214
- View Pull Request
1215
+ {t("mail.thread.viewPullRequest")}
1215
1216
  </a>
1216
1217
  </TooltipTrigger>
1217
1218
  <TooltipContent
@@ -1219,7 +1220,7 @@ export function EmailThread({
1219
1220
  align="start"
1220
1221
  className="flex items-center gap-1.5 text-[12px] font-medium"
1221
1222
  >
1222
- View Pull Request
1223
+ {t("mail.thread.viewPullRequest")}
1223
1224
  <kbd className="flex items-center justify-center rounded border border-border/60 bg-muted px-1 text-[10px] text-muted-foreground">
1224
1225
  {formatShortcut("cmd")}
1225
1226
  </kbd>
@@ -1238,11 +1239,13 @@ export function EmailThread({
1238
1239
  className="inline-flex items-center gap-1.5 text-[12px] text-muted-foreground/50 hover:text-muted-foreground transition-colors disabled:opacity-50"
1239
1240
  >
1240
1241
  <IconMailOff className="h-3 w-3" />
1241
- {unsubscribing ? "Unsubscribing..." : "Unsubscribe"}
1242
+ {unsubscribing
1243
+ ? t("mail.thread.unsubscribing")
1244
+ : t("mail.thread.unsubscribe")}
1242
1245
  </button>
1243
1246
  </TooltipTrigger>
1244
1247
  <TooltipContent>
1245
- Unsubscribe from this mailing list
1248
+ {t("mail.thread.unsubscribeTooltip")}
1246
1249
  </TooltipContent>
1247
1250
  </Tooltip>
1248
1251
  )}
@@ -1679,6 +1682,7 @@ const ExpandedMessageCard = forwardRef<
1679
1682
  },
1680
1683
  ref,
1681
1684
  ) {
1685
+ const t = useT();
1682
1686
  const [showDetails, setShowDetails] = useState(false);
1683
1687
  const senderName = email.from.name || email.from.email;
1684
1688
  const recipients = [
@@ -1960,7 +1964,7 @@ const ExpandedMessageCard = forwardRef<
1960
1964
  className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground"
1961
1965
  >
1962
1966
  <IconDownload className="h-3 w-3" />
1963
- Download all
1967
+ {t("mail.thread.downloadAll")}
1964
1968
  </button>
1965
1969
  )}
1966
1970
  </div>
@@ -2070,6 +2074,7 @@ function PlainTextBody({
2070
2074
  searchTerm?: string;
2071
2075
  activeLocalIdx?: number | null;
2072
2076
  }) {
2077
+ const t = useT();
2073
2078
  const [showQuoted, setShowQuoted] = useState(false);
2074
2079
  const [showSig, setShowSig] = useState(false);
2075
2080
  const containerRef = useRef<HTMLDivElement>(null);
@@ -2169,7 +2174,7 @@ function PlainTextBody({
2169
2174
  {hasSig && !showSig && !forceShowAll && (
2170
2175
  <button
2171
2176
  type="button"
2172
- aria-label="Show signature"
2177
+ aria-label={t("mail.thread.showSignature")}
2173
2178
  onClick={() => setShowSig(true)}
2174
2179
  className="mt-1 inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground/50 transition-colors hover:bg-accent hover:text-muted-foreground"
2175
2180
  >
@@ -2179,7 +2184,7 @@ function PlainTextBody({
2179
2184
  {hasQuoted && !showQuoted && !forceShowAll && (showSig || !hasSig) && (
2180
2185
  <button
2181
2186
  type="button"
2182
- aria-label="Show quoted text"
2187
+ aria-label={t("mail.thread.showQuotedText")}
2183
2188
  onClick={() => setShowQuoted(true)}
2184
2189
  className="mt-1 inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground/50 transition-colors hover:bg-accent hover:text-muted-foreground"
2185
2190
  >
@@ -2541,6 +2546,7 @@ function HtmlEmailBody({
2541
2546
  searchTerm?: string;
2542
2547
  activeLocalIdx?: number | null;
2543
2548
  }) {
2549
+ const t = useT();
2544
2550
  const frameHostRef = useRef<HTMLDivElement>(null);
2545
2551
  const iframeRef = useRef<HTMLIFrameElement>(null);
2546
2552
  const [height, setHeight] = useState(200);
@@ -2977,7 +2983,7 @@ function HtmlEmailBody({
2977
2983
  if (lightened) el.setAttribute("color", lightened);
2978
2984
  });
2979
2985
 
2980
- // Transform dark text color rules in <style> blocks
2986
+ // Transform dark text color rules in <style> blocks // i18n-ignore
2981
2987
  doc.querySelectorAll("style").forEach((tag) => {
2982
2988
  const text = tag.textContent || "";
2983
2989
  tag.textContent = text.replace(
@@ -3287,8 +3293,8 @@ function HtmlEmailBody({
3287
3293
  <IconPhoto className="h-3.5 w-3.5 shrink-0 text-muted-foreground/60" />
3288
3294
  <span>
3289
3295
  {isEmbedded
3290
- ? "Remote images hidden in this embed."
3291
- : "Images blocked."}
3296
+ ? t("mail.thread.remoteImagesHidden")
3297
+ : t("mail.thread.imagesBlocked")}
3292
3298
  </span>
3293
3299
  {!isEmbedded && (
3294
3300
  <>
@@ -3296,14 +3302,16 @@ function HtmlEmailBody({
3296
3302
  onClick={() => setShowImagesForThread(true)}
3297
3303
  className="text-primary hover:text-primary/80 font-medium transition-colors"
3298
3304
  >
3299
- Show images
3305
+ {t("mail.thread.showImages")}
3300
3306
  </button>
3301
3307
  {senderEmail && (
3302
3308
  <button
3303
3309
  onClick={handleAlwaysTrust}
3304
3310
  className="text-muted-foreground/60 hover:text-muted-foreground font-medium transition-colors"
3305
3311
  >
3306
- Always from {senderEmail.split("@")[1]}
3312
+ {t("mail.thread.alwaysFrom", {
3313
+ domain: senderEmail.split("@")[1],
3314
+ })}
3307
3315
  </button>
3308
3316
  )}
3309
3317
  </>
@@ -3321,7 +3329,7 @@ function HtmlEmailBody({
3321
3329
  colorScheme: useDarkIframeCss ? "dark" : "light",
3322
3330
  borderRadius: hasDesignedBg && isDark ? "6px" : undefined,
3323
3331
  }}
3324
- title="Email content"
3332
+ title={t("mail.thread.emailContent")}
3325
3333
  />
3326
3334
  </div>
3327
3335
  );
@@ -3348,6 +3356,7 @@ function ThreadSearchBar({
3348
3356
  totalMatches: number;
3349
3357
  inputRef: React.RefObject<HTMLInputElement | null>;
3350
3358
  }) {
3359
+ const t = useT();
3351
3360
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
3352
3361
  if (e.key === "Escape") {
3353
3362
  e.preventDefault();
@@ -3368,7 +3377,7 @@ function ThreadSearchBar({
3368
3377
  value={query}
3369
3378
  onChange={(e) => onChange(e.target.value)}
3370
3379
  onKeyDown={handleKeyDown}
3371
- placeholder="Search in conversation…"
3380
+ placeholder={t("mail.thread.searchConversation")}
3372
3381
  className="flex-1 bg-transparent text-[13px] text-foreground placeholder:text-muted-foreground/40 outline-none min-w-0"
3373
3382
  autoComplete="off"
3374
3383
  spellCheck={false}
@@ -3376,7 +3385,7 @@ function ThreadSearchBar({
3376
3385
  {query && (
3377
3386
  <span className="text-[12px] text-muted-foreground/50 tabular-nums shrink-0 select-none">
3378
3387
  {totalMatches === 0
3379
- ? "No matches"
3388
+ ? t("mail.search.noMatches")
3380
3389
  : `${matchIdx + 1} / ${totalMatches}`}
3381
3390
  </span>
3382
3391
  )}