Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions .changeset/localize-core-agent-chat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@agent-native/core": minor
"@agent-native/toolkit": minor
---

Localize the Core agent-chat interface and Toolkit composer across every supported locale, provide built-in Core translations with app-level catalog overrides, and guard the complete chat surface against new raw visible strings.
23 changes: 13 additions & 10 deletions packages/core/src/client/AgentPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ function ChatLoadingSkeleton({
composerAreaClassName?: string;
composerLayoutVariant?: AssistantChatProps["composerLayoutVariant"];
}) {
const t = useT();
// Provide empty no-op implementations so renderHeader can render the real
// tab/mode buttons without needing actual chat state.
const noop = useCallback(() => {}, []);
Expand Down Expand Up @@ -349,7 +350,7 @@ function ChatLoadingSkeleton({
>
<div className="agent-chat-scroll flex-1 overflow-y-auto overflow-x-hidden min-h-0">
<div className="agent-empty-state sr-only" aria-busy="true">
Loading chat...
{t("agentChat.empty.loadingChat")}
</div>
</div>
{composerSlot}
Expand Down Expand Up @@ -1241,7 +1242,7 @@ function AgentPanelInner({
<ShareButton
resourceType="chat_thread"
resourceId={activeTab.id}
resourceTitle={activeTab.label || "Chat"}
resourceTitle={activeTab.label || t("agentPanel.chat")}
shareUrl={getChatThreadShareUrl(activeTab.id)}
trigger="icon"
triggerClassName="h-7 w-7"
Expand Down Expand Up @@ -1550,7 +1551,7 @@ function AgentPanelInner({
<ShareButton
resourceType="chat_thread"
resourceId={activeTab.id}
resourceTitle={activeTab.label || "Chat"}
resourceTitle={activeTab.label || t("agentPanel.chat")}
shareUrl={getChatThreadShareUrl(activeTab.id)}
trigger="icon"
triggerClassName="h-8 w-8 border border-border bg-background/95 shadow-sm backdrop-blur hover:bg-accent"
Expand Down Expand Up @@ -1680,7 +1681,7 @@ function AgentPanelInner({
)}
<button
type="button"
aria-label="Close tab"
aria-label={t("agentPanel.closeTab")}
onClick={(e) => {
e.stopPropagation();
closeTab(tab.id);
Expand Down Expand Up @@ -1778,7 +1779,7 @@ function AgentPanelInner({
)}
<button
type="button"
aria-label="Close tab"
aria-label={t("agentPanel.closeTab")}
onClick={(e) => {
e.stopPropagation();
closeTab(tab.id);
Expand Down Expand Up @@ -1825,7 +1826,7 @@ function AgentPanelInner({
<span>Terminal {i + 1}</span>
<button
type="button"
aria-label="Close tab"
aria-label={t("agentPanel.closeTab")}
onClick={(e) => {
e.stopPropagation();
closeCliTab(id);
Expand Down Expand Up @@ -1904,7 +1905,7 @@ function AgentPanelInner({
)}
<button
type="button"
aria-label="Close tab"
aria-label={t("agentPanel.closeTab")}
onClick={(e) => {
e.stopPropagation();
closeTab(tab.id);
Expand Down Expand Up @@ -1950,6 +1951,7 @@ function AgentPanelInner({
activeTabRefCb,
activateOnKeyDown,
closeCliTab,
t,
],
);

Expand Down Expand Up @@ -2074,7 +2076,7 @@ function AgentPanelInner({
<Suspense
fallback={
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
Loading terminal...
{t("agentPanel.loadingTerminal")}
</div>
}
>
Expand Down Expand Up @@ -3529,6 +3531,7 @@ export function focusAgentChat() {
* Dispatches a custom event that AgentSidebar listens for.
*/
export function AgentToggleButton({ className }: { className?: string }) {
const t = useT();
const [open, setOpen] = useState(false);
useEffect(() => {
const handler = (event: Event) => {
Expand All @@ -3548,7 +3551,7 @@ export function AgentToggleButton({ className }: { className?: string }) {
trigger={
<button
type="button"
aria-label="Toggle agent"
aria-label={t("agentPanel.toggleAgent")}
onClick={() => window.dispatchEvent(new Event("agent-panel:toggle"))}
className={cn(
"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent/40 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
Expand All @@ -3558,7 +3561,7 @@ export function AgentToggleButton({ className }: { className?: string }) {
<IconMessageDots size={20} aria-hidden />
</button>
}
content="Toggle agent"
content={t("agentPanel.toggleAgent")}
delayMs={200}
/>
);
Expand Down
38 changes: 35 additions & 3 deletions packages/core/src/client/AssistantChat.display.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,7 @@ describe("missing agent engine setup", () => {
expect(source).toContain(
"if (!hideUserMessage) resumeFollowingRef.current()",
);
expect(source).toContain('aria-label="Scroll to bottom"');
expect(source).toContain('"agentChat.composer.scrollToBottom"');
expect(source).toContain("autoScroll={false}");
expect(source).not.toContain("scrollAnchor");
expect(source).toContain("composerContextItems.length > 0");
Expand Down Expand Up @@ -1564,15 +1564,42 @@ describe("resolveAssistantChatRunningStatusLabel", () => {
).toBe("Preparing generate-design action");
});

it("localizes Core-owned activity labels while preserving the tool name", () => {
expect(
resolveAssistantChatRunningStatusLabel({
runningActivityLabel: "Preparing generate-design...",
isAutoResuming: false,
isReconnecting: false,
hasReconnectContent: false,
labels: {
thinking: "Denkt nach",
resuming: "Wird fortgesetzt",
stillWorking: "Arbeitet weiter",
working: "Arbeitet",
contactingModel: "Modell wird kontaktiert",
starting: (activity) => `${activity} wird gestartet...`,
preparing: (activity) => `${activity} wird vorbereitet...`,
writing: (activity) => `${activity} wird geschrieben...`,
stillGenerating: (activity) => `${activity} wird weiterhin generiert`,
},
}),
).toBe("generate-design wird vorbereitet...");
});

it("shows replayed recovery as still working instead of reconnecting", () => {
expect(
resolveAssistantChatRunningStatusLabel({
runningActivityLabel: null,
isAutoResuming: false,
isReconnecting: true,
hasReconnectContent: true,
labels: {
thinking: "Denkt nach",
resuming: "Wird fortgesetzt",
stillWorking: "Arbeitet weiter",
},
}),
).toBe("Still working");
).toBe("Arbeitet weiter");
});

it("keeps bare reconnect recovery as thinking", () => {
Expand All @@ -1582,8 +1609,13 @@ describe("resolveAssistantChatRunningStatusLabel", () => {
isAutoResuming: false,
isReconnecting: true,
hasReconnectContent: false,
labels: {
thinking: "Denkt nach",
resuming: "Wird fortgesetzt",
stillWorking: "Arbeitet weiter",
},
}),
).toBe("Thinking");
).toBe("Denkt nach");
});
});

Expand Down
Loading
Loading