blume 0.7.0 → 0.8.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.
Files changed (168) hide show
  1. package/CHANGELOG.md +618 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/cli/index.js +1487 -360
  5. package/dist/cli/index.js.map +91 -85
  6. package/dist/types/ai/component-markdown.d.ts +34 -0
  7. package/dist/types/components/content/youtube.d.ts +18 -0
  8. package/dist/types/core/base-path.d.ts +9 -0
  9. package/dist/types/core/config-input.d.ts +36 -2
  10. package/dist/types/core/config.d.ts +3 -2
  11. package/dist/types/core/data.d.ts +2 -0
  12. package/dist/types/core/i18n-ui.d.ts +476 -132
  13. package/dist/types/core/schema.d.ts +216 -145
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/openapi/references.d.ts +60 -0
  16. package/docs/01-quickstart.mdx +5 -2
  17. package/docs/02-deployment.mdx +8 -8
  18. package/docs/03-faq.mdx +46 -16
  19. package/docs/advanced/custom-pages.mdx +1 -1
  20. package/docs/advanced/skills.mdx +1 -1
  21. package/docs/configuration/ai.mdx +49 -10
  22. package/docs/configuration/customization.mdx +11 -0
  23. package/docs/configuration/index.mdx +27 -3
  24. package/docs/configuration/seo.mdx +2 -2
  25. package/docs/content/components.mdx +1 -1
  26. package/docs/content/i18n.mdx +1 -1
  27. package/docs/content/navigation.mdx +3 -3
  28. package/docs/content/sources.mdx +1 -1
  29. package/docs/content/syntax.mdx +4 -2
  30. package/docs/index.mdx +2 -2
  31. package/docs/reference/cli.mdx +8 -6
  32. package/package.json +14 -4
  33. package/skills/blume/SKILL.md +5 -3
  34. package/skills/blume-update-docs/SKILL.md +3 -2
  35. package/src/ai/agent-readability.ts +9 -8
  36. package/src/ai/ask-context.ts +7 -2
  37. package/src/ai/ask-data.ts +3 -0
  38. package/src/ai/component-markdown.ts +461 -0
  39. package/src/ai/llms.ts +135 -26
  40. package/src/ai/markdown.ts +35 -6
  41. package/src/ai/mcp/data.ts +25 -4
  42. package/src/ai/mcp/discovery.ts +10 -3
  43. package/src/ai/mcp/server.ts +21 -7
  44. package/src/ai/visibility.ts +74 -0
  45. package/src/astro/component-slots.ts +11 -1
  46. package/src/astro/generate.ts +76 -45
  47. package/src/astro/integration.ts +1 -1
  48. package/src/astro/markdown-negotiation.ts +1 -1
  49. package/src/astro/pages.ts +81 -19
  50. package/src/astro/templates.ts +99 -12
  51. package/src/blume-modules.d.ts +8 -0
  52. package/src/cli/commands/build.ts +99 -19
  53. package/src/cli/commands/check.ts +1 -1
  54. package/src/cli/commands/dev.ts +26 -5
  55. package/src/cli/commands/eject.ts +47 -19
  56. package/src/cli/commands/init.ts +120 -180
  57. package/src/cli/commands/preview.ts +4 -1
  58. package/src/cli/commands/validate.ts +43 -2
  59. package/src/cli/dev-lock.ts +8 -4
  60. package/src/cli/eject-scripts.ts +72 -0
  61. package/src/cli/env.ts +15 -5
  62. package/src/cli/init/questions.ts +158 -0
  63. package/src/cli/init/scaffold.ts +380 -0
  64. package/src/components/content/AccordionItem.astro +23 -4
  65. package/src/components/content/Badge.astro +3 -1
  66. package/src/components/content/Card.astro +4 -2
  67. package/src/components/content/Step.astro +10 -1
  68. package/src/components/content/Tabs.astro +15 -3
  69. package/src/components/content/Tile.astro +2 -1
  70. package/src/components/content/Tooltip.astro +3 -1
  71. package/src/components/content/Update.astro +9 -2
  72. package/src/components/content/auto-type-table.ts +7 -1
  73. package/src/components/content/base-href.ts +33 -0
  74. package/src/components/content/changelog-element.ts +9 -2
  75. package/src/components/content/mermaid-element.ts +7 -2
  76. package/src/components/islands/AskAI.astro +5 -2
  77. package/src/components/islands/ask-ai.tsx +56 -6
  78. package/src/components/islands/hooks.ts +28 -8
  79. package/src/components/layout/Banner.astro +10 -2
  80. package/src/components/layout/Header.astro +13 -4
  81. package/src/components/layout/Logo.astro +11 -3
  82. package/src/components/layout/NavTree.astro +17 -3
  83. package/src/components/layout/PageActions.astro +25 -10
  84. package/src/components/layout/PageLayout.astro +45 -8
  85. package/src/components/layout/ReferenceLayout.astro +8 -1
  86. package/src/components/layout/RootLayout.astro +67 -9
  87. package/src/components/layout/Search.astro +94 -22
  88. package/src/components/layout/search/algolia.ts +11 -2
  89. package/src/components/layout/search/endpoint.ts +11 -5
  90. package/src/components/layout/search/orama-cloud.ts +8 -2
  91. package/src/components/layout/search/types.ts +5 -1
  92. package/src/components/layout/search/typesense.ts +4 -1
  93. package/src/components/layout/toc-element.ts +1 -1
  94. package/src/components/openapi/ApiTagOperations.astro +2 -1
  95. package/src/components/openapi/Operation.astro +47 -40
  96. package/src/components/openapi/RequestPanel.astro +1 -1
  97. package/src/components/openapi/helpers.ts +71 -3
  98. package/src/components/openapi/panel.ts +1 -1
  99. package/src/core/base-path.ts +24 -0
  100. package/src/core/builtin-tags.ts +2 -0
  101. package/src/core/config-input.ts +37 -2
  102. package/src/core/config.ts +3 -2
  103. package/src/core/data.ts +2 -0
  104. package/src/core/graph.ts +15 -5
  105. package/src/core/i18n-ui.ts +45 -0
  106. package/src/core/last-modified.ts +13 -6
  107. package/src/core/links.ts +32 -8
  108. package/src/core/navigation.ts +29 -4
  109. package/src/core/package-json.ts +17 -2
  110. package/src/core/project-graph.ts +15 -6
  111. package/src/core/schema.ts +36 -2
  112. package/src/core/sources/assets.ts +6 -1
  113. package/src/core/sources/filesystem.ts +4 -0
  114. package/src/core/sources/mdx-remote.ts +23 -14
  115. package/src/core/sources/normalize.ts +152 -50
  116. package/src/core/sources/notion.ts +8 -8
  117. package/src/core/ui-packs/ar.ts +1 -0
  118. package/src/core/ui-packs/bg.ts +1 -0
  119. package/src/core/ui-packs/bn.ts +1 -0
  120. package/src/core/ui-packs/ca.ts +1 -0
  121. package/src/core/ui-packs/cs.ts +1 -0
  122. package/src/core/ui-packs/da.ts +1 -0
  123. package/src/core/ui-packs/de.ts +1 -0
  124. package/src/core/ui-packs/el.ts +1 -0
  125. package/src/core/ui-packs/es.ts +1 -0
  126. package/src/core/ui-packs/fa.ts +1 -0
  127. package/src/core/ui-packs/fi.ts +1 -0
  128. package/src/core/ui-packs/fr.ts +2 -1
  129. package/src/core/ui-packs/he.ts +1 -0
  130. package/src/core/ui-packs/hi.ts +1 -0
  131. package/src/core/ui-packs/hr.ts +1 -0
  132. package/src/core/ui-packs/hu.ts +1 -0
  133. package/src/core/ui-packs/id.ts +1 -0
  134. package/src/core/ui-packs/it.ts +1 -0
  135. package/src/core/ui-packs/ja.ts +1 -0
  136. package/src/core/ui-packs/ko.ts +1 -0
  137. package/src/core/ui-packs/nl.ts +1 -0
  138. package/src/core/ui-packs/no.ts +1 -0
  139. package/src/core/ui-packs/pl.ts +1 -0
  140. package/src/core/ui-packs/pt-br.ts +1 -0
  141. package/src/core/ui-packs/pt.ts +1 -0
  142. package/src/core/ui-packs/ro.ts +1 -0
  143. package/src/core/ui-packs/ru.ts +1 -0
  144. package/src/core/ui-packs/sk.ts +1 -0
  145. package/src/core/ui-packs/sr.ts +1 -0
  146. package/src/core/ui-packs/sv.ts +1 -0
  147. package/src/core/ui-packs/th.ts +1 -0
  148. package/src/core/ui-packs/tr.ts +1 -0
  149. package/src/core/ui-packs/uk.ts +1 -0
  150. package/src/core/ui-packs/vi.ts +1 -0
  151. package/src/core/ui-packs/zh-tw.ts +1 -0
  152. package/src/core/ui-packs/zh.ts +1 -0
  153. package/src/deploy/adapter-output.ts +18 -8
  154. package/src/deploy/redirects.ts +7 -2
  155. package/src/deploy/sitemap.ts +53 -11
  156. package/src/index.ts +5 -0
  157. package/src/markdown/base-links.ts +10 -8
  158. package/src/markdown/index.ts +15 -3
  159. package/src/markdown/inline-code.ts +7 -2
  160. package/src/markdown/package-commands.ts +10 -4
  161. package/src/openapi/model.ts +12 -4
  162. package/src/openapi/parse.ts +21 -0
  163. package/src/openapi/references.ts +38 -8
  164. package/src/openapi/source.ts +59 -10
  165. package/src/registry/eject.ts +184 -12
  166. package/src/registry/registry.ts +0 -3
  167. package/src/search/documents.ts +34 -2
  168. package/src/seo/jsonld.ts +13 -12
@@ -90,50 +90,140 @@ const mapRoute = (
90
90
  return { groups, route, segments };
91
91
  };
92
92
 
93
- const CODE_FENCE = /^```/u;
93
+ // CommonMark allows backtick *and* tilde fences. The scanners track which
94
+ // delimiter opened the current fence (`null` when outside one) so a ``` line
95
+ // inside a ~~~ block is content, not a toggle — see `nextFenceState`.
96
+ const CODE_FENCE = /^(?<delimiter>```|~~~)/u;
97
+
98
+ /** The fence delimiter opening the current code block, or null outside one. */
99
+ type FenceState = "```" | "~~~" | null;
100
+
101
+ /**
102
+ * Advance the fenced-code state for one line: an opening fence records its
103
+ * delimiter, only the matching delimiter closes it, and any other line leaves
104
+ * the state untouched.
105
+ */
106
+ const nextFenceState = (line: string, fence: FenceState): FenceState => {
107
+ const delimiter = line.trimStart().match(CODE_FENCE)?.groups?.delimiter as
108
+ | Exclude<FenceState, null>
109
+ | undefined;
110
+ if (delimiter === undefined) {
111
+ return fence;
112
+ }
113
+ if (fence === null) {
114
+ return delimiter;
115
+ }
116
+ return fence === delimiter ? null : fence;
117
+ };
94
118
  // A closing hash sequence must be preceded by whitespace (CommonMark), so a
95
- // heading like `## What is C#` keeps its trailing `#`.
96
- const ATX_HEADING = /^(?<hashes>#{1,6})\s+(?<text>.+?)(?:\s+#+)?\s*$/u;
119
+ // heading like `## What is C#` keeps its trailing `#`. Up to 3 leading spaces
120
+ // are allowed; 4+ is an indented code block.
121
+ const ATX_HEADING = /^ {0,3}(?<hashes>#{1,6})\s+(?<text>.+?)(?:\s+#+)?\s*$/u;
122
+ // A setext underline: a run of `=` (level 1) or `-` (level 2) alone on a line,
123
+ // up to 3 leading spaces. It only forms a heading directly under paragraph
124
+ // text — see `scanHeadingLine`.
125
+ const SETEXT_UNDERLINE = /^ {0,3}(?<marker>=+|-+)\s*$/u;
126
+ // Lines that end a paragraph without being one (CommonMark): blank lines are
127
+ // checked separately; these cover list items, blockquotes, and thematic
128
+ // breaks, so a `---` after any of them stays a thematic break, not an
129
+ // underline promoting the list/quote text to a heading.
130
+ const PARAGRAPH_INTERRUPT = /^ {0,3}(?:[-+*][ \t]|\d{1,9}[.)][ \t]|>)/u;
131
+ const THEMATIC_BREAK =
132
+ /^ {0,3}(?:(?:-[ \t]*){3,}|(?:\*[ \t]*){3,}|(?:_[ \t]*){3,})$/u;
133
+ const FRONT_MATTER_CLOSE = /^(?:-{3}|\.{3})\s*$/u;
97
134
 
98
135
  /**
99
- * Extract ATX headings from a markdown body, skipping fenced code blocks. Each
100
- * heading's anchor slug comes from a per-document `github-slugger` — the exact
101
- * slugger the renderer uses (`markdown/heading-anchors`)advanced over every
102
- * `#`–`######` in document order. Matching it (rather than a hand-rolled
103
- * slugify) keeps the manifest's anchor ids identical to the rendered ones, so
104
- * `blume validate` stops false-flagging links like `#the-read--write-fallback`
105
- * (a hand slugify collapses `--`; github-slugger keeps it) and resolves repeated
106
- * headings the same way (`setup`, `setup-1`).
136
+ * The body lines, minus a leading front matter block. Bodies from the
137
+ * normalize pipeline are already frontmatter-stripped, but `extractHeadings`
138
+ * also runs on raw documentswhere a leading `---` block (closed by `---` or
139
+ * `...`) is front matter, not a thematic break whose closing `---` would
140
+ * underline the last metadata line into a phantom setext heading.
107
141
  */
108
- /** Scan one line for an ATX heading; returns the next fenced-block state. */
142
+ const linesWithoutFrontMatter = (body: string): string[] => {
143
+ const lines = body.split("\n");
144
+ if (!/^-{3}\s*$/u.test(lines[0] ?? "")) {
145
+ return lines;
146
+ }
147
+ const close = lines.findIndex(
148
+ (line, index) => index > 0 && FRONT_MATTER_CLOSE.test(line)
149
+ );
150
+ return close === -1 ? lines : lines.slice(close + 1);
151
+ };
152
+
153
+ /** Scanner state: the open fence plus the paragraph lines accumulated so far. */
154
+ interface HeadingScanState {
155
+ fence: FenceState;
156
+ /** Consecutive paragraph lines — the candidate text for a setext underline. */
157
+ paragraph: string[];
158
+ }
159
+
160
+ /**
161
+ * Extract ATX and setext headings from a markdown body, skipping fenced code
162
+ * blocks, exactly as the renderer sees them: ATX headings may be indented up
163
+ * to 3 spaces, and a paragraph underlined with `=`/`-` is a level 1/2 setext
164
+ * heading. Each heading's anchor slug comes from a per-document
165
+ * `github-slugger` — the exact slugger the renderer uses
166
+ * (`markdown/heading-anchors`) — advanced over every heading in document
167
+ * order. Matching it (rather than a hand-rolled slugify) keeps the manifest's
168
+ * anchor ids identical to the rendered ones, so `blume validate` stops
169
+ * false-flagging links like `#the-read--write-fallback` (a hand slugify
170
+ * collapses `--`; github-slugger keeps it) and resolves repeated headings the
171
+ * same way (`setup`, `setup-1`).
172
+ */
173
+ /** Scan one line for a heading, advancing the fence/paragraph state. */
109
174
  const scanHeadingLine = (
110
175
  line: string,
111
- inFence: boolean,
176
+ state: HeadingScanState,
112
177
  slugger: GithubSlugger,
113
178
  headings: Heading[]
114
- ): boolean => {
115
- if (CODE_FENCE.test(line.trimStart())) {
116
- return !inFence;
117
- }
118
- if (inFence) {
119
- return inFence;
179
+ ): void => {
180
+ const next = nextFenceState(line, state.fence);
181
+ // Skip fence delimiter lines themselves and anything inside a fence. A fence
182
+ // also ends any open paragraph, so no underline can reach across it.
183
+ if (state.fence !== null || next !== null) {
184
+ state.fence = next;
185
+ state.paragraph = [];
186
+ return;
120
187
  }
121
- const match = line.match(ATX_HEADING);
122
- if (match?.groups) {
123
- const depth = match.groups.hashes?.length ?? 1;
124
- const text = (match.groups.text ?? "").trim();
188
+ const atx = line.match(ATX_HEADING);
189
+ if (atx?.groups) {
190
+ const depth = atx.groups.hashes?.length ?? 1;
191
+ const text = (atx.groups.text ?? "").trim();
125
192
  headings.push({ depth, slug: slugger.slug(text), text });
193
+ state.paragraph = [];
194
+ return;
195
+ }
196
+ const setext = line.match(SETEXT_UNDERLINE);
197
+ if (setext?.groups && state.paragraph.length > 0) {
198
+ // Setext wins over thematic break when it closes a paragraph (CommonMark);
199
+ // a multi-line paragraph renders as one heading, soft breaks as spaces.
200
+ const text = state.paragraph.join(" ").trim();
201
+ headings.push({
202
+ depth: setext.groups.marker?.startsWith("=") ? 1 : 2,
203
+ slug: slugger.slug(text),
204
+ text,
205
+ });
206
+ state.paragraph = [];
207
+ return;
208
+ }
209
+ if (
210
+ line.trim() === "" ||
211
+ THEMATIC_BREAK.test(line) ||
212
+ PARAGRAPH_INTERRUPT.test(line)
213
+ ) {
214
+ state.paragraph = [];
215
+ return;
126
216
  }
127
- return inFence;
217
+ state.paragraph.push(line.trim());
128
218
  };
129
219
 
130
220
  export const extractHeadings = (body: string): Heading[] => {
131
221
  const headings: Heading[] = [];
132
222
  const slugger = new GithubSlugger();
133
- let inFence = false;
223
+ const state: HeadingScanState = { fence: null, paragraph: [] };
134
224
 
135
- for (const line of body.split("\n")) {
136
- inFence = scanHeadingLine(line, inFence, slugger, headings);
225
+ for (const line of linesWithoutFrontMatter(body)) {
226
+ scanHeadingLine(line, state, slugger, headings);
137
227
  }
138
228
 
139
229
  return headings;
@@ -145,19 +235,20 @@ const INLINE_CODE = /`[^`]*`/gu;
145
235
  /**
146
236
  * Extract link targets from a markdown body for later validation, recording the
147
237
  * 1-based line/column of each target. Skips fenced code blocks and inline code.
238
+ * `lineOffset` shifts every recorded line: the body is frontmatter-stripped, so
239
+ * diagnostics that point into the raw file must add the stripped block's height.
148
240
  */
149
241
  /** Scan one line for link targets; returns the next fenced-block state. */
150
242
  const scanLinkLine = (
151
243
  line: string,
152
244
  lineNumber: number,
153
- inFence: boolean,
245
+ fence: FenceState,
154
246
  links: PageLink[]
155
- ): boolean => {
156
- if (CODE_FENCE.test(line.trimStart())) {
157
- return !inFence;
158
- }
159
- if (inFence) {
160
- return inFence;
247
+ ): FenceState => {
248
+ const next = nextFenceState(line, fence);
249
+ // Skip fence delimiter lines themselves and anything inside a fence.
250
+ if (fence !== null || next !== null) {
251
+ return next;
161
252
  }
162
253
  // Blank out inline code spans (`[label](/x)` shown as syntax, not a link)
163
254
  // with same-length padding so recorded columns stay accurate.
@@ -180,17 +271,17 @@ const scanLinkLine = (
180
271
  target,
181
272
  });
182
273
  }
183
- return inFence;
274
+ return next;
184
275
  };
185
276
 
186
- export const extractLinks = (body: string): PageLink[] => {
277
+ export const extractLinks = (body: string, lineOffset = 0): PageLink[] => {
187
278
  const links: PageLink[] = [];
188
- let inFence = false;
189
- let lineNumber = 0;
279
+ let fence: FenceState = null;
280
+ let lineNumber = lineOffset;
190
281
 
191
282
  for (const line of body.split("\n")) {
192
283
  lineNumber += 1;
193
- inFence = scanLinkLine(line, lineNumber, inFence, links);
284
+ fence = scanLinkLine(line, lineNumber, fence, links);
194
285
  }
195
286
 
196
287
  return links;
@@ -212,14 +303,13 @@ const JSX_OPEN = /<(?<tag>[A-Z][A-Za-z0-9]*)/gu;
212
303
  /** Scan one line for JSX component tags; returns the next fenced-block state. */
213
304
  const scanTagLine = (
214
305
  line: string,
215
- inFence: boolean,
306
+ fence: FenceState,
216
307
  tags: Set<string>
217
- ): boolean => {
218
- if (CODE_FENCE.test(line.trimStart())) {
219
- return !inFence;
220
- }
221
- if (inFence) {
222
- return inFence;
308
+ ): FenceState => {
309
+ const next = nextFenceState(line, fence);
310
+ // Skip fence delimiter lines themselves and anything inside a fence.
311
+ if (fence !== null || next !== null) {
312
+ return next;
223
313
  }
224
314
  const clean = line.replaceAll(INLINE_CODE, "").replaceAll(DOUBLE_QUOTED, "");
225
315
  for (const match of clean.matchAll(JSX_OPEN)) {
@@ -228,18 +318,27 @@ const scanTagLine = (
228
318
  tags.add(tag);
229
319
  }
230
320
  }
231
- return inFence;
321
+ return next;
232
322
  };
233
323
 
234
324
  export const extractComponentTags = (body: string): string[] => {
235
325
  const tags = new Set<string>();
236
- let inFence = false;
326
+ let fence: FenceState = null;
237
327
  for (const line of body.split("\n")) {
238
- inFence = scanTagLine(line, inFence, tags);
328
+ fence = scanTagLine(line, fence, tags);
239
329
  }
240
330
  return [...tags];
241
331
  };
242
332
 
333
+ /**
334
+ * Height of the frontmatter block stripped from `raw` to produce `body` (0
335
+ * when the raw text is unknown or nothing was stripped). Link positions are
336
+ * extracted from the stripped body, but diagnostics point into the raw
337
+ * document — recorded lines must shift by this offset to match it.
338
+ */
339
+ const strippedLineOffset = (raw: string | undefined, body: string): number =>
340
+ raw ? Math.max(0, raw.split("\n").length - body.split("\n").length) : 0;
341
+
243
342
  const deriveTitle = (
244
343
  meta: PageMeta,
245
344
  headings: Heading[],
@@ -348,7 +447,10 @@ export const normalizeEntry = (
348
447
  headings,
349
448
  id: `${ctx.source.name}:${entry.ref}`,
350
449
  lastModified: meta.lastModified ?? entry.lastModified,
351
- links: extractLinks(entry.body.text),
450
+ links: extractLinks(
451
+ entry.body.text,
452
+ strippedLineOffset(entry.raw, entry.body.text)
453
+ ),
352
454
  meta,
353
455
  navPath,
354
456
  segments,
@@ -100,10 +100,9 @@ export interface NotionSourceOptions {
100
100
  token?: string;
101
101
  properties?: NotionPropertyMap;
102
102
  /**
103
- * When set, the `Status` property is treated as a publish signal: any value
104
- * other than this maps to `draft: true`. Omit to import every page regardless
105
- * of status (the safe default for databases that use Status for an editorial
106
- * workflow rather than publishing).
103
+ * The `Status` property is treated as a publish signal: any value other than
104
+ * this maps to `draft: true`. Defaults to `Published`; pages without a
105
+ * Status/select property are always imported as published.
107
106
  */
108
107
  publishedValue?: string;
109
108
  /** Opt-in dev polling interval (seconds); omit to freeze for the session. */
@@ -395,12 +394,13 @@ export const notionSource = (
395
394
  };
396
395
 
397
396
  const isDraft = (page: NotionPage): boolean => {
398
- if (!options.publishedValue) {
399
- return false;
400
- }
401
397
  const prop = page.properties[props.status ?? "Status"];
402
398
  const status = prop?.status?.name ?? prop?.select?.name;
403
- return Boolean(status && status !== options.publishedValue);
399
+ // A page without a Status/select property stays published; one with a
400
+ // status is a draft unless it matches the published value.
401
+ return Boolean(
402
+ status && status !== (options.publishedValue ?? "Published")
403
+ );
404
404
  };
405
405
 
406
406
  const orderOf = (page: NotionPage): number | undefined => {
@@ -23,6 +23,7 @@ export const ar: UIStringsOverride = {
23
23
  send: "إرسال",
24
24
  title: "اسأل الذكاء الاصطناعي",
25
25
  },
26
+ banner: { dismiss: "إغلاق الإعلان" },
26
27
  feedback: {
27
28
  no: "لا",
28
29
  question: "هل كانت هذه الصفحة مفيدة؟",
@@ -23,6 +23,7 @@ export const bg: UIStringsOverride = {
23
23
  send: "Изпрати",
24
24
  title: "Попитай ИИ",
25
25
  },
26
+ banner: { dismiss: "Затваряне на съобщението" },
26
27
  feedback: {
27
28
  no: "Не",
28
29
  question: "Беше ли полезна тази страница?",
@@ -23,6 +23,7 @@ export const bn: UIStringsOverride = {
23
23
  send: "পাঠান",
24
24
  title: "AI-কে জিজ্ঞাসা করুন",
25
25
  },
26
+ banner: { dismiss: "ঘোষণা বন্ধ করুন" },
26
27
  feedback: {
27
28
  no: "না",
28
29
  question: "এই পৃষ্ঠাটি কি সহায়ক ছিল?",
@@ -23,6 +23,7 @@ export const ca: UIStringsOverride = {
23
23
  send: "Envia",
24
24
  title: "Pregunta a la IA",
25
25
  },
26
+ banner: { dismiss: "Tanca l'anunci" },
26
27
  feedback: {
27
28
  no: "No",
28
29
  question: "Aquesta pàgina t'ha estat útil?",
@@ -23,6 +23,7 @@ export const cs: UIStringsOverride = {
23
23
  send: "Odeslat",
24
24
  title: "Zeptat se AI",
25
25
  },
26
+ banner: { dismiss: "Zavřít oznámení" },
26
27
  feedback: {
27
28
  no: "Ne",
28
29
  question: "Byla tato stránka užitečná?",
@@ -23,6 +23,7 @@ export const da: UIStringsOverride = {
23
23
  send: "Send",
24
24
  title: "Spørg AI",
25
25
  },
26
+ banner: { dismiss: "Luk meddelelsen" },
26
27
  feedback: {
27
28
  no: "Nej",
28
29
  question: "Var denne side nyttig?",
@@ -23,6 +23,7 @@ export const de: UIStringsOverride = {
23
23
  send: "Senden",
24
24
  title: "KI fragen",
25
25
  },
26
+ banner: { dismiss: "Ankündigung schließen" },
26
27
  feedback: {
27
28
  no: "Nein",
28
29
  question: "War diese Seite hilfreich?",
@@ -23,6 +23,7 @@ export const el: UIStringsOverride = {
23
23
  send: "Αποστολή",
24
24
  title: "Ρωτήστε την AI",
25
25
  },
26
+ banner: { dismiss: "Κλείσιμο ανακοίνωσης" },
26
27
  feedback: {
27
28
  no: "Όχι",
28
29
  question: "Σας φάνηκε χρήσιμη αυτή η σελίδα;",
@@ -23,6 +23,7 @@ export const es: UIStringsOverride = {
23
23
  send: "Enviar",
24
24
  title: "Preguntar a la IA",
25
25
  },
26
+ banner: { dismiss: "Cerrar el anuncio" },
26
27
  feedback: {
27
28
  no: "No",
28
29
  question: "¿Te ha resultado útil esta página?",
@@ -23,6 +23,7 @@ export const fa: UIStringsOverride = {
23
23
  send: "ارسال",
24
24
  title: "از هوش مصنوعی بپرسید",
25
25
  },
26
+ banner: { dismiss: "بستن اطلاعیه" },
26
27
  feedback: {
27
28
  no: "خیر",
28
29
  question: "آیا این صفحه مفید بود؟",
@@ -23,6 +23,7 @@ export const fi: UIStringsOverride = {
23
23
  send: "Lähetä",
24
24
  title: "Kysy tekoälyltä",
25
25
  },
26
+ banner: { dismiss: "Sulje ilmoitus" },
26
27
  feedback: {
27
28
  no: "Ei",
28
29
  question: "Oliko tästä sivusta apua?",
@@ -5,7 +5,7 @@ export const fr: UIStringsOverride = {
5
5
  actions: {
6
6
  addToCursor: "Ajouter à Cursor",
7
7
  addToVscode: "Ajouter à VS Code",
8
- askAI: "Demander à l'IA",
8
+ askAI: "Demander à l'IA à propos de cette page",
9
9
  connectMcp: "Se connecter à MCP",
10
10
  copied: "Copié !",
11
11
  copyClaudeCode: "Copier la commande Claude Code",
@@ -23,6 +23,7 @@ export const fr: UIStringsOverride = {
23
23
  send: "Envoyer",
24
24
  title: "Demander à l'IA",
25
25
  },
26
+ banner: { dismiss: "Fermer l'annonce" },
26
27
  feedback: {
27
28
  no: "Non",
28
29
  question: "Cette page vous a-t-elle été utile ?",
@@ -23,6 +23,7 @@ export const he: UIStringsOverride = {
23
23
  send: "שלח",
24
24
  title: "שאל את ה-AI",
25
25
  },
26
+ banner: { dismiss: "סגירת ההודעה" },
26
27
  feedback: {
27
28
  no: "לא",
28
29
  question: "האם העמוד הזה היה מועיל?",
@@ -23,6 +23,7 @@ export const hi: UIStringsOverride = {
23
23
  send: "भेजें",
24
24
  title: "AI से पूछें",
25
25
  },
26
+ banner: { dismiss: "घोषणा बंद करें" },
26
27
  feedback: {
27
28
  no: "नहीं",
28
29
  question: "क्या यह पेज सहायक था?",
@@ -23,6 +23,7 @@ export const hr: UIStringsOverride = {
23
23
  send: "Pošalji",
24
24
  title: "Pitaj AI",
25
25
  },
26
+ banner: { dismiss: "Zatvori obavijest" },
26
27
  feedback: {
27
28
  no: "Ne",
28
29
  question: "Je li vam ova stranica bila korisna?",
@@ -23,6 +23,7 @@ export const hu: UIStringsOverride = {
23
23
  send: "Küldés",
24
24
  title: "Kérdezd az AI-t",
25
25
  },
26
+ banner: { dismiss: "Közlemény bezárása" },
26
27
  feedback: {
27
28
  no: "Nem",
28
29
  question: "Hasznos volt ez az oldal?",
@@ -23,6 +23,7 @@ export const id: UIStringsOverride = {
23
23
  send: "Kirim",
24
24
  title: "Tanya AI",
25
25
  },
26
+ banner: { dismiss: "Tutup pengumuman" },
26
27
  feedback: {
27
28
  no: "Tidak",
28
29
  question: "Apakah halaman ini membantu?",
@@ -23,6 +23,7 @@ export const it: UIStringsOverride = {
23
23
  send: "Invia",
24
24
  title: "Chiedi all'IA",
25
25
  },
26
+ banner: { dismiss: "Chiudi l'annuncio" },
26
27
  feedback: {
27
28
  no: "No",
28
29
  question: "Questa pagina ti è stata utile?",
@@ -23,6 +23,7 @@ export const ja: UIStringsOverride = {
23
23
  send: "送信",
24
24
  title: "AI に質問",
25
25
  },
26
+ banner: { dismiss: "お知らせを閉じる" },
26
27
  feedback: {
27
28
  no: "いいえ",
28
29
  question: "このページは役に立ちましたか?",
@@ -23,6 +23,7 @@ export const ko: UIStringsOverride = {
23
23
  send: "보내기",
24
24
  title: "AI에게 질문",
25
25
  },
26
+ banner: { dismiss: "공지 닫기" },
26
27
  feedback: {
27
28
  no: "아니요",
28
29
  question: "이 페이지가 도움이 되었나요?",
@@ -23,6 +23,7 @@ export const nl: UIStringsOverride = {
23
23
  send: "Verzenden",
24
24
  title: "AI vragen",
25
25
  },
26
+ banner: { dismiss: "Aankondiging sluiten" },
26
27
  feedback: {
27
28
  no: "Nee",
28
29
  question: "Was deze pagina nuttig?",
@@ -23,6 +23,7 @@ export const no: UIStringsOverride = {
23
23
  send: "Send",
24
24
  title: "Spør AI",
25
25
  },
26
+ banner: { dismiss: "Lukk kunngjøringen" },
26
27
  feedback: {
27
28
  no: "Nei",
28
29
  question: "Var denne siden nyttig?",
@@ -23,6 +23,7 @@ export const pl: UIStringsOverride = {
23
23
  send: "Wyślij",
24
24
  title: "Zapytaj AI",
25
25
  },
26
+ banner: { dismiss: "Zamknij ogłoszenie" },
26
27
  feedback: {
27
28
  no: "Nie",
28
29
  question: "Czy ta strona była pomocna?",
@@ -23,6 +23,7 @@ export const ptBR: UIStringsOverride = {
23
23
  send: "Enviar",
24
24
  title: "Perguntar à IA",
25
25
  },
26
+ banner: { dismiss: "Fechar o anúncio" },
26
27
  feedback: {
27
28
  no: "Não",
28
29
  question: "Esta página foi útil?",
@@ -23,6 +23,7 @@ export const pt: UIStringsOverride = {
23
23
  send: "Enviar",
24
24
  title: "Perguntar à IA",
25
25
  },
26
+ banner: { dismiss: "Fechar o anúncio" },
26
27
  feedback: {
27
28
  no: "Não",
28
29
  question: "Esta página foi útil?",
@@ -23,6 +23,7 @@ export const ro: UIStringsOverride = {
23
23
  send: "Trimite",
24
24
  title: "Întreabă AI",
25
25
  },
26
+ banner: { dismiss: "Închide anunțul" },
26
27
  feedback: {
27
28
  no: "Nu",
28
29
  question: "Ți-a fost utilă această pagină?",
@@ -23,6 +23,7 @@ export const ru: UIStringsOverride = {
23
23
  send: "Отправить",
24
24
  title: "Спросить ИИ",
25
25
  },
26
+ banner: { dismiss: "Закрыть объявление" },
26
27
  feedback: {
27
28
  no: "Нет",
28
29
  question: "Эта страница была полезной?",
@@ -23,6 +23,7 @@ export const sk: UIStringsOverride = {
23
23
  send: "Odoslať",
24
24
  title: "Opýtať sa AI",
25
25
  },
26
+ banner: { dismiss: "Zavrieť oznámenie" },
26
27
  feedback: {
27
28
  no: "Nie",
28
29
  question: "Bola táto stránka užitočná?",
@@ -23,6 +23,7 @@ export const sr: UIStringsOverride = {
23
23
  send: "Пошаљи",
24
24
  title: "Питајте AI",
25
25
  },
26
+ banner: { dismiss: "Затвори обавештење" },
26
27
  feedback: {
27
28
  no: "Не",
28
29
  question: "Да ли вам је ова страница помогла?",
@@ -23,6 +23,7 @@ export const sv: UIStringsOverride = {
23
23
  send: "Skicka",
24
24
  title: "Fråga AI",
25
25
  },
26
+ banner: { dismiss: "Stäng meddelandet" },
26
27
  feedback: {
27
28
  no: "Nej",
28
29
  question: "Var den här sidan till hjälp?",
@@ -23,6 +23,7 @@ export const th: UIStringsOverride = {
23
23
  send: "ส่ง",
24
24
  title: "ถาม AI",
25
25
  },
26
+ banner: { dismiss: "ปิดประกาศ" },
26
27
  feedback: {
27
28
  no: "ไม่",
28
29
  question: "หน้านี้มีประโยชน์หรือไม่?",
@@ -23,6 +23,7 @@ export const tr: UIStringsOverride = {
23
23
  send: "Gönder",
24
24
  title: "Yapay zekâya sor",
25
25
  },
26
+ banner: { dismiss: "Duyuruyu kapat" },
26
27
  feedback: {
27
28
  no: "Hayır",
28
29
  question: "Bu sayfa yardımcı oldu mu?",
@@ -23,6 +23,7 @@ export const uk: UIStringsOverride = {
23
23
  send: "Надіслати",
24
24
  title: "Запитати ШІ",
25
25
  },
26
+ banner: { dismiss: "Закрити оголошення" },
26
27
  feedback: {
27
28
  no: "Ні",
28
29
  question: "Чи була ця сторінка корисною?",
@@ -23,6 +23,7 @@ export const vi: UIStringsOverride = {
23
23
  send: "Gửi",
24
24
  title: "Hỏi AI",
25
25
  },
26
+ banner: { dismiss: "Đóng thông báo" },
26
27
  feedback: {
27
28
  no: "Không",
28
29
  question: "Trang này có hữu ích không?",
@@ -23,6 +23,7 @@ export const zhTW: UIStringsOverride = {
23
23
  send: "傳送",
24
24
  title: "向 AI 提問",
25
25
  },
26
+ banner: { dismiss: "關閉公告" },
26
27
  feedback: {
27
28
  no: "沒有幫助",
28
29
  question: "這個頁面有幫助嗎?",
@@ -23,6 +23,7 @@ export const zh: UIStringsOverride = {
23
23
  send: "发送",
24
24
  title: "向 AI 提问",
25
25
  },
26
+ banner: { dismiss: "关闭公告" },
26
27
  feedback: {
27
28
  no: "没有帮助",
28
29
  question: "这个页面有帮助吗?",