@roomi-fields/notebooklm-mcp 1.5.0 → 1.5.1

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 (161) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +2 -0
  3. package/deployment/INDEX.md +292 -0
  4. package/deployment/PACKAGE-FILES.txt +180 -0
  5. package/deployment/QUICK-START.md +100 -0
  6. package/deployment/docs/01-INSTALL.md +611 -0
  7. package/deployment/docs/02-CONFIGURATION.md +404 -0
  8. package/deployment/docs/03-API.md +1691 -0
  9. package/deployment/docs/04-N8N-INTEGRATION.md +373 -0
  10. package/deployment/docs/05-TROUBLESHOOTING.md +429 -0
  11. package/deployment/docs/06-NOTEBOOK-LIBRARY.md +692 -0
  12. package/deployment/docs/07-AUTO-DISCOVERY.md +236 -0
  13. package/deployment/docs/08-WSL-USAGE.md +363 -0
  14. package/deployment/docs/09-MULTI-INTERFACE.md +293 -0
  15. package/deployment/docs/10-CONTENT-MANAGEMENT.md +421 -0
  16. package/deployment/docs/11-MULTI-ACCOUNT.md +295 -0
  17. package/deployment/docs/README.md +207 -0
  18. package/deployment/scripts/README.md +564 -0
  19. package/deployment/scripts/install.ps1 +114 -0
  20. package/deployment/scripts/setup-auth.ps1 +217 -0
  21. package/deployment/scripts/start-server.ps1 +72 -0
  22. package/deployment/scripts/stop-server.ps1 +51 -0
  23. package/deployment/scripts/test-api.ps1 +651 -0
  24. package/deployment/scripts/test-auth.ps1 +323 -0
  25. package/deployment/scripts/test-auto-discovery.ps1 +295 -0
  26. package/deployment/scripts/test-cors.ps1 +398 -0
  27. package/deployment/scripts/test-errors.ps1 +581 -0
  28. package/deployment/scripts/test-server.ps1 +140 -0
  29. package/deployment/scripts/test-sessions.ps1 +426 -0
  30. package/deployment/scripts/test-validation.ps1 +299 -0
  31. package/dist/cli/accounts.js.map +1 -1
  32. package/dist/config.d.ts +1 -0
  33. package/dist/config.d.ts.map +1 -1
  34. package/dist/config.js +15 -0
  35. package/dist/config.js.map +1 -1
  36. package/dist/content/content-manager.d.ts.map +1 -1
  37. package/dist/content/content-manager.js +113 -118
  38. package/dist/content/content-manager.js.map +1 -1
  39. package/dist/i18n/en.json +120 -0
  40. package/dist/i18n/fr.json +120 -0
  41. package/dist/i18n/index.d.ts +168 -0
  42. package/dist/i18n/index.d.ts.map +1 -0
  43. package/dist/i18n/index.js +213 -0
  44. package/dist/i18n/index.js.map +1 -0
  45. package/dist/session/browser-session.d.ts.map +1 -1
  46. package/dist/session/browser-session.js +1 -0
  47. package/dist/session/browser-session.js.map +1 -1
  48. package/dist/session/shared-context-manager.d.ts.map +1 -1
  49. package/dist/session/shared-context-manager.js +2 -0
  50. package/dist/session/shared-context-manager.js.map +1 -1
  51. package/docs/ADDING_A_LANGUAGE.md +209 -0
  52. package/package.json +6 -3
  53. package/scripts/archive/add-and-activate-notebook.ps1 +31 -0
  54. package/scripts/archive/add-new-notebook.ps1 +25 -0
  55. package/scripts/archive/add-rom1pey.ps1 +2 -0
  56. package/scripts/archive/add-rpmonster.ps1 +2 -0
  57. package/scripts/archive/add-source-debug.ps1 +11 -0
  58. package/scripts/archive/add-source-e2e.ps1 +28 -0
  59. package/scripts/archive/add-source-visible.ps1 +11 -0
  60. package/scripts/archive/add-test-notebook.ps1 +13 -0
  61. package/scripts/archive/add-test-source.ps1 +50 -0
  62. package/scripts/archive/capture-screen.ps1 +11 -0
  63. package/scripts/archive/change-language.mjs +45 -0
  64. package/scripts/archive/change-language.ts +44 -0
  65. package/scripts/archive/check-account.ps1 +19 -0
  66. package/scripts/archive/check-notebook-2.ps1 +8 -0
  67. package/scripts/archive/check-test-notebook.ps1 +11 -0
  68. package/scripts/archive/create-notebook-auto.ps1 +31 -0
  69. package/scripts/archive/create-notebook.ps1 +8 -0
  70. package/scripts/archive/create-rom1pey-notebook.ps1 +19 -0
  71. package/scripts/archive/create-rom1pey.ps1 +8 -0
  72. package/scripts/archive/create-test-notebook-fresh.ps1 +21 -0
  73. package/scripts/archive/create-test-notebook.ps1 +16 -0
  74. package/scripts/archive/debug-add-source-auto.ps1 +29 -0
  75. package/scripts/archive/debug-add-source.ps1 +19 -0
  76. package/scripts/archive/debug-add-text-source.ps1 +47 -0
  77. package/scripts/archive/debug-home.ps1 +10 -0
  78. package/scripts/archive/debug-selectors.ps1 +55 -0
  79. package/scripts/archive/debug-sources-panel.ps1 +22 -0
  80. package/scripts/archive/debug-ui.ps1 +17 -0
  81. package/scripts/archive/discover-home.ps1 +26 -0
  82. package/scripts/archive/kill-automation-chrome.ps1 +37 -0
  83. package/scripts/archive/list-my-notebooks.ps1 +27 -0
  84. package/scripts/archive/navigate-home-visible.ps1 +23 -0
  85. package/scripts/archive/navigate-home.ps1 +15 -0
  86. package/scripts/archive/run-e2e-english.ps1 +111 -0
  87. package/scripts/archive/run-e2e-rom1pey-v2.ps1 +122 -0
  88. package/scripts/archive/run-e2e-rom1pey.ps1 +117 -0
  89. package/scripts/archive/setup-english-test.ps1 +36 -0
  90. package/scripts/archive/setup-test-notebook.ps1 +71 -0
  91. package/scripts/archive/simple-add-source.ps1 +14 -0
  92. package/scripts/archive/t10.ps1 +2 -0
  93. package/scripts/archive/t20.ps1 +4 -0
  94. package/scripts/archive/t30.ps1 +9 -0
  95. package/scripts/archive/t31.ps1 +11 -0
  96. package/scripts/archive/t32.ps1 +6 -0
  97. package/scripts/archive/t39.ps1 +5 -0
  98. package/scripts/archive/t40.ps1 +5 -0
  99. package/scripts/archive/t53.ps1 +12 -0
  100. package/scripts/archive/t54.ps1 +12 -0
  101. package/scripts/archive/t55.ps1 +11 -0
  102. package/scripts/archive/t9.ps1 +1 -0
  103. package/scripts/archive/test-access.ps1 +28 -0
  104. package/scripts/archive/test-add-delete-source.ps1 +64 -0
  105. package/scripts/archive/test-add-source-visible.ps1 +16 -0
  106. package/scripts/archive/test-add-source.ps1 +19 -0
  107. package/scripts/archive/test-add-text-debug.ps1 +28 -0
  108. package/scripts/archive/test-add-text-source.ps1 +8 -0
  109. package/scripts/archive/test-add-url-source.ps1 +7 -0
  110. package/scripts/archive/test-ask-ascii.ps1 +20 -0
  111. package/scripts/archive/test-ask-cnv.ps1 +20 -0
  112. package/scripts/archive/test-ask-headed.ps1 +51 -0
  113. package/scripts/archive/test-ask-ifs.ps1 +16 -0
  114. package/scripts/archive/test-ask-now.ps1 +24 -0
  115. package/scripts/archive/test-ask-real.ps1 +19 -0
  116. package/scripts/archive/test-ask-visible.ps1 +20 -0
  117. package/scripts/archive/test-create-notebook.ps1 +8 -0
  118. package/scripts/archive/test-create-then-add.ps1 +17 -0
  119. package/scripts/archive/test-delete-source.ps1 +41 -0
  120. package/scripts/archive/test-e2e-notebook.ps1 +21 -0
  121. package/scripts/archive/test-english-notebook.ps1 +20 -0
  122. package/scripts/archive/test-english.ps1 +7 -0
  123. package/scripts/archive/test-full-custom-instructions.ps1 +40 -0
  124. package/scripts/archive/test-full-infographic.ps1 +34 -0
  125. package/scripts/archive/test-full-language.ps1 +21 -0
  126. package/scripts/archive/test-full-presentation.ps1 +85 -0
  127. package/scripts/archive/test-full-report.ps1 +34 -0
  128. package/scripts/archive/test-full-source-selection.ps1 +35 -0
  129. package/scripts/archive/test-full-video-brief.ps1 +22 -0
  130. package/scripts/archive/test-full-video-explainer.ps1 +22 -0
  131. package/scripts/archive/test-full-video-styles.ps1 +37 -0
  132. package/scripts/archive/test-generate-report.ps1 +15 -0
  133. package/scripts/archive/test-generate-study-guide.ps1 +11 -0
  134. package/scripts/archive/test-headed-ask.ps1 +13 -0
  135. package/scripts/archive/test-headed-now.ps1 +9 -0
  136. package/scripts/archive/test-headed.ps1 +9 -0
  137. package/scripts/archive/test-hello.ps1 +7 -0
  138. package/scripts/archive/test-i18n-studio.ps1 +8 -0
  139. package/scripts/archive/test-i18n.ps1 +7 -0
  140. package/scripts/archive/test-manual-headed.ps1 +26 -0
  141. package/scripts/archive/test-mathieu-quota.ps1 +8 -0
  142. package/scripts/archive/test-notebook-1.ps1 +10 -0
  143. package/scripts/archive/test-notebook-2-sources.ps1 +12 -0
  144. package/scripts/archive/test-notebook1.ps1 +14 -0
  145. package/scripts/archive/test-personal-notebook.ps1 +14 -0
  146. package/scripts/archive/test-rate-limit.ps1 +19 -0
  147. package/scripts/archive/test-real-ask.ps1 +50 -0
  148. package/scripts/archive/test-real-ask2.ps1 +30 -0
  149. package/scripts/archive/test-rom1pey.ps1 +7 -0
  150. package/scripts/archive/test-rotation-complete.ps1 +14 -0
  151. package/scripts/archive/test-rotation.ps1 +8 -0
  152. package/scripts/archive/test-show-browser.ps1 +39 -0
  153. package/scripts/archive/test-update-notebook.ps1 +4 -0
  154. package/scripts/archive/verify-language-slow.ps1 +21 -0
  155. package/scripts/archive/verify-language.ps1 +15 -0
  156. package/scripts/check-server.ps1 +46 -0
  157. package/scripts/mcp-wsl-helper.sh +146 -0
  158. package/scripts/start-server.ps1 +94 -0
  159. package/scripts/stop-server.ps1 +30 -0
  160. package/scripts/switch-account-language.sh +191 -0
  161. package/scripts/test-account.ps1 +58 -0
@@ -14,6 +14,20 @@ import { randomDelay, realisticClick, humanType } from '../utils/stealth-utils.j
14
14
  import { log } from '../utils/logger.js';
15
15
  import { CONFIG } from '../config.js';
16
16
  import { waitForLatestAnswer, snapshotAllResponses, isErrorMessage } from '../utils/page-utils.js';
17
+ import { setLocale, tAll } from '../i18n/index.js';
18
+ // Initialize i18n with configured locale
19
+ setLocale(CONFIG.uiLocale);
20
+ /**
21
+ * Build selectors for all supported locales
22
+ * @param template Selector template with {text} placeholder
23
+ * @param category i18n category (e.g., 'tabs', 'buttons')
24
+ * @param key i18n key within the category
25
+ * @returns Array of selectors for all locales
26
+ */
27
+ function i18nSelectors(template, category, key) {
28
+ const texts = tAll(category, key);
29
+ return texts.map((text) => template.replace('{text}', text));
30
+ }
17
31
  import { ContentGenerator } from './content-generator.js';
18
32
  // Note: UI selectors are defined inline in methods for better maintainability
19
33
  // as NotebookLM's UI may change frequently
@@ -92,9 +106,8 @@ export class ContentManager {
92
106
  // Icon button with "add" icon specifically
93
107
  'button:has(mat-icon:has-text("add"))',
94
108
  'button:has(mat-icon:has-text("add_circle"))',
95
- // Text-based patterns
96
- 'button:has-text("Add source")',
97
- 'button:has-text("Ajouter une source")',
109
+ // Text-based patterns (bilingual via i18n)
110
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'addSource'),
98
111
  // FAB buttons (floating action button for adding)
99
112
  'button.mat-fab',
100
113
  'button.mat-mini-fab',
@@ -156,10 +169,10 @@ export class ContentManager {
156
169
  /* no dialog */
157
170
  }
158
171
  const sourcesTabSelectors = [
159
- // NotebookLM current UI (Dec 2024) - MDC tabs
160
- 'div.mdc-tab:has-text("Sources")',
161
- '.mat-mdc-tab:has-text("Sources")',
162
- '[role="tab"]:has-text("Sources")',
172
+ // NotebookLM current UI (Dec 2024) - MDC tabs (bilingual FR/EN via i18n)
173
+ ...i18nSelectors('div.mdc-tab:has-text("{text}")', 'tabs', 'sources'),
174
+ ...i18nSelectors('.mat-mdc-tab:has-text("{text}")', 'tabs', 'sources'),
175
+ ...i18nSelectors('[role="tab"]:has-text("{text}")', 'tabs', 'sources'),
163
176
  // First tab in the tab list (Sources is typically first)
164
177
  '.mat-mdc-tab-list .mdc-tab:first-child',
165
178
  ];
@@ -281,11 +294,10 @@ export class ContentManager {
281
294
  }
282
295
  log.info(` 📁 Uploading file: ${path.basename(resolvedPath)}`);
283
296
  try {
284
- // Click on file upload option
297
+ // Click on file upload option (bilingual via i18n)
285
298
  const fileTypeSelectors = [
286
- 'button:has-text("Upload files")',
287
- 'button:has-text("Importer des fichiers")',
288
- 'button:has-text("Upload")',
299
+ ...i18nSelectors('button:has-text("{text}")', 'sourceTypes', 'uploadFiles'),
300
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'upload'),
289
301
  '[data-type="file"]',
290
302
  ];
291
303
  for (const selector of fileTypeSelectors) {
@@ -333,13 +345,11 @@ export class ContentManager {
333
345
  }
334
346
  log.info(` 🌐 Adding URL: ${input.url}`);
335
347
  try {
336
- // Click on URL/Website option
348
+ // Click on URL/Website option (bilingual selectors)
337
349
  const urlTypeSelectors = [
338
- 'button:has-text("Website")',
339
- 'button:has-text("Site web")',
340
- 'button:has-text("Link")',
341
- 'button:has-text("URL")',
342
- 'button:has-text("Web")',
350
+ ...i18nSelectors('button:has-text("{text}")', 'sourceTypes', 'website'),
351
+ ...i18nSelectors('button:has-text("{text}")', 'sourceTypes', 'link'),
352
+ ...i18nSelectors('button:has-text("{text}")', 'sourceTypes', 'url'),
343
353
  '[data-type="url"]',
344
354
  '[aria-label*="website"]',
345
355
  '[aria-label*="URL"]',
@@ -363,36 +373,46 @@ export class ContentManager {
363
373
  }
364
374
  if (!foundUrlOption) {
365
375
  log.info(` ℹ️ No URL option button found, looking for input directly`);
376
+ // DEBUG: List all visible buttons in the page
377
+ try {
378
+ const buttons = await this.page.locator('button').all();
379
+ log.info(` 🔍 DEBUG: Found ${buttons.length} buttons total`);
380
+ for (let i = 0; i < Math.min(buttons.length, 15); i++) {
381
+ const btn = buttons[i];
382
+ const visible = await btn.isVisible().catch(() => false);
383
+ if (visible) {
384
+ const text = await btn.textContent().catch(() => '');
385
+ const ariaLabel = await btn.getAttribute('aria-label').catch(() => '');
386
+ log.info(` 🔍 Button[${i}]: text="${text?.trim()}", aria="${ariaLabel}"`);
387
+ }
388
+ }
389
+ }
390
+ catch (e) {
391
+ log.warning(` ⚠️ Could not list buttons: ${e}`);
392
+ }
366
393
  }
367
394
  // Wait for input to appear after clicking option
368
395
  await randomDelay(500, 1000);
369
- // Find URL input (can be input OR textarea)
396
+ // Find URL input (can be input OR textarea) - bilingual selectors
370
397
  log.info(` 🔍 Looking for URL input...`);
371
398
  const urlInputSelectors = [
372
- // French placeholders - input AND textarea
373
- 'input[placeholder*="Collez"]',
374
- 'textarea[placeholder*="Collez"]',
375
- 'input[placeholder*="liens"]',
376
- 'textarea[placeholder*="liens"]',
377
- // English placeholders
399
+ // i18n placeholder selectors
400
+ ...i18nSelectors('input[placeholder*="{text}"]', 'placeholders', 'pasteUrl'),
401
+ ...i18nSelectors('textarea[placeholder*="{text}"]', 'placeholders', 'pasteUrl'),
402
+ ...i18nSelectors('input[placeholder*="{text}"]', 'placeholders', 'enterUrl'),
403
+ ...i18nSelectors('textarea[placeholder*="{text}"]', 'placeholders', 'enterUrl'),
404
+ ...i18nSelectors('input[placeholder*="{text}"]', 'placeholders', 'pasteLinks'),
405
+ ...i18nSelectors('textarea[placeholder*="{text}"]', 'placeholders', 'pasteLinks'),
406
+ // URL/http generic selectors (work in both languages)
378
407
  'input[placeholder*="URL"]',
379
408
  'textarea[placeholder*="URL"]',
380
409
  'input[placeholder*="url"]',
381
410
  'textarea[placeholder*="url"]',
382
411
  'input[placeholder*="http"]',
383
412
  'textarea[placeholder*="http"]',
384
- 'input[placeholder*="Paste"]',
385
- 'textarea[placeholder*="Paste"]',
386
- 'input[placeholder*="Enter"]',
387
- 'textarea[placeholder*="Enter"]',
388
- 'input[placeholder*="Coller"]',
389
- 'textarea[placeholder*="Coller"]',
390
- 'input[placeholder*="link"]',
391
- 'textarea[placeholder*="link"]',
392
- 'input[placeholder*="Link"]',
393
- 'textarea[placeholder*="Link"]',
394
413
  'input[name="url"]',
395
414
  'input[type="url"]',
415
+ // Fallback dialog selectors
396
416
  '[role="dialog"] input[type="text"]',
397
417
  '[role="dialog"] input:not([type="hidden"])',
398
418
  '[role="dialog"] textarea',
@@ -491,18 +511,13 @@ export class ContentManager {
491
511
  }
492
512
  log.info(` 📝 Adding text content (${input.text.length} chars)`);
493
513
  try {
494
- // Click on paste text option
495
- // NotebookLM dialog shows: "Coller du texte" (French) with "Texte copié" span inside a clickable element
514
+ // Click on paste text option (bilingual FR/EN via i18n)
496
515
  const textTypeSelectors = [
497
- // French UI (current NotebookLM Dec 2024) - span element
498
- 'span:has-text("Texte copié")',
499
- ':has-text("Texte copié")',
516
+ // Span element with pasted text label
517
+ ...i18nSelectors('span:has-text("{text}")', 'sourceTypes', 'pastedText'),
518
+ ...i18nSelectors(':has-text("{text}")', 'sourceTypes', 'pastedText'),
500
519
  // Parent of the span (clickable area)
501
- '*:has(> span:has-text("Texte copié"))',
502
- // English UI
503
- 'span:has-text("Copied text")',
504
- ':has-text("Copied text")',
505
- '*:has(> span:has-text("Copied text"))',
520
+ ...i18nSelectors('*:has(> span:has-text("{text}"))', 'sourceTypes', 'pastedText'),
506
521
  // Generic fallbacks
507
522
  'span:has-text("Paste text")',
508
523
  ':has-text("Paste text")',
@@ -649,9 +664,9 @@ export class ContentManager {
649
664
  catch (e) {
650
665
  log.warning(` ⚠️ Could not take debug screenshot: ${e}`);
651
666
  }
652
- // DEBUG: Check if the "Insérer" button is enabled
667
+ // DEBUG: Check if the "Insert" button is enabled (bilingual via i18n)
653
668
  try {
654
- const insertBtnSelectors = ['button:has-text("Insérer")', 'button:has-text("Insert")'];
669
+ const insertBtnSelectors = i18nSelectors('button:has-text("{text}")', 'buttons', 'insert');
655
670
  for (const sel of insertBtnSelectors) {
656
671
  const btn = this.page.locator(sel).first();
657
672
  if (await btn.isVisible({ timeout: 500 })) {
@@ -814,16 +829,14 @@ export class ContentManager {
814
829
  */
815
830
  async clickUploadButton() {
816
831
  const uploadBtnSelectors = [
817
- // Primary action buttons (most likely)
818
- 'button.mdc-button--raised:has-text("Insert")',
819
- 'button.mat-flat-button:has-text("Insert")',
820
- 'button[color="primary"]:has-text("Insert")',
821
- // Generic text patterns
822
- 'button:has-text("Insert")',
823
- 'button:has-text("Insérer")',
824
- 'button:has-text("Add")',
825
- 'button:has-text("Ajouter")',
826
- 'button:has-text("Upload")',
832
+ // Primary action buttons (most likely) - bilingual via i18n
833
+ ...i18nSelectors('button.mdc-button--raised:has-text("{text}")', 'buttons', 'insert'),
834
+ ...i18nSelectors('button.mat-flat-button:has-text("{text}")', 'buttons', 'insert'),
835
+ ...i18nSelectors('button[color="primary"]:has-text("{text}")', 'buttons', 'insert'),
836
+ // Generic text patterns (bilingual via i18n)
837
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'insert'),
838
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'add'),
839
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'upload'),
827
840
  'button:has-text("Import")',
828
841
  'button:has-text("Save")',
829
842
  'button:has-text("Submit")',
@@ -970,24 +983,22 @@ export class ContentManager {
970
983
  await randomDelay(1000, 2000);
971
984
  // METHOD 1: Look for pasted text source in the SOURCES PANEL specifically (not anywhere on page)
972
985
  // Use more specific selectors to avoid matching dialog content
973
- // Support both French ("Texte collé") and English ("Pasted text") UI
986
+ // Support both French ("Texte collé") and English ("Pasted text") UI via i18n
974
987
  const pastedTextSelectors = [
975
- // Sources panel specific selectors - French
976
- 'mat-checkbox:has-text("Texte collé")',
977
- '[class*="source"]:has-text("Texte collé")',
978
- ':has-text("Texte collé"):not([role="dialog"])',
979
- // Sources panel specific selectors - English
980
- 'mat-checkbox:has-text("Pasted text")',
981
- '[class*="source"]:has-text("Pasted text")',
982
- ':has-text("Pasted text"):not([role="dialog"])',
988
+ // Sources panel specific selectors (bilingual via i18n)
989
+ ...i18nSelectors('mat-checkbox:has-text("{text}")', 'sourceNames', 'pastedText'),
990
+ ...i18nSelectors('[class*="source"]:has-text("{text}")', 'sourceNames', 'pastedText'),
991
+ ...i18nSelectors(':has-text("{text}"):not([role="dialog"])', 'sourceNames', 'pastedText'),
983
992
  ];
993
+ // Get localized pasted text names for detection
994
+ const pastedTextNames = tAll('sourceNames', 'pastedText');
984
995
  for (const selector of pastedTextSelectors) {
985
996
  try {
986
997
  const el = this.page.locator(selector).first();
987
998
  if (await el.isVisible({ timeout: 1000 })) {
988
999
  log.success(` ✅ Found pasted text source: ${selector}`);
989
- // Detect source name from selector (FR: "Texte collé", EN: "Pasted text")
990
- const detectedName = selector.includes('Pasted text') ? 'Pasted text' : 'Texte collé';
1000
+ // Detect source name from selector - find which locale's text is in the selector
1001
+ const detectedName = pastedTextNames.find((name) => selector.includes(name)) || pastedTextNames[0];
991
1002
  return { success: true, sourceName: detectedName, status: 'ready' };
992
1003
  }
993
1004
  }
@@ -1033,7 +1044,8 @@ export class ContentManager {
1033
1044
  const el = this.page.locator(selector).first();
1034
1045
  if (await el.isVisible({ timeout: 1000 })) {
1035
1046
  log.success(` ✅ Found pasted text source after wait: ${selector}`);
1036
- const detectedName = selector.includes('Pasted text') ? 'Pasted text' : 'Texte collé';
1047
+ // Detect source name from selector - find which locale's text is in the selector
1048
+ const detectedName = pastedTextNames.find((name) => selector.includes(name)) || pastedTextNames[0];
1037
1049
  return { success: true, sourceName: detectedName, status: 'ready' };
1038
1050
  }
1039
1051
  }
@@ -1650,10 +1662,11 @@ export class ContentManager {
1650
1662
  // The tabs are: Sources | Discussion | Studio
1651
1663
  // Tab class: mdc-tab mat-mdc-tab mat-focus-indicator
1652
1664
  const studioSelectors = [
1653
- 'div.mdc-tab:has-text("Studio")', // Material Design tab with text
1654
- '.mat-mdc-tab:has-text("Studio")', // Angular Material tab
1655
- '[role="tab"]:has-text("Studio")', // Tab role with Studio text
1656
- 'div.mdc-tab >> text=Studio', // Playwright text selector
1665
+ // Material Design tabs (bilingual FR/EN via i18n)
1666
+ ...i18nSelectors('div.mdc-tab:has-text("{text}")', 'tabs', 'studio'),
1667
+ ...i18nSelectors('.mat-mdc-tab:has-text("{text}")', 'tabs', 'studio'),
1668
+ ...i18nSelectors('[role="tab"]:has-text("{text}")', 'tabs', 'studio'),
1669
+ ...i18nSelectors('div.mdc-tab >> text={text}', 'tabs', 'studio'),
1657
1670
  '.notebook-guide', // Legacy fallback
1658
1671
  ];
1659
1672
  for (const selector of studioSelectors) {
@@ -2131,19 +2144,13 @@ export class ContentManager {
2131
2144
  */
2132
2145
  async clickDeleteOption() {
2133
2146
  const deleteSelectors = [
2134
- // Menu item selectors
2135
- 'button:has-text("Delete")',
2136
- 'button:has-text("Supprimer")',
2137
- 'button:has-text("Remove")',
2138
- 'button:has-text("Retirer")',
2139
- '[role="menuitem"]:has-text("Delete")',
2140
- '[role="menuitem"]:has-text("Supprimer")',
2141
- '[role="menuitem"]:has-text("Remove")',
2142
- '[role="menuitem"]:has-text("Retirer")',
2143
- 'mat-menu-item:has-text("Delete")',
2144
- 'mat-menu-item:has-text("Remove")',
2145
- '.mat-menu-item:has-text("Delete")',
2146
- '.mat-menu-item:has-text("Remove")',
2147
+ // Menu item selectors (bilingual FR/EN via i18n)
2148
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'delete'),
2149
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'remove'),
2150
+ ...i18nSelectors('[role="menuitem"]:has-text("{text}")', 'buttons', 'delete'),
2151
+ ...i18nSelectors('[role="menuitem"]:has-text("{text}")', 'buttons', 'remove'),
2152
+ ...i18nSelectors('mat-menu-item:has-text("{text}")', 'buttons', 'delete'),
2153
+ ...i18nSelectors('.mat-menu-item:has-text("{text}")', 'buttons', 'delete'),
2147
2154
  // With icons
2148
2155
  'button:has(mat-icon:has-text("delete"))',
2149
2156
  '[role="menuitem"]:has(mat-icon:has-text("delete"))',
@@ -2191,13 +2198,10 @@ export class ContentManager {
2191
2198
  */
2192
2199
  async confirmDeletion() {
2193
2200
  const confirmSelectors = [
2194
- // Confirmation buttons
2195
- 'button:has-text("Confirm")',
2196
- 'button:has-text("Confirmer")',
2197
- 'button:has-text("Yes")',
2198
- 'button:has-text("Oui")',
2199
- 'button:has-text("Delete")',
2200
- 'button:has-text("Supprimer")',
2201
+ // Confirmation buttons (bilingual via i18n)
2202
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'confirm'),
2203
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'yes'),
2204
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'delete'),
2201
2205
  'button:has-text("OK")',
2202
2206
  // Dialog confirm buttons
2203
2207
  '[role="dialog"] button.mat-primary',
@@ -2467,8 +2471,8 @@ export class ContentManager {
2467
2471
  'button[aria-label*="Download"]',
2468
2472
  'button[aria-label*="Télécharger"]',
2469
2473
  'button[aria-label*="download"]',
2470
- 'button:has-text("Download")',
2471
- 'button:has-text("Télécharger")',
2474
+ // Text-based patterns (bilingual via i18n)
2475
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'download'),
2472
2476
  'a[download]',
2473
2477
  '.download-button',
2474
2478
  '[data-action="download"]',
@@ -2582,13 +2586,11 @@ export class ContentManager {
2582
2586
  continue;
2583
2587
  }
2584
2588
  }
2585
- // Find download button (either direct or in menu)
2589
+ // Find download button (either direct or in menu) - bilingual via i18n
2586
2590
  const downloadSelectors = [
2587
2591
  // Menu item patterns (if menu was opened)
2588
- '[role="menuitem"]:has-text("Download")',
2589
- '[role="menuitem"]:has-text("Télécharger")',
2590
- 'mat-menu-item:has-text("Download")',
2591
- 'mat-menu-item:has-text("Télécharger")',
2592
+ ...i18nSelectors('[role="menuitem"]:has-text("{text}")', 'buttons', 'download'),
2593
+ ...i18nSelectors('mat-menu-item:has-text("{text}")', 'buttons', 'download'),
2592
2594
  '.mat-mdc-menu-item:has-text("Download")',
2593
2595
  // Material Design icon buttons
2594
2596
  'button:has(mat-icon:has-text("download"))',
@@ -2598,9 +2600,8 @@ export class ContentManager {
2598
2600
  'button[aria-label*="Download"]',
2599
2601
  'button[aria-label*="Télécharger"]',
2600
2602
  'button[aria-label*="download"]',
2601
- // Text patterns
2602
- 'button:has-text("Download")',
2603
- 'button:has-text("Télécharger")',
2603
+ // Text patterns (bilingual via i18n)
2604
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'download'),
2604
2605
  // Icon buttons near audio
2605
2606
  '.audio-controls button:has(mat-icon)',
2606
2607
  '.audio-player button:has(mat-icon)',
@@ -2860,13 +2861,11 @@ export class ContentManager {
2860
2861
  // Step 1: Navigate to Studio panel where notes are managed
2861
2862
  await this.navigateToStudio();
2862
2863
  await randomDelay(500, 1000);
2863
- // Step 2: Look for "Add note" or "+" button in the Studio panel
2864
+ // Step 2: Look for "Add note" or "+" button in the Studio panel (bilingual via i18n)
2864
2865
  const addNoteSelectors = [
2865
- // Primary selectors for Add Note button
2866
- 'button:has-text("Add note")',
2867
- 'button:has-text("Ajouter une note")',
2868
- 'button:has-text("New note")',
2869
- 'button:has-text("Nouvelle note")',
2866
+ // Primary selectors for Add Note button (bilingual via i18n)
2867
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'addNote'),
2868
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'newNote'),
2870
2869
  // Icon button patterns
2871
2870
  'button[aria-label*="Add note"]',
2872
2871
  'button[aria-label*="add note" i]',
@@ -3019,17 +3018,13 @@ export class ContentManager {
3019
3018
  status: 'failed',
3020
3019
  };
3021
3020
  }
3022
- // Step 6: Save the note by clicking Save/Done button
3021
+ // Step 6: Save the note by clicking Save/Done button (bilingual via i18n)
3023
3022
  const saveSelectors = [
3024
- // Primary save buttons
3025
- 'button:has-text("Save")',
3026
- 'button:has-text("Enregistrer")',
3027
- 'button:has-text("Done")',
3028
- 'button:has-text("Terminé")',
3029
- 'button:has-text("Create")',
3030
- 'button:has-text("Créer")',
3031
- 'button:has-text("Add")',
3032
- 'button:has-text("Ajouter")',
3023
+ // Primary save buttons (bilingual via i18n)
3024
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'save'),
3025
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'done'),
3026
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'create'),
3027
+ ...i18nSelectors('button:has-text("{text}")', 'buttons', 'add'),
3033
3028
  // Icon buttons
3034
3029
  'button:has(mat-icon:has-text("check"))',
3035
3030
  'button:has(mat-icon:has-text("save"))',