@12-apps/mcp 3.5.0 → 3.6.0

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.
package/src/guide.ts CHANGED
@@ -87,138 +87,6 @@ export interface AiHostGuide {
87
87
  pluginUrl?: string;
88
88
  }
89
89
 
90
- const CONNECTOR_TAIL: readonly string[] = [
91
- "Deixe OAuth Client ID e Client Secret em branco — não é preciso gerar credenciais: a loja registra o conector automaticamente no primeiro acesso.",
92
- "Confirme e clique em Connect: abre a tela de login da loja — entre com a SUA conta de lojista e autorize o acesso.",
93
- "Pronto: ative o conector na conversa para o assistente consultar e operar a sua loja.",
94
- ];
95
-
96
- /**
97
- * ChatGPT's two-stage configuration (Developer mode is now required before a
98
- * connector can be created). Stage 1 enables Developer mode in Security & login;
99
- * stage 2 creates the connector and signs in — which registers the connection on
100
- * the store side, so no prompt needs to be pasted afterwards.
101
- */
102
- function chatgptConfigureStages(
103
- platformName: string,
104
- ): readonly AiHostConfigureStage[] {
105
- return [
106
- {
107
- id: "enable-dev-mode",
108
- label: "enable developer mode",
109
- link: {
110
- url: "https://chatgpt.com/plugins#settings/Security",
111
- label: "Abrir Segurança e login",
112
- },
113
- steps: [
114
- "Ative o Modo desenvolvedor em Settings › Security and login (Segurança e login).",
115
- ],
116
- },
117
- {
118
- id: "configurar",
119
- label: "configurar",
120
- link: {
121
- url: "https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins",
122
- label: "Criar o conector",
123
- },
124
- steps: [
125
- "Isso vai abrir um popup para você criar um plugin novo. Coloque como nome o nome da sua loja e, no campo MCP, o link copiado no passo anterior.",
126
- 'Marque a caixa "I understand and want to continue" — a OpenAI não revisou este servidor MCP; ela avisa que sites podem tentar roubar seus dados ou induzir o modelo a ações indevidas, incluindo destruir dados.',
127
- `Clique em "Sign in with ${platformName}" e entre com a sua conta de lojista para autorizar o acesso. Pronto: a conexão é registrada automaticamente.`,
128
- ],
129
- },
130
- ];
131
- }
132
-
133
- /**
134
- * The AI hosts a store owner can connect, in recommended order. Same OAuth flow
135
- * everywhere (the host drives it) — only the menu path differs per app.
136
- *
137
- * A FUNCTION of the platform's name, because one step is not generic: the
138
- * ChatGPT connector's consent screen shows an OAuth button labelled with
139
- * whoever operates the server, and the owner is told which button to click. It
140
- * used to name one particular STORE on one particular deployment — not even the
141
- * product, a tenant of it — so every other adopter instructed its owners to
142
- * click a button that does not exist.
143
- */
144
- export function aiHostGuides(platformName: string): readonly AiHostGuide[] {
145
- const chatgptStages = chatgptConfigureStages(platformName);
146
- return [
147
- {
148
- id: "claude",
149
- label: "Claude.ai",
150
- brand: "claude",
151
- kind: "No navegador",
152
- link: {
153
- url: "https://claude.ai/new?modal=add-custom-connector#settings/customize-connectors",
154
- label: "Abrir os conectores do Claude",
155
- },
156
- docs: {
157
- url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
158
- label: "documentação oficial da Anthropic — conectores personalizados",
159
- },
160
- steps: [
161
- "Clique no botão acima (ou vá em Settings › Customize › Connectors) e escolha Add custom connector.",
162
- "Dê um nome ao conector (ex.: o nome da sua loja) e cole a URL do servidor MCP da sua loja (copie acima) no campo de URL.",
163
- ...CONNECTOR_TAIL,
164
- ],
165
- },
166
- {
167
- id: "claude-desktop",
168
- label: "Claude Desktop",
169
- brand: "claude",
170
- kind: "Aplicativo (Windows/Mac)",
171
- docs: {
172
- url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
173
- label: "documentação oficial da Anthropic — conectores personalizados",
174
- },
175
- steps: [
176
- "Abra o Claude Desktop e vá em Settings (⚙️) › Connectors.",
177
- "Clique em Add custom connector e cole a URL do servidor MCP da sua loja (copie acima).",
178
- ...CONNECTOR_TAIL,
179
- ],
180
- },
181
- {
182
- id: "chatgpt",
183
- label: "ChatGPT",
184
- brand: "openai",
185
- kind: "No navegador",
186
- link: {
187
- url: "https://chatgpt.com/plugins",
188
- label: "Abrir os plugins do ChatGPT",
189
- },
190
- docs: {
191
- url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
192
- label:
193
- "documentação oficial da OpenAI — conectar um servidor MCP ao ChatGPT",
194
- },
195
- configureStages: chatgptStages,
196
- // Mirrors the flattened stage instructions so the MCP connect guide
197
- // (`connectToChatGpt`) can never drift from what owners see in the wizard.
198
- steps: chatgptStages.flatMap((stage) => stage.steps),
199
- },
200
- {
201
- id: "codex",
202
- label: "Codex",
203
- brand: "openai",
204
- kind: "App / CLI de desenvolvedor",
205
- link: {
206
- url: "https://developers.openai.com/codex",
207
- label: "Documentação do Codex",
208
- },
209
- docs: {
210
- url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
211
- label: "documentação oficial da OpenAI — conectar um servidor MCP",
212
- },
213
- steps: [
214
- "No Codex, abra as configurações de MCP (Settings › MCP no app, ou o arquivo de configuração na CLI).",
215
- "Adicione um servidor MCP e cole a URL do servidor MCP da sua loja (copie acima) como um conector remoto (HTTP).",
216
- ...CONNECTOR_TAIL,
217
- ],
218
- },
219
- ];
220
- }
221
-
222
90
  export interface AiCapability {
223
91
  /** Stable id — maps to an icon in the component. */
224
92
  id: string;
@@ -228,38 +96,16 @@ export interface AiCapability {
228
96
  detail: string;
229
97
  }
230
98
 
231
- /** What a connected assistant does for the owner — one marketing card each. */
232
- export const AI_CAPABILITIES: readonly AiCapability[] = [
233
- {
234
- id: "orders",
235
- title: "Acompanhe seus pedidos",
236
- detail: '"Quais pedidos entraram hoje?"',
237
- },
238
- {
239
- id: "inventory",
240
- title: "Controle o estoque",
241
- detail:
242
- '"Quanto ainda tenho do produto X? Registre a entrada de 20 unidades."',
243
- },
244
- {
245
- id: "catalog",
246
- title: "Gerencie o catálogo",
247
- detail: "Crie e edite produtos e categorias conversando.",
248
- },
249
- {
250
- id: "sales",
251
- title: "Entenda suas vendas",
252
- detail: '"Qual foi o faturamento da semana?"',
253
- },
254
- ];
255
99
 
256
100
  /**
257
101
  * The permission model in one line, shown prominently: the assistant acts AS
258
102
  * the signed-in owner (auth-passthrough) — it can do exactly what the owner
259
103
  * can, nothing more, and no extra credential/API key is ever created.
104
+ *
105
+ * A TYPE now, not a constant. The sentence is copy, and a package that shipped
106
+ * it made one product's Portuguese every adopter's silent default.
260
107
  */
261
- export const AI_PERMISSION_MODEL =
262
- "O assistente age em seu nome, com exatamente as suas permissões: ele pode fazer o que você pode fazer na sua loja — nada além disso. Não é preciso criar nenhuma chave ou credencial extra; a autorização usa o seu próprio login.";
108
+ export type AiPermissionModel = string;
263
109
 
264
110
  /** The two tools the paste-in prompt drives, and what to call the store id. */
265
111
  export interface AiConnectPromptSpec {
@@ -288,12 +134,34 @@ export interface AiConnectPromptSpec {
288
134
  * two tools that do not exist, and because nothing registered the connection,
289
135
  * the wizard's confirm step then waited forever for a state that could never
290
136
  * arrive.
137
+
138
+ /**
139
+ * The sentences the paste-in prompt is written in.
140
+ *
141
+ * A template rather than a string, because the prompt INTERPOLATES the host's
142
+ * own tool names and its word order is language-specific. The package supplies
143
+ * the spec; the host supplies the way it is said.
144
+ */
145
+ export interface AiConnectPromptCopy {
146
+ (spec: AiConnectPromptSpec): string;
147
+ }
148
+
149
+ /**
150
+ * The message the owner pastes into the assistant's chat right after
151
+ * connecting: announce the connection, then read something real to prove it
152
+ * works. The owner types nothing.
153
+ *
154
+ * BUILT from the host's tool names rather than shipped with them. This was a
155
+ * constant naming two tools — `announceAiConnection` and `listInventory` — that
156
+ * THIS PACKAGE does not define or serve; they belong to one adopter's surface.
157
+ * Any other host handed its owner a prompt instructing the assistant to call
158
+ * two tools that do not exist, and because nothing registered the connection,
159
+ * the wizard's confirm step then waited forever for a state that could never
160
+ * arrive.
161
+ *
162
+ * The WORDS around those names are the host's too, since FUT-760: this is a
163
+ * thin seam that hands the spec to the host's own template.
291
164
  */
292
- export function aiConnectPrompt(spec: AiConnectPromptSpec): string {
293
- return (
294
- "Você agora tem acesso ao conector MCP da minha loja. Faça, nesta ordem:\n" +
295
- `1) Execute a ferramenta ${spec.announceTool} informando qual assistente você é (host: "chatgpt", "claude" ou "codex") para registrar a conexão com a minha loja.\n` +
296
- `2) Execute a ferramenta ${spec.probeTool} para confirmar o acesso a ${spec.probeSubject}.\n` +
297
- `Se precisar do identificador da loja, me pergunte o ${spec.identifierName}.`
298
- );
165
+ export function aiConnectPrompt(spec: AiConnectPromptSpec, copy: AiConnectPromptCopy): string {
166
+ return copy(spec);
299
167
  }
package/src/index.ts CHANGED
@@ -13,9 +13,6 @@ export * from "./types";
13
13
  // truth for BOTH the `@12-apps/mcp/react` onboarding UI and the server-side connect
14
14
  // tools, so what an agent reads via MCP cannot drift from what owners see.
15
15
  export {
16
- aiHostGuides,
17
- AI_CAPABILITIES,
18
- AI_PERMISSION_MODEL,
19
16
  aiConnectPrompt,
20
17
  type AiConnectPromptSpec,
21
18
  providerForHostId,
@@ -99,3 +96,11 @@ export {
99
96
  type AuthorizationServerMetadataInput,
100
97
  type AuthorizationServerPaths,
101
98
  } from "./auth/authorization-server-metadata";
99
+
100
+ export type { AiConnectPromptCopy } from "./guide";
101
+ export {
102
+ PT_BR_AI_CAPABILITIES,
103
+ PT_BR_AI_CONNECT_PROMPT,
104
+ PT_BR_AI_HOST_GUIDES,
105
+ PT_BR_AI_PERMISSION_MODEL,
106
+ } from "./pt-BR";
package/src/pt-BR.ts ADDED
@@ -0,0 +1,195 @@
1
+ import type {
2
+ AiCapability,
3
+ AiConnectPromptSpec,
4
+ AiHostConfigureStage,
5
+ AiHostGuide,
6
+ AiPermissionModel,
7
+ } from "./guide";
8
+
9
+ /**
10
+ * The pt-BR pack for the AI-integration surface — NAMED constants a host passes
11
+ * by hand, never defaults (FUT-760).
12
+ *
13
+ * The filename is what exempts this file from the copy-portability gate:
14
+ * Portuguese may ship, it may not be silent. Every sentence here is VERBATIM
15
+ * what `guide.ts` used to compile in, so a host adopting it sees no change on
16
+ * screen — what changes is that the walkthrough is chosen in a diff.
17
+ *
18
+ * The guides are DATA as much as copy: which assistants are offered, their
19
+ * stage ids and brands. Both halves travel together because a step label and
20
+ * the stage it labels are useless apart.
21
+ */
22
+
23
+ const CONNECTOR_TAIL: readonly string[] = [
24
+ "Deixe OAuth Client ID e Client Secret em branco — não é preciso gerar credenciais: a loja registra o conector automaticamente no primeiro acesso.",
25
+ "Confirme e clique em Connect: abre a tela de login da loja — entre com a SUA conta de lojista e autorize o acesso.",
26
+ "Pronto: ative o conector na conversa para o assistente consultar e operar a sua loja.",
27
+ ];
28
+
29
+ /**
30
+ * ChatGPT's two-stage configuration (Developer mode is now required before a
31
+ * connector can be created). Stage 1 enables Developer mode in Security & login;
32
+ * stage 2 creates the connector and signs in — which registers the connection on
33
+ * the store side, so no prompt needs to be pasted afterwards.
34
+ */
35
+ function chatgptConfigureStages(
36
+ platformName: string,
37
+ ): readonly AiHostConfigureStage[] {
38
+ return [
39
+ {
40
+ id: "enable-dev-mode",
41
+ label: "enable developer mode",
42
+ link: {
43
+ url: "https://chatgpt.com/plugins#settings/Security",
44
+ label: "Abrir Segurança e login",
45
+ },
46
+ steps: [
47
+ "Ative o Modo desenvolvedor em Settings › Security and login (Segurança e login).",
48
+ ],
49
+ },
50
+ {
51
+ id: "configurar",
52
+ label: "configurar",
53
+ link: {
54
+ url: "https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins",
55
+ label: "Criar o conector",
56
+ },
57
+ steps: [
58
+ "Isso vai abrir um popup para você criar um plugin novo. Coloque como nome o nome da sua loja e, no campo MCP, o link copiado no passo anterior.",
59
+ 'Marque a caixa "I understand and want to continue" — a OpenAI não revisou este servidor MCP; ela avisa que sites podem tentar roubar seus dados ou induzir o modelo a ações indevidas, incluindo destruir dados.',
60
+ `Clique em "Sign in with ${platformName}" e entre com a sua conta de lojista para autorizar o acesso. Pronto: a conexão é registrada automaticamente.`,
61
+ ],
62
+ },
63
+ ];
64
+ }
65
+
66
+ /**
67
+ * The AI hosts a store owner can connect, in recommended order. Same OAuth flow
68
+ * everywhere (the host drives it) — only the menu path differs per app.
69
+ *
70
+ * A FUNCTION of the platform's name, because one step is not generic: the
71
+ * ChatGPT connector's consent screen shows an OAuth button labelled with
72
+ * whoever operates the server, and the owner is told which button to click. It
73
+ * used to name one particular STORE on one particular deployment — not even the
74
+ * product, a tenant of it — so every other adopter instructed its owners to
75
+ * click a button that does not exist.
76
+ */
77
+ export function PT_BR_AI_HOST_GUIDES(platformName: string): readonly AiHostGuide[] {
78
+ const chatgptStages = chatgptConfigureStages(platformName);
79
+ return [
80
+ {
81
+ id: "claude",
82
+ label: "Claude.ai",
83
+ brand: "claude",
84
+ kind: "No navegador",
85
+ link: {
86
+ url: "https://claude.ai/new?modal=add-custom-connector#settings/customize-connectors",
87
+ label: "Abrir os conectores do Claude",
88
+ },
89
+ docs: {
90
+ url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
91
+ label: "documentação oficial da Anthropic — conectores personalizados",
92
+ },
93
+ steps: [
94
+ "Clique no botão acima (ou vá em Settings › Customize › Connectors) e escolha Add custom connector.",
95
+ "Dê um nome ao conector (ex.: o nome da sua loja) e cole a URL do servidor MCP da sua loja (copie acima) no campo de URL.",
96
+ ...CONNECTOR_TAIL,
97
+ ],
98
+ },
99
+ {
100
+ id: "claude-desktop",
101
+ label: "Claude Desktop",
102
+ brand: "claude",
103
+ kind: "Aplicativo (Windows/Mac)",
104
+ docs: {
105
+ url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
106
+ label: "documentação oficial da Anthropic — conectores personalizados",
107
+ },
108
+ steps: [
109
+ "Abra o Claude Desktop e vá em Settings (⚙️) › Connectors.",
110
+ "Clique em Add custom connector e cole a URL do servidor MCP da sua loja (copie acima).",
111
+ ...CONNECTOR_TAIL,
112
+ ],
113
+ },
114
+ {
115
+ id: "chatgpt",
116
+ label: "ChatGPT",
117
+ brand: "openai",
118
+ kind: "No navegador",
119
+ link: {
120
+ url: "https://chatgpt.com/plugins",
121
+ label: "Abrir os plugins do ChatGPT",
122
+ },
123
+ docs: {
124
+ url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
125
+ label:
126
+ "documentação oficial da OpenAI — conectar um servidor MCP ao ChatGPT",
127
+ },
128
+ configureStages: chatgptStages,
129
+ // Mirrors the flattened stage instructions so the MCP connect guide
130
+ // (`connectToChatGpt`) can never drift from what owners see in the wizard.
131
+ steps: chatgptStages.flatMap((stage) => stage.steps),
132
+ },
133
+ {
134
+ id: "codex",
135
+ label: "Codex",
136
+ brand: "openai",
137
+ kind: "App / CLI de desenvolvedor",
138
+ link: {
139
+ url: "https://developers.openai.com/codex",
140
+ label: "Documentação do Codex",
141
+ },
142
+ docs: {
143
+ url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
144
+ label: "documentação oficial da OpenAI — conectar um servidor MCP",
145
+ },
146
+ steps: [
147
+ "No Codex, abra as configurações de MCP (Settings › MCP no app, ou o arquivo de configuração na CLI).",
148
+ "Adicione um servidor MCP e cole a URL do servidor MCP da sua loja (copie acima) como um conector remoto (HTTP).",
149
+ ...CONNECTOR_TAIL,
150
+ ],
151
+ },
152
+ ];
153
+ }
154
+
155
+ export const PT_BR_AI_CAPABILITIES: readonly AiCapability[] = [
156
+ {
157
+ id: "orders",
158
+ title: "Acompanhe seus pedidos",
159
+ detail: '"Quais pedidos entraram hoje?"',
160
+ },
161
+ {
162
+ id: "inventory",
163
+ title: "Controle o estoque",
164
+ detail:
165
+ '"Quanto ainda tenho do produto X? Registre a entrada de 20 unidades."',
166
+ },
167
+ {
168
+ id: "catalog",
169
+ title: "Gerencie o catálogo",
170
+ detail: "Crie e edite produtos e categorias conversando.",
171
+ },
172
+ {
173
+ id: "sales",
174
+ title: "Entenda suas vendas",
175
+ detail: '"Qual foi o faturamento da semana?"',
176
+ },
177
+ ];
178
+
179
+ /**
180
+ * The permission model in one line, shown prominently: the assistant acts AS
181
+ * the signed-in owner (auth-passthrough) — it can do exactly what the owner
182
+ * can, nothing more, and no extra credential/API key is ever created.
183
+ */
184
+ export const PT_BR_AI_PERMISSION_MODEL: AiPermissionModel =
185
+ "O assistente age em seu nome, com exatamente as suas permissões: ele pode fazer o que você pode fazer na sua loja — nada além disso. Não é preciso criar nenhuma chave ou credencial extra; a autorização usa o seu próprio login.";
186
+
187
+ /** The pt-BR paste-in prompt, built from the host's own tool names. */
188
+ export function PT_BR_AI_CONNECT_PROMPT(spec: AiConnectPromptSpec): string {
189
+ return (
190
+ "Você agora tem acesso ao conector MCP da minha loja. Faça, nesta ordem:\n" +
191
+ `1) Execute a ferramenta ${spec.announceTool} informando qual assistente você é (host: "chatgpt", "claude" ou "codex") para registrar a conexão com a minha loja.\n` +
192
+ `2) Execute a ferramenta ${spec.probeTool} para confirmar o acesso a ${spec.probeSubject}.\n` +
193
+ `Se precisar do identificador da loja, me pergunte o ${spec.identifierName}.`
194
+ );
195
+ }
@@ -1,7 +1,8 @@
1
1
  import { Box } from "@12-apps/ui/mui/Box";
2
2
  import { Text } from "@12-apps/ui/typography/Text";
3
3
 
4
- import { AI_CAPABILITIES, type AiCapability } from "../guide";
4
+ import type { AiCapability } from "../guide";
5
+ import type { AiCapabilitiesCopy } from "./copy";
5
6
  import { CapabilityIcon } from "./ai-icons";
6
7
 
7
8
  /** One marketing card: an icon, a headline and an example prompt (as a bubble). */
@@ -64,22 +65,25 @@ function CapabilityCard({ capability }: { capability: AiCapability }): React.JSX
64
65
  * Marketing block for the AI integration: a headline + a responsive grid of
65
66
  * capability cards. Shown on the onboarding landing (before the owner starts) to
66
67
  * sell the feature — "here's what the assistant does for you". `capabilities`
67
- * defaults to the shared set; apps can pass their own.
68
+ * and `copy` are REQUIRED host copy the package ships no default sentence
69
+ * (FUT-760).
68
70
  */
69
71
  export function AiCapabilities({
70
- capabilities = AI_CAPABILITIES,
72
+ capabilities,
73
+ copy,
71
74
  }: {
72
- capabilities?: readonly AiCapability[];
75
+ capabilities: readonly AiCapability[];
76
+ copy: AiCapabilitiesCopy;
73
77
  }): React.JSX.Element {
74
78
  return (
75
79
  <Box data-testid="ai-capability-examples" sx={{ width: "100%" }}>
76
80
  <Box sx={{ mb: 3 }}>
77
81
  <Text variant="heading" size="sm" as="h2">
78
- O que o assistente faz por você
82
+ {copy.heading}
79
83
  </Text>
80
84
  <Box sx={{ mt: 0.5 }}>
81
85
  <Text variant="body" size="sm" as="p" color="secondary">
82
- Sem planilhas, sem cliques — é só perguntar na conversa.
86
+ {copy.subheading}
83
87
  </Text>
84
88
  </Box>
85
89
  </Box>
@@ -1,3 +1,4 @@
1
+ import type { AiConnectionSummaryCopy } from "./copy";
1
2
  import { providerForHostId, type AiHostGuide, type AiProvider } from "../guide";
2
3
 
3
4
  /**
@@ -54,14 +55,14 @@ export function connectionForHost(
54
55
  );
55
56
  }
56
57
 
57
- /** Short pt-BR "ativo há X" from a timestamp (agora / min / h / dias). */
58
- export function activeAgo(date: Date): string {
58
+ /** The recency line from a timestamp, in the host's own words. */
59
+ export function activeAgo(date: Date, copy: AiConnectionSummaryCopy): string {
59
60
  const minutes = Math.max(0, Math.floor((Date.now() - date.getTime()) / 60_000));
60
- if (minutes < 1) return "ativo agora";
61
- if (minutes < 60) return `ativo há ${minutes} min`;
61
+ if (minutes < 1) return copy.activeNow;
62
+ if (minutes < 60) return copy.activeMinutes(minutes);
62
63
  const hours = Math.floor(minutes / 60);
63
- if (hours < 24) return `ativo há ${hours} h`;
64
- return `ativo há ${Math.floor(hours / 24)} dias`;
64
+ if (hours < 24) return copy.activeHours(hours);
65
+ return copy.activeDays(Math.floor(hours / 24));
65
66
  }
66
67
 
67
68
  /** The most-recently-active connection across all providers (null when none). */
@@ -77,20 +78,24 @@ export function connectedTitle(
77
78
  connections: readonly AiConnection[],
78
79
  hosts: readonly AiHostGuide[],
79
80
  connectedHostId: string | undefined,
81
+ copy: AiConnectionSummaryCopy,
80
82
  ): string {
81
83
  const providers = [...new Set(connections.map((c) => c.host).filter((h): h is AiProvider => h !== null))];
82
- if (providers.length === 1) return `${PROVIDER_LABEL[providers[0]!]} Conectado`;
84
+ if (providers.length === 1) return `${PROVIDER_LABEL[providers[0]!]} ${copy.connectedSuffix}`;
83
85
  if (providers.length > 1) {
84
- return `${providers.map((p) => PROVIDER_LABEL[p]).join(", ")} conectados`;
86
+ return copy.connectedSeveral(providers.map((p) => PROVIDER_LABEL[p]).join(", "));
85
87
  }
86
88
  // Legacy connection with no attributed provider — name the completed host.
87
- if (connections.length > 0) return `${hostLabel(hosts, connectedHostId)} Conectado`;
88
- return "IA conectada";
89
+ if (connections.length > 0) return `${hostLabel(hosts, connectedHostId)} ${copy.connectedSuffix}`;
90
+ return copy.connectedGeneric;
89
91
  }
90
92
 
91
93
  /** The completed summary line (second line) for the configured-state header. */
92
- export function connectedSummary(connections: readonly AiConnection[]): string {
94
+ export function connectedSummary(
95
+ connections: readonly AiConnection[],
96
+ copy: AiConnectionSummaryCopy,
97
+ ): string {
93
98
  const recent = mostRecent(connections);
94
- if (!recent) return "Integração configurada";
95
- return activeAgo(recent.lastActiveAt);
99
+ if (!recent) return copy.configured;
100
+ return activeAgo(recent.lastActiveAt, copy);
96
101
  }