@richscripts/richtexteditor 2.14.1 → 2.16.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 (244) hide show
  1. package/README.md +182 -10
  2. package/angular/build-manifest.json +23 -0
  3. package/angular/index.d.ts +71 -76
  4. package/angular/index.mjs +2 -0
  5. package/angular/package.json +4 -0
  6. package/headless/index.d.ts +3 -1
  7. package/headless/index.js +72 -16
  8. package/index.d.ts +40 -52
  9. package/integrations/initialization-scope.js +27 -0
  10. package/integrations/native-mount.js +30 -0
  11. package/integrations/shared.d.ts +23 -1
  12. package/integrations/shared.js +68 -22
  13. package/package.json +25 -3
  14. package/react/index.js +118 -37
  15. package/react/index.mjs +29 -0
  16. package/richtexteditor/all_plugins.js +8816 -2373
  17. package/richtexteditor/all_plugins.min.js +77 -59
  18. package/richtexteditor/lang/emoji/LICENSE-CLDR.txt +41 -0
  19. package/richtexteditor/lang/emoji/rte-emoji-de.js +3 -0
  20. package/richtexteditor/lang/emoji/rte-emoji-es.js +3 -0
  21. package/richtexteditor/lang/emoji/rte-emoji-fr.js +3 -0
  22. package/richtexteditor/lang/emoji/rte-emoji-it.js +3 -0
  23. package/richtexteditor/lang/emoji/rte-emoji-ja.js +3 -0
  24. package/richtexteditor/lang/emoji/rte-emoji-ko.js +3 -0
  25. package/richtexteditor/lang/emoji/rte-emoji-nl.js +3 -0
  26. package/richtexteditor/lang/emoji/rte-emoji-pt-pt.js +3 -0
  27. package/richtexteditor/lang/emoji/rte-emoji-pt.js +3 -0
  28. package/richtexteditor/lang/emoji/rte-emoji-ru.js +3 -0
  29. package/richtexteditor/lang/emoji/rte-emoji-zh-Hans.js +3 -0
  30. package/richtexteditor/lang/emoji/rte-emoji-zh-Hant.js +3 -0
  31. package/richtexteditor/lang/rte-lang-af.js +1 -0
  32. package/richtexteditor/lang/rte-lang-ar.js +1 -0
  33. package/richtexteditor/lang/rte-lang-bg.js +1 -0
  34. package/richtexteditor/lang/rte-lang-bn.js +1 -0
  35. package/richtexteditor/lang/rte-lang-bs.js +1 -0
  36. package/richtexteditor/lang/rte-lang-ca.js +1 -0
  37. package/richtexteditor/lang/rte-lang-cs.js +1 -0
  38. package/richtexteditor/lang/rte-lang-cy.js +1 -0
  39. package/richtexteditor/lang/rte-lang-da.js +1 -0
  40. package/richtexteditor/lang/rte-lang-de.js +1050 -2
  41. package/richtexteditor/lang/rte-lang-el.js +1 -0
  42. package/richtexteditor/lang/rte-lang-en.js +1 -0
  43. package/richtexteditor/lang/rte-lang-es.js +1056 -2
  44. package/richtexteditor/lang/rte-lang-et.js +1 -0
  45. package/richtexteditor/lang/rte-lang-fa.js +1 -0
  46. package/richtexteditor/lang/rte-lang-fi.js +1 -0
  47. package/richtexteditor/lang/rte-lang-fil.js +1 -0
  48. package/richtexteditor/lang/rte-lang-fj.js +1 -0
  49. package/richtexteditor/lang/rte-lang-fr.js +1056 -2
  50. package/richtexteditor/lang/rte-lang-ga.js +1 -0
  51. package/richtexteditor/lang/rte-lang-gu.js +1 -0
  52. package/richtexteditor/lang/rte-lang-he.js +1 -0
  53. package/richtexteditor/lang/rte-lang-hi.js +1 -0
  54. package/richtexteditor/lang/rte-lang-hr.js +1 -0
  55. package/richtexteditor/lang/rte-lang-ht.js +1 -0
  56. package/richtexteditor/lang/rte-lang-hu.js +1 -0
  57. package/richtexteditor/lang/rte-lang-id.js +1 -0
  58. package/richtexteditor/lang/rte-lang-is.js +1 -0
  59. package/richtexteditor/lang/rte-lang-it.js +1056 -2
  60. package/richtexteditor/lang/rte-lang-ja.js +1056 -2
  61. package/richtexteditor/lang/rte-lang-kn.js +1 -0
  62. package/richtexteditor/lang/rte-lang-ko.js +1056 -2
  63. package/richtexteditor/lang/rte-lang-lt.js +1 -0
  64. package/richtexteditor/lang/rte-lang-lv.js +1 -0
  65. package/richtexteditor/lang/rte-lang-mg.js +1 -0
  66. package/richtexteditor/lang/rte-lang-mi.js +1 -0
  67. package/richtexteditor/lang/rte-lang-ml.js +1 -0
  68. package/richtexteditor/lang/rte-lang-mr.js +1 -0
  69. package/richtexteditor/lang/rte-lang-ms.js +1 -0
  70. package/richtexteditor/lang/rte-lang-mt.js +1 -0
  71. package/richtexteditor/lang/rte-lang-mww.js +1 -0
  72. package/richtexteditor/lang/rte-lang-nb.js +1 -0
  73. package/richtexteditor/lang/rte-lang-nl.js +1056 -2
  74. package/richtexteditor/lang/rte-lang-otq.js +1 -0
  75. package/richtexteditor/lang/rte-lang-pa.js +1 -0
  76. package/richtexteditor/lang/rte-lang-pl.js +1 -0
  77. package/richtexteditor/lang/rte-lang-pt-pt.js +1056 -2
  78. package/richtexteditor/lang/rte-lang-pt.js +1056 -2
  79. package/richtexteditor/lang/rte-lang-ro.js +1 -0
  80. package/richtexteditor/lang/rte-lang-ru.js +1056 -2
  81. package/richtexteditor/lang/rte-lang-sk.js +1 -0
  82. package/richtexteditor/lang/rte-lang-sl.js +1 -0
  83. package/richtexteditor/lang/rte-lang-sm.js +1 -0
  84. package/richtexteditor/lang/rte-lang-sr-Cyrl.js +1 -0
  85. package/richtexteditor/lang/rte-lang-sr-Latn.js +1 -0
  86. package/richtexteditor/lang/rte-lang-sv.js +1 -0
  87. package/richtexteditor/lang/rte-lang-sw.js +1 -0
  88. package/richtexteditor/lang/rte-lang-ta.js +1 -0
  89. package/richtexteditor/lang/rte-lang-te.js +1 -0
  90. package/richtexteditor/lang/rte-lang-th.js +1 -0
  91. package/richtexteditor/lang/rte-lang-tlh-Latn.js +1 -0
  92. package/richtexteditor/lang/rte-lang-tlh-Piqd.js +1 -0
  93. package/richtexteditor/lang/rte-lang-to.js +1 -0
  94. package/richtexteditor/lang/rte-lang-tr.js +1 -0
  95. package/richtexteditor/lang/rte-lang-ty.js +1 -0
  96. package/richtexteditor/lang/rte-lang-uk.js +1 -0
  97. package/richtexteditor/lang/rte-lang-ur.js +1 -0
  98. package/richtexteditor/lang/rte-lang-vi.js +1 -0
  99. package/richtexteditor/lang/rte-lang-yua.js +1 -0
  100. package/richtexteditor/lang/rte-lang-zh-Hans.js +1050 -2
  101. package/richtexteditor/lang/rte-lang-zh-Hant.js +1050 -2
  102. package/richtexteditor/plugins/a11yenhance.js +4 -1
  103. package/richtexteditor/plugins/a11yenhance.min.js +1 -1
  104. package/richtexteditor/plugins/accessibilitychecker.js +256 -64
  105. package/richtexteditor/plugins/accessibilitychecker.min.js +1 -1
  106. package/richtexteditor/plugins/aitoolkit.js +1057 -386
  107. package/richtexteditor/plugins/aitoolkit.min.js +7 -7
  108. package/richtexteditor/plugins/all_plugins.js +8816 -2373
  109. package/richtexteditor/plugins/all_plugins.min.js +77 -59
  110. package/richtexteditor/plugins/autocorrect.js +66 -31
  111. package/richtexteditor/plugins/autocorrect.min.js +1 -1
  112. package/richtexteditor/plugins/autoembed.js +18 -1
  113. package/richtexteditor/plugins/autoembed.min.js +1 -1
  114. package/richtexteditor/plugins/blocktypes.js +18 -9
  115. package/richtexteditor/plugins/blocktypes.min.js +1 -1
  116. package/richtexteditor/plugins/bookmarkcard.js +12 -5
  117. package/richtexteditor/plugins/bookmarkcard.min.js +2 -2
  118. package/richtexteditor/plugins/changecase.js +72 -19
  119. package/richtexteditor/plugins/changecase.min.js +1 -1
  120. package/richtexteditor/plugins/charlimit.js +62 -2
  121. package/richtexteditor/plugins/charlimit.min.js +2 -2
  122. package/richtexteditor/plugins/comments.js +460 -61
  123. package/richtexteditor/plugins/comments.min.js +4 -3
  124. package/richtexteditor/plugins/contentminimap.js +28 -9
  125. package/richtexteditor/plugins/contentminimap.min.js +1 -1
  126. package/richtexteditor/plugins/crdt-engine.js +27 -1
  127. package/richtexteditor/plugins/crossreference.js +1 -1
  128. package/richtexteditor/plugins/crossreference.min.js +1 -1
  129. package/richtexteditor/plugins/dialoga11y.js +26 -4
  130. package/richtexteditor/plugins/dialoga11y.min.js +1 -1
  131. package/richtexteditor/plugins/dictation.js +1 -1
  132. package/richtexteditor/plugins/documentimport.js +250 -59
  133. package/richtexteditor/plugins/documentimport.min.js +4 -4
  134. package/richtexteditor/plugins/documentoutline.js +47 -8
  135. package/richtexteditor/plugins/documentoutline.min.js +1 -1
  136. package/richtexteditor/plugins/docxexport.js +233 -32
  137. package/richtexteditor/plugins/docxexport.min.js +2 -2
  138. package/richtexteditor/plugins/draghandle.js +267 -3
  139. package/richtexteditor/plugins/draghandle.min.js +1 -1
  140. package/richtexteditor/plugins/emailtoolkit.js +28 -14
  141. package/richtexteditor/plugins/emailtoolkit.min.js +1 -1
  142. package/richtexteditor/plugins/emojiautocomplete.js +111 -57
  143. package/richtexteditor/plugins/emojiautocomplete.min.js +1 -1
  144. package/richtexteditor/plugins/footnotes.js +65 -11
  145. package/richtexteditor/plugins/footnotes.min.js +1 -1
  146. package/richtexteditor/plugins/formatpainter.js +10 -0
  147. package/richtexteditor/plugins/formatpainter.min.js +1 -1
  148. package/richtexteditor/plugins/formattingmarks.js +6 -6
  149. package/richtexteditor/plugins/formattingmarks.min.js +1 -1
  150. package/richtexteditor/plugins/gapcursor.js +60 -0
  151. package/richtexteditor/plugins/gapcursor.min.js +1 -1
  152. package/richtexteditor/plugins/ghostcomplete.js +1 -1
  153. package/richtexteditor/plugins/html2pdf.js +8 -8
  154. package/richtexteditor/plugins/html2pdf.min.js +1 -1
  155. package/richtexteditor/plugins/imageeditor.js +31 -12
  156. package/richtexteditor/plugins/imageeditor.min.js +1 -1
  157. package/richtexteditor/plugins/importstyles.js +1 -1
  158. package/richtexteditor/plugins/inlinecode.js +25 -0
  159. package/richtexteditor/plugins/inlinecode.min.js +1 -1
  160. package/richtexteditor/plugins/insertcode.js +29 -11
  161. package/richtexteditor/plugins/insertcode.min.js +6 -6
  162. package/richtexteditor/plugins/insertemoji.js +113 -16
  163. package/richtexteditor/plugins/insertemoji.min.js +1 -1
  164. package/richtexteditor/plugins/insertgallery.js +46 -40
  165. package/richtexteditor/plugins/insertgallery.min.js +1 -1
  166. package/richtexteditor/plugins/inserttemplate.js +43 -19
  167. package/richtexteditor/plugins/inserttemplate.min.js +1 -1
  168. package/richtexteditor/plugins/keyboarda11y.js +66 -3
  169. package/richtexteditor/plugins/keyboarda11y.min.js +1 -1
  170. package/richtexteditor/plugins/layouttable.js +20 -0
  171. package/richtexteditor/plugins/layouttable.min.js +1 -1
  172. package/richtexteditor/plugins/linkchecker.js +29 -9
  173. package/richtexteditor/plugins/linkchecker.min.js +1 -1
  174. package/richtexteditor/plugins/localdraft.js +51 -7
  175. package/richtexteditor/plugins/localdraft.min.js +1 -1
  176. package/richtexteditor/plugins/markdownexport.js +159 -0
  177. package/richtexteditor/plugins/markdownexport.min.js +2 -2
  178. package/richtexteditor/plugins/mathdialog.js +11 -5
  179. package/richtexteditor/plugins/mathdialog.min.js +1 -1
  180. package/richtexteditor/plugins/mentionplugin.js +90 -27
  181. package/richtexteditor/plugins/mentionplugin.min.js +1 -1
  182. package/richtexteditor/plugins/mermaiddiagram.js +14 -8
  183. package/richtexteditor/plugins/mermaiddiagram.min.js +2 -2
  184. package/richtexteditor/plugins/multilevellist.js +4 -0
  185. package/richtexteditor/plugins/multilevellist.min.js +1 -1
  186. package/richtexteditor/plugins/nodetypes.js +1 -1
  187. package/richtexteditor/plugins/nodetypes.min.js +1 -1
  188. package/richtexteditor/plugins/pagination.js +2 -2
  189. package/richtexteditor/plugins/pagination.min.js +1 -1
  190. package/richtexteditor/plugins/pdfexport.js +1752 -1167
  191. package/richtexteditor/plugins/pdfexport.min.js +28 -13
  192. package/richtexteditor/plugins/permanentpen.js +3 -3
  193. package/richtexteditor/plugins/permanentpen.min.js +1 -1
  194. package/richtexteditor/plugins/readabilitystats.js +25 -11
  195. package/richtexteditor/plugins/readabilitystats.min.js +1 -1
  196. package/richtexteditor/plugins/readaloud.js +30 -16
  197. package/richtexteditor/plugins/readaloud.min.js +1 -1
  198. package/richtexteditor/plugins/restrictedediting.js +166 -0
  199. package/richtexteditor/plugins/restrictedediting.min.js +2 -2
  200. package/richtexteditor/plugins/revisionhistory.js +173 -41
  201. package/richtexteditor/plugins/revisionhistory.min.js +3 -3
  202. package/richtexteditor/plugins/rubyannotation.js +21 -11
  203. package/richtexteditor/plugins/rubyannotation.min.js +1 -1
  204. package/richtexteditor/plugins/sanitizer.js +119 -7
  205. package/richtexteditor/plugins/sanitizer.min.js +1 -1
  206. package/richtexteditor/plugins/slashcommand.js +32 -18
  207. package/richtexteditor/plugins/slashcommand.min.js +2 -2
  208. package/richtexteditor/plugins/smartchips.js +39 -25
  209. package/richtexteditor/plugins/smartchips.min.js +2 -2
  210. package/richtexteditor/plugins/spellcheck.js +20 -11
  211. package/richtexteditor/plugins/spellcheck.min.js +2 -2
  212. package/richtexteditor/plugins/stateinspector.js +21 -11
  213. package/richtexteditor/plugins/stateinspector.min.js +2 -2
  214. package/richtexteditor/plugins/structured-content-bridge.js +90 -32
  215. package/richtexteditor/plugins/structured-content-bridge.min.js +8 -8
  216. package/richtexteditor/plugins/tableofcontents.js +5 -2
  217. package/richtexteditor/plugins/tableofcontents.min.js +1 -1
  218. package/richtexteditor/plugins/tabletools.js +388 -3
  219. package/richtexteditor/plugins/tabletools.min.js +1 -1
  220. package/richtexteditor/plugins/textdirection.js +19 -6
  221. package/richtexteditor/plugins/textdirection.min.js +1 -1
  222. package/richtexteditor/plugins/textpartlanguage.js +161 -32
  223. package/richtexteditor/plugins/textpartlanguage.min.js +1 -1
  224. package/richtexteditor/plugins/todolist.js +90 -6
  225. package/richtexteditor/plugins/todolist.min.js +1 -1
  226. package/richtexteditor/plugins/trackedchanges.js +1812 -25
  227. package/richtexteditor/plugins/trackedchanges.min.js +5 -3
  228. package/richtexteditor/plugins/wordexport.js +2 -2
  229. package/richtexteditor/plugins/yjscollab.js +11 -6
  230. package/richtexteditor/plugins/yjscollab.min.js +1 -1
  231. package/richtexteditor/plugins/zz_richtextbox_dialog_style.js +22 -13
  232. package/richtexteditor/plugins/zz_richtextbox_dialog_style.min.js +1 -1
  233. package/richtexteditor/rte-config.js +144 -86
  234. package/richtexteditor/rte-headless.js +19 -4
  235. package/richtexteditor/rte-watchdog.js +3 -1
  236. package/richtexteditor/rte-webcomponent.js +91 -16
  237. package/richtexteditor/rte.js +146 -88
  238. package/richtexteditor/runtime/colorpicker_more_ns.htm +3 -1
  239. package/scripts/build-angular.mjs +97 -0
  240. package/svelte/index.d.ts +2 -1
  241. package/svelte/index.js +80 -9
  242. package/vue/index.js +130 -34
  243. package/angular/component.ts +0 -297
  244. package/angular/index.ts +0 -24
@@ -22,7 +22,10 @@ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
22
22
  var scripts = document.getElementsByTagName("script");
23
23
  for (var j = scripts.length - 1; j >= 0; j--) {
24
24
  var src = scripts[j].getAttribute("src") || "";
25
- if (/(?:^|\/)(?:aitoolkit|all_plugins)\.js(?:\?.*)?$/i.test(src)) {
25
+ // 2026-10-17 the .min names too: a page loading all_plugins.min.js matched nothing and fell back to the fixed
26
+ // /richtexteditor path - the drive root over file://, so the AI stylesheet never loaded from a download opened
27
+ // from disk. tools/ai/aitoolkit-css-location.mjs
28
+ if (/(?:^|\/)(?:aitoolkit|all_plugins)(?:\.min)?\.js(?:[?#].*)?$/i.test(src)) {
26
29
  ourScript = scripts[j];
27
30
  break;
28
31
  }
@@ -33,11 +36,13 @@ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
33
36
  // paying for it. The readable .css is still shipped and still the one to edit
34
37
  // or override; this only changes what gets fetched by default.
35
38
  function resolve(name) {
36
- return srcAttr
37
- ? srcAttr.replace(/[^/]+$/, name)
38
- : (((window.RTE_DefaultConfig && window.RTE_DefaultConfig.url_base) || "/richtexteditor") + "/plugins/" + name);
39
+ if (srcAttr) return srcAttr.replace(/[?#].*$/, "").replace(/[^/]+$/, name);
40
+ // no plugin script of ours (bundled into an app script): the folder rte.js was loaded from, then the old default
41
+ var base = (typeof RichTextEditor !== "undefined" && RichTextEditor.__scriptBase) ||
42
+ (window.RTE_DefaultConfig && window.RTE_DefaultConfig.url_base) || "/richtexteditor";
43
+ return base + "/plugins/" + name;
39
44
  }
40
- function bust(href) { return href.indexOf("?") < 0 ? href + "?v=20260830a" : href; }
45
+ function bust(href) { return href.indexOf("?") < 0 ? href + "?v=20261018a" : href; }
41
46
 
42
47
  var link = document.createElement("link");
43
48
  link.rel = "stylesheet";
@@ -94,6 +99,15 @@ function RTE_Plugin_AIToolkit() {
94
99
  var obj = this;
95
100
  var config;
96
101
  var editor;
102
+ // 2026-10-25 UI words outside InitConfig (inline review chips, review shortcuts, Ask AI result / plan, the legacy
103
+ // review layout) read config.text_ keys with English as the fallback; {0} and {1} take the values.
104
+ // tools/i18n/ai-review-chips-language.mjs
105
+ function uiT(key, fallback, a, b) {
106
+ var s = (config && config["text_" + key]) || fallback;
107
+ if (a !== undefined) s = s.split("{0}").join(String(a));
108
+ if (b !== undefined) s = s.split("{1}").join(String(b));
109
+ return s;
110
+ }
97
111
 
98
112
  obj.PluginName = "AIToolkit";
99
113
 
@@ -127,26 +141,26 @@ function RTE_Plugin_AIToolkit() {
127
141
  // in a later pass. Customer overrides via `aiToolkitActions` keep
128
142
  // any legacy long-form labels untouched.
129
143
  config.aiToolkitActions = config.aiToolkitActions || [
130
- { id: "chat-panel", section: "Start", icon: "chat", title: "Chat", description: "Ask follow-up questions about this document.", target: "chat-panel" },
131
- { id: "open-dialog", section: "Start", icon: "open_dialog", title: "Draft", description: "Generate, rewrite, translate, or summarize before applying.", target: "dialog" },
132
- { id: "review-panel", section: "Start", icon: "review", title: "Review", description: "Accept or reject prepared AI edits.", target: "review-panel" },
133
- { id: "proofread", section: "Quick edit", icon: "proofread", title: "Proofread", description: "Check grammar, spacing, and clarity.", resolverMode: "proofread", target: "selection-preview" },
134
- { id: "rewrite", section: "Quick edit", icon: "rewrite", title: "Rewrite", description: "Improve clarity without changing intent.", resolverMode: "rewrite", target: "selection-preview" },
135
- { id: "translate", section: "Quick edit", icon: "translate", title: "Translate", description: "Choose a language and preview the result.", resolverMode: "translate", target: "dialog", autoRun: false },
136
- { id: "justify", section: "Quick edit", icon: "justify", title: "Explain", description: "Rewrite and show why it changed.", resolverMode: "justify", target: "dialog", autoRun: false },
137
- { id: "comment", section: "Insert", icon: "comment", title: "Add note", description: "Insert a short comment near the selection.", resolverMode: "comment", target: "comment" },
138
- { id: "paragraph", section: "Insert", icon: "paragraph", title: "Add paragraph", description: "Draft a supporting paragraph below.", resolverMode: "paragraph", target: "insert" }
144
+ { id: "chat-panel", section: aiT("aia_sec_start", "Start"), icon: "chat", title: aiT("aia_chat_panel_t", "Chat"), description: aiT("aia_chat_panel_d", "Ask follow-up questions about this document."), target: "chat-panel" },
145
+ { id: "open-dialog", section: aiT("aia_sec_start", "Start"), icon: "open_dialog", title: aiT("aia_open_dialog_t", "Draft"), description: aiT("aia_open_dialog_d", "Generate, rewrite, translate, or summarize before applying."), target: "dialog" },
146
+ { id: "review-panel", section: aiT("aia_sec_start", "Start"), icon: "review", title: aiT("aia_review_panel_t", "Review"), description: aiT("aia_review_panel_d", "Accept or reject prepared AI edits."), target: "review-panel" },
147
+ { id: "proofread", section: aiT("aia_sec_quick", "Quick edit"), icon: "proofread", title: aiT("aia_proofread_t", "Proofread"), description: aiT("aia_proofread_d", "Check grammar, spacing, and clarity."), resolverMode: "proofread", target: "selection-preview" },
148
+ { id: "rewrite", section: aiT("aia_sec_quick", "Quick edit"), icon: "rewrite", title: aiT("aia_rewrite_t", "Rewrite"), description: aiT("aia_rewrite_d", "Improve clarity without changing intent."), resolverMode: "rewrite", target: "selection-preview" },
149
+ { id: "translate", section: aiT("aia_sec_quick", "Quick edit"), icon: "translate", title: aiT("aia_translate_t", "Translate"), description: aiT("aia_translate_d", "Choose a language and preview the result."), resolverMode: "translate", target: "dialog", autoRun: false },
150
+ { id: "justify", section: aiT("aia_sec_quick", "Quick edit"), icon: "justify", title: aiT("aia_justify_t", "Explain"), description: aiT("aia_justify_d", "Rewrite and show why it changed."), resolverMode: "justify", target: "dialog", autoRun: false },
151
+ { id: "comment", section: aiT("aia_sec_insert", "Insert"), icon: "comment", title: aiT("aia_comment_t", "Add note"), description: aiT("aia_comment_d", "Insert a short comment near the selection."), resolverMode: "comment", target: "comment" },
152
+ { id: "paragraph", section: aiT("aia_sec_insert", "Insert"), icon: "paragraph", title: aiT("aia_paragraph_t", "Add paragraph"), description: aiT("aia_paragraph_d", "Draft a supporting paragraph below."), resolverMode: "paragraph", target: "insert" }
139
153
  ];
140
154
  // 2026-05-09 (v20260509j): single-verb default chip labels matching
141
155
  // the toolbar dropdown idiom — chips render on one line at the
142
156
  // default 390px chat panel width. Customer overrides via
143
157
  // aiToolkitChatPrompts keep their own labels untouched.
144
158
  config.aiToolkitChatPrompts = config.aiToolkitChatPrompts || [
145
- { id: "summarize", label: "Summarize", prompt: "Summarize the current content and suggest the clearest next edit." },
146
- { id: "proofread", label: "Proofread", prompt: "Proofread the current content and prepare a cleaner version I can review." },
147
- { id: "translate", label: "Translate", prompt: "Translate the current content into Spanish and prepare a reviewable version." },
148
- { id: "headings", label: "Headings", prompt: "Suggest better section titles and subheadings for this content." },
149
- { id: "expand", label: "Expand", prompt: "Add a short supporting paragraph that strengthens this content." }
159
+ { id: "summarize", label: aiT("aip_summarize", "Summarize"), prompt: "Summarize the current content and suggest the clearest next edit." },
160
+ { id: "proofread", label: aiT("aip_proofread", "Proofread"), prompt: "Proofread the current content and prepare a cleaner version I can review." },
161
+ { id: "translate", label: aiT("aip_translate", "Translate"), prompt: "Translate the current content into Spanish and prepare a reviewable version." },
162
+ { id: "headings", label: aiT("aip_headings", "Headings"), prompt: "Suggest better section titles and subheadings for this content." },
163
+ { id: "expand", label: aiT("aip_expand", "Expand"), prompt: "Add a short supporting paragraph that strengthens this content." }
150
164
  ];
151
165
  // 2026-05-20 (v20260520c): descriptions trimmed to ~5-8 words so
152
166
  // each fits the one-line mode caption without wrapping at the 480px
@@ -165,23 +179,29 @@ function RTE_Plugin_AIToolkit() {
165
179
  // on narrow viewports. Same brevity Notion AI inline composer subtitles
166
180
  // ship — "Improve writing", "Make shorter" — single 2-4 word verb
167
181
  // phrases.
182
+ // 2026-10-18 the Ask AI dialog's words read config.text_ keys where they are SHOWN (English as the fallback);
183
+ // labels that also feed logic (Document / Selection) are translated at the display sites only.
184
+ // tools/i18n/ask-ai-language.mjs
185
+ function aiT(key, fallback, arg) { var s = (config && config["text_" + key]) || fallback; return arg === undefined ? s : s.split("{0}").join(String(arg)); }
186
+ obj.__aiT = aiT;
168
187
  config.aiToolkitDialogModes = config.aiToolkitDialogModes || [
169
- { id: "proofread", title: "Proofread", description: "Fix grammar & clarity." },
170
- { id: "rewrite", title: "Rewrite", description: "Rewrite for clarity." },
171
- { id: "translate", title: "Translate", description: "Translate to a language." },
172
- { id: "justify", title: "Explain rewrite", description: "Rewrite and explain why." },
173
- { id: "shorten", title: "Shorten", description: "Compress to be shorter." },
174
- { id: "expand", title: "Expand", description: "Add supporting detail." },
175
- { id: "summarize", title: "Summarize", description: "Summarize the source." }
188
+ { id: "proofread", title: aiT("aim_proofread_t", "Proofread"), description: aiT("aim_proofread_d", "Fix grammar & clarity.") },
189
+ { id: "rewrite", title: aiT("aim_rewrite_t", "Rewrite"), description: aiT("aim_rewrite_d", "Rewrite for clarity.") },
190
+ { id: "translate", title: aiT("aim_translate_t", "Translate"), description: aiT("aim_translate_d", "Translate to a language.") },
191
+ { id: "justify", title: aiT("aim_justify_t", "Explain rewrite"), description: aiT("aim_justify_d", "Rewrite and explain why.") },
192
+ { id: "shorten", title: aiT("aim_shorten_t", "Shorten"), description: aiT("aim_shorten_d", "Compress to be shorter.") },
193
+ { id: "expand", title: aiT("aim_expand_t", "Expand"), description: aiT("aim_expand_d", "Add supporting detail.") },
194
+ { id: "summarize", title: aiT("aim_summarize_t", "Summarize"), description: aiT("aim_summarize_d", "Summarize the source.") }
176
195
  ];
177
196
  config.aiToolkitResolver = config.aiToolkitResolver || null;
197
+ // 2026-10-13 image generation: the host's provider call, bring your own key. null = not configured.
198
+ config.aiToolkitImageResolver = config.aiToolkitImageResolver || null;
178
199
  config.aiToolkitOperationHandlers = config.aiToolkitOperationHandlers || {};
179
200
  config.aiToolkitPersistenceKey = config.aiToolkitPersistenceKey || "";
180
201
  config.aiToolkitReviewSyncInterval = config.aiToolkitReviewSyncInterval || 15000;
181
202
  config.aiToolkitSuggestionLedgerUrl = config.aiToolkitSuggestionLedgerUrl || "";
182
203
  config.aiToolkitReviewLogUrl = config.aiToolkitReviewLogUrl || "";
183
204
  config.aiToolkitAutoToolbarButtons = config.aiToolkitAutoToolbarButtons !== false;
184
- config.aiToolkitUiVersion = config.aiToolkitUiVersion || "v20260520k";
185
205
 
186
206
  appendToolbarCommand("toolbar_default", "#{aiassist}");
187
207
  appendToolbarCommand("toolbar_basic", "#{aiassist}");
@@ -200,10 +220,129 @@ function RTE_Plugin_AIToolkit() {
200
220
 
201
221
  obj.InitEditor = function (argeditor) {
202
222
  editor = argeditor;
223
+
224
+ // 2026-10-13 Image generation (tools/ai/image-generation.mjs). The host supplies the provider call through
225
+ // config.aiToolkitImageResolver, so the editor makes no outbound request of its own. What comes back is checked
226
+ // before anything is inserted: an image data URL (png, jpeg, webp or gif) or an https address, at most 6 million
227
+ // characters.
228
+ var IMAGE_SRC_MAX = 6000000;
229
+ function checkGeneratedImageSrc(src) {
230
+ var s = typeof src === "string" ? src.replace(/^\s+|\s+$/g, "") : "";
231
+ if (!s) throw new Error("The image generator returned no image.");
232
+ if (s.length > IMAGE_SRC_MAX) throw new Error("The generated image is too large to insert.");
233
+ if (/^data:image\/(png|jpe?g|webp|gif);base64,[A-Za-z0-9+\/=]+$/i.test(s)) return s;
234
+ if (/^https:\/\/[^\s"'<>]+$/i.test(s)) return s;
235
+ throw new Error("The image generator returned something that is not an image address.");
236
+ }
237
+ function generateImage(prompt) {
238
+ var text = String(prompt || "").replace(/^\s+|\s+$/g, "");
239
+ if (!text) return Promise.reject(new Error("Describe the image first."));
240
+ var resolver = config.aiToolkitImageResolver;
241
+ if (typeof resolver !== "function") {
242
+ return Promise.reject(new Error("No image generator is configured (config.aiToolkitImageResolver)."));
243
+ }
244
+ return Promise.resolve().then(function () { return resolver(text); }).then(checkGeneratedImageSrc);
245
+ }
246
+ function insertGeneratedImage(src, alt) {
247
+ editor.insertHTML('<img src="' + escapeAttribute(src) + '" alt="' + escapeAttribute(alt || "") + '">');
248
+ try { if (typeof editor.fireChange === "function") editor.fireChange(); } catch (e) { /* ignore */ }
249
+ }
250
+ var imageComposer = null;
251
+ function closeImageComposer() {
252
+ // 2026-10-14 Focus inside the card (Escape, Cancel) goes back to the editor when it closes, so a
253
+ // keyboard user does not land on the page body (WCAG 2.4.3). tools/ai/image-generator-focus.mjs
254
+ var hadFocus = false;
255
+ try { hadFocus = !!(imageComposer && imageComposer.contains(imageComposer.ownerDocument.activeElement)); } catch (e) { /* ignore */ }
256
+ if (imageComposer && imageComposer.parentNode) imageComposer.parentNode.removeChild(imageComposer);
257
+ imageComposer = null;
258
+ if (hadFocus) { try { editor.focus(); } catch (e) { /* ignore */ } }
259
+ }
260
+ // A small prompt card over the editor. Generate inserts the image at the caret and closes the card; Cancel and
261
+ // Escape close it without inserting anything.
262
+ function openImageComposer() {
263
+ closeImageComposer();
264
+ var frame = editor.iframe;
265
+ if (!frame || !frame.ownerDocument) return;
266
+ var host = frame.ownerDocument;
267
+ var box = host.createElement("div");
268
+ box.className = "rte-image-gen";
269
+ box.setAttribute("role", "dialog");
270
+ box.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aig_title", "Generate an image") : "Generate an image"));
271
+ var bs = box.style;
272
+ bs.position = "fixed"; bs.zIndex = "2147483000"; bs.width = "320px"; bs.padding = "12px";
273
+ bs.background = "#fff"; bs.border = "1px solid rgba(0,0,0,.15)"; bs.borderRadius = "8px";
274
+ bs.boxShadow = "0 8px 24px rgba(15,23,42,.18)"; bs.font = "13px/1.4 system-ui, sans-serif"; bs.color = "#1f2937";
275
+ var fr = frame.getBoundingClientRect();
276
+ bs.left = Math.round(fr.left + 16) + "px";
277
+ bs.top = Math.round(fr.top + 16) + "px";
278
+ var label = host.createElement("label");
279
+ label.textContent = (obj.__aiT ? obj.__aiT("aig_describe", "Describe the image") : "Describe the image");
280
+ label.style.display = "block";
281
+ label.style.marginBottom = "6px";
282
+ var area = host.createElement("textarea");
283
+ area.rows = 3;
284
+ area.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aig_describe", "Describe the image") : "Describe the image"));
285
+ area.style.width = "100%"; area.style.boxSizing = "border-box"; area.style.font = "inherit";
286
+ var status = host.createElement("div");
287
+ status.className = "rte-image-gen-status";
288
+ status.setAttribute("aria-live", "polite");
289
+ status.style.minHeight = "18px"; status.style.margin = "6px 0"; status.style.color = "#b91c1c";
290
+ var row = host.createElement("div");
291
+ row.style.textAlign = "right";
292
+ var cancel = host.createElement("button");
293
+ cancel.type = "button";
294
+ cancel.className = "rte-image-gen-cancel";
295
+ cancel.textContent = (obj.__aiT ? obj.__aiT("aig_cancel", "Cancel") : "Cancel");
296
+ cancel.style.marginRight = "6px";
297
+ var go = host.createElement("button");
298
+ go.type = "button";
299
+ go.className = "rte-image-gen-go";
300
+ go.textContent = (obj.__aiT ? obj.__aiT("aig_generate", "Generate") : "Generate");
301
+ go.style.background = "#1d67ba"; go.style.color = "#fff"; go.style.border = "0";
302
+ go.style.borderRadius = "4px"; go.style.padding = "4px 12px";
303
+ go.addEventListener("click", function () {
304
+ var text = area.value.replace(/^\s+|\s+$/g, "");
305
+ if (!text) { status.textContent = (obj.__aiT ? obj.__aiT("aig_describefirst", "Describe the image first.") : "Describe the image first."); return; }
306
+ go.disabled = true;
307
+ status.style.color = "#475569";
308
+ status.textContent = (obj.__aiT ? obj.__aiT("aig_generating", "Generating...") : "Generating...");
309
+ generateImage(text).then(function (src) {
310
+ insertGeneratedImage(src, text);
311
+ closeImageComposer();
312
+ // the Generate button was disabled while it ran, which moved focus out of the card: return it here
313
+ try { editor.focus(); } catch (e) { /* ignore */ }
314
+ }, function (err) {
315
+ go.disabled = false;
316
+ status.style.color = "#b91c1c";
317
+ status.textContent = (err && err.message) || (obj.__aiT ? obj.__aiT("aig_failed", "The image could not be generated.") : "The image could not be generated.");
318
+ });
319
+ });
320
+ cancel.addEventListener("click", closeImageComposer);
321
+ box.addEventListener("keydown", function (e) {
322
+ if (e.key === "Escape" || e.key === "Esc") { e.preventDefault(); closeImageComposer(); }
323
+ });
324
+ row.appendChild(cancel);
325
+ row.appendChild(go);
326
+ box.appendChild(label);
327
+ box.appendChild(area);
328
+ box.appendChild(status);
329
+ box.appendChild(row);
330
+ host.body.appendChild(box);
331
+ imageComposer = box;
332
+ try { area.focus(); } catch (e) { /* focus is best effort */ }
333
+ }
334
+ editor.generateImage = function (prompt) {
335
+ return generateImage(prompt).then(function (src) { insertGeneratedImage(src, prompt); return true; });
336
+ };
337
+ editor.openImageGenerator = function () { openImageComposer(); };
338
+
203
339
  editor.aiToolkit = {
340
+ generateImage: generateImage,
341
+ setImageResolver: function (fn) { config.aiToolkitImageResolver = typeof fn === "function" ? fn : null; },
204
342
  normalizeText: normalizeText,
205
343
  ensureSentence: ensureSentence,
206
344
  buildDemoResult: buildDemoResult,
345
+ isDemoMode: isDemoMode,
207
346
  getTranslateLanguages: getTranslateLanguages,
208
347
  getTranslateLanguageLabel: getTranslateLanguageLabel,
209
348
  getOperationContract: getOperationContract,
@@ -690,6 +829,21 @@ function RTE_Plugin_AIToolkit() {
690
829
  config[key] = current ? current + token : token;
691
830
  }
692
831
 
832
+ // 2026-10-15 with no resolver the toolkit answers from built-in samples, and nothing said so: an evaluating
833
+ // customer reported the AI feature as "not working". The dialog and the chat panel now carry a notice while
834
+ // no resolver is set. tools/ai/demo-mode-notice.mjs
835
+ function isDemoMode() {
836
+ return typeof config.aiToolkitResolver !== "function";
837
+ }
838
+
839
+ function appendDemoNotice(parent) {
840
+ if (!isDemoMode()) return null;
841
+ var notice = append(parent, "div", "", "rte-ai-demo-notice",
842
+ config.text_aitoolkitdemonotice || "Demo mode: these are built-in sample results, not a real AI model. Connect an AI provider with the aiToolkitResolver setting to get real suggestions.");
843
+ notice.setAttribute("role", "note");
844
+ return notice;
845
+ }
846
+
693
847
  function append(parent, tagName, cssText, className, text) {
694
848
  var node = parent.ownerDocument.createElement(tagName);
695
849
  if (cssText) {
@@ -1100,7 +1254,9 @@ function RTE_Plugin_AIToolkit() {
1100
1254
  if (mode === "insert" && editor && editor.insertHTML) {
1101
1255
  editor.insertHTML(html);
1102
1256
  } else if (editor && editor.setHTMLCode) {
1103
- editor.setHTMLCode(html);
1257
+ // undoable: Ctrl+Z gives back exactly the document this replaced
1258
+ // (tools/import/replace-undo-plugins.mjs)
1259
+ editor.setHTMLCode(html, { undoable: true });
1104
1260
  }
1105
1261
  return html;
1106
1262
  }).catch(function (err) {
@@ -1137,6 +1293,385 @@ function RTE_Plugin_AIToolkit() {
1137
1293
  .trim();
1138
1294
  }
1139
1295
 
1296
+ // 2026-10-18 Spoken labels are phrases joined into sentences. A phrase that already ended one ("... is ready.")
1297
+ // gave "ready.. Document thread"; after such a phrase the next follows with a space. tools/ai/aria-double-period.mjs
1298
+ function joinSentences(parts) {
1299
+ var out = "";
1300
+ for (var i = 0; i < parts.length; i++) {
1301
+ var s = String(parts[i] == null ? "" : parts[i]).replace(/[ \t\r\n]+$/, "");
1302
+ if (!s) continue;
1303
+ out += !out ? s : (/[.?!\u2026\u3002\uff01\uff1f]$/.test(out) ? " " : ". ") + s;
1304
+ }
1305
+ return out;
1306
+ }
1307
+
1308
+ // 2026-10-18 The inline AI suggestion card and the AI chat compose most of their words - shown and announced -
1309
+ // from many small builders. With a language pack loaded, this catalog translates them where they are displayed:
1310
+ // a whole string, else sentence by sentence, with patterns for the counts ("Queue 1 of 2"). Text a person wrote
1311
+ // (the card's Current / Suggested compare, the chat's messages) is never touched; anything not catalogued stays
1312
+ // as it is; an English page does nothing here. tools/i18n/ai-card-chat-language.mjs
1313
+ var AIQ_EXACT = [
1314
+ ["air_accept", "Accept"],
1315
+ ["air_reject", "Reject"],
1316
+ ["air_current", "Current"],
1317
+ ["air_suggested", "Suggested"],
1318
+ ["air_why", "Why"],
1319
+ ["air_scope_sel", "Selection"],
1320
+ ["air_scope_doc", "Document"],
1321
+ ["air_type_proofread", "Proofread"],
1322
+ ["air_type_rewrite", "Rewrite"],
1323
+ ["air_type_translate", "Translate"],
1324
+ ["air_type_comment", "Comment"],
1325
+ ["air_type_paragraph", "Paragraph"],
1326
+ ["air_type_summarize", "Summarize"],
1327
+ ["air_type_expand", "Expand"],
1328
+ ["air_type_shorten", "Shorten"],
1329
+ ["aip_headings", "Headings"],
1330
+ ["aichat", "AI Chat"],
1331
+ ["aic_newchat", "Start a fresh chat"],
1332
+ ["aic_closechat", "Close AI chat"],
1333
+ ["aic_close", "Close"],
1334
+ ["aic_usesel", "Use selection"],
1335
+ ["aic_usedoc", "Use document"],
1336
+ ["aic_copymsg", "Copy message text"],
1337
+ ["aic_copy", "Copy"],
1338
+ ["aic_regen", "Regenerate response"],
1339
+ ["aid_regenerate", "Regenerate"],
1340
+ ["aic_actions", "AI chat actions"],
1341
+ ["aic_send", "Send"],
1342
+ ["aic_starttitle", "Start with AI Chat"],
1343
+ ["aic_pill_doc", "Document context"],
1344
+ ["aic_pill_sel", "Selection context"],
1345
+ ["aic_compose_doc", "Compose next AI request for the current document"],
1346
+ ["aic_compose_sel", "Compose next AI request for the current selection"],
1347
+ ["aic_emptytitle_doc", "Type a prompt or load a starter for the current document first"],
1348
+ ["aic_emptytitle_sel", "Type a prompt or load a starter for the current selection first"],
1349
+ ["aic_empty_doc", "Ask anything about the current document"],
1350
+ ["aic_empty_sel", "Ask anything about the current selection"],
1351
+ ["air_focusaria", "Focus the current AI review item"],
1352
+ ["aiq_preview", "AI suggestion preview"],
1353
+ ["aiq_proposed", "Proposed edit"],
1354
+ ["aiq_curandsug", "Current and suggested text"],
1355
+ ["aiq_curtext", "Current text"],
1356
+ ["aiq_sugtext", "Suggested text"],
1357
+ ["aiq_ready", "Ready to review"],
1358
+ ["aiq_curfocus", "Current focus"],
1359
+ ["aiq_seldraft", "Selection draft"],
1360
+ ["aiq_docdraft", "Document draft"],
1361
+ ["aiq_prepdraft", "Prepared draft"],
1362
+ ["aiq_customdraft", "Custom draft"],
1363
+ ["aiq_draftready", "Draft ready"],
1364
+ ["aiq_glance", "Change at a glance"],
1365
+ ["aiq_surface", "Surface edit"],
1366
+ ["aiq_textrewrite", "Text rewrite"],
1367
+ ["aiq_textchange", "Text change"],
1368
+ ["aiq_langchange", "Language change"],
1369
+ ["aiq_reviewchange", "Reviewable change"],
1370
+ ["aiq_acceptedchange", "Accepted change"],
1371
+ ["aiq_rejectedchange", "Rejected change"],
1372
+ ["aiq_lowimpact", "Low impact"],
1373
+ ["aiq_medimpact", "Medium impact"],
1374
+ ["aiq_highimpact", "High impact"],
1375
+ ["aiq_whatnext", "What happens next"],
1376
+ ["aiq_qcomplete", "Queue complete"],
1377
+ ["aiq_qopened", "Queue opened"],
1378
+ ["aiq_qhandoff", "Queue handoff"],
1379
+ ["aiq_qstate", "Queue state"],
1380
+ ["aiq_recready", "Recovery ready"],
1381
+ ["aiq_recavail", "Recovery available"],
1382
+ ["aiq_finalitem", "Accepting or rejecting this final pending item finishes the current review run"],
1383
+ ["aiq_moveson", "Accepting or rejecting this item moves review straight to the next pending suggestion"],
1384
+ ["aiq_nolater", "No later pending items remain in this queue"],
1385
+ ["aiq_thenrec", "Then recovery and next queues appear"],
1386
+ ["aiq_thencomplete", "Then queue complete"],
1387
+ ["aiq_undolast", "Undo the last decision or open another queue"],
1388
+ ["aiq_afterfinal", "After reviewing the final pending item, recovery and next queues appear"],
1389
+ ["aiq_afterdec", "After this decision"],
1390
+ ["aiq_handoff", "Action handoff"],
1391
+ ["aiq_decidelane", "Decide lane"],
1392
+ ["aiq_curlane", "Current lane"],
1393
+ ["aiq_curactionlane", "Current action lane"],
1394
+ ["aiq_kblane", "Keyboard lane"],
1395
+ ["aiq_curenter", "Current Enter target"],
1396
+ ["aiq_spaceact", "Enter or Space activates this action"],
1397
+ ["aiq_acceptfinish", "Accept & finish"],
1398
+ ["aiq_rejectfinish", "Reject & finish"],
1399
+ ["aiq_acceptnext", "Accept & next"],
1400
+ ["aiq_rejectnext", "Reject & next"],
1401
+ ["aiq_accfinishdesc", "Accept & finish this AI change and finish the current review queue"],
1402
+ ["aiq_rejfinishdesc", "Reject & finish this AI change and finish the current review queue"],
1403
+ ["aiq_open", "Open"],
1404
+ ["aiq_review", "Review"],
1405
+ ["aiq_move", "Move"],
1406
+ ["aiq_previous", "Previous"],
1407
+ ["aiq_next", "Next"],
1408
+ ["aiq_decide", "Decide"],
1409
+ ["aiq_recover", "Recover"],
1410
+ ["aiq_actions", "AI suggestion actions"],
1411
+ ["aiq_openactions", "Open actions"],
1412
+ ["aiq_moveactions", "Move actions"],
1413
+ ["aiq_decideactions", "Decide actions"],
1414
+ ["aiq_recoveractions", "Recover actions"],
1415
+ ["aiq_whysug", "Why this suggestion"],
1416
+ ["aiq_upnext", "Up next"],
1417
+ ["aiq_upnextcur", "Up next becomes current"],
1418
+ ["aiq_follows", "This follows after the current decision"],
1419
+ ["aiq_nextitem", "Next item"],
1420
+ ["aiq_reviewnow", "Review this item now"],
1421
+ ["aiq_reopen", "Reopen this suggestion in the current review lane"],
1422
+ ["aiq_jump", "Jump earlier or later in this queue before deciding"],
1423
+ ["aiq_previtem", "Previous AI review item"],
1424
+ ["aiq_nextitemai", "Next AI review item"],
1425
+ ["aiq_noprev", "No previous AI review item in the current queue"],
1426
+ ["aiq_nonext", "No next AI review item in the current queue"],
1427
+ ["aiq_shortcuts", "Inline AI review shortcuts"],
1428
+ ["aiq_kbguide", "Keyboard guide"],
1429
+ ["aiq_move_l", "move"],
1430
+ ["aiq_jump_l", "jump"],
1431
+ ["aiq_enter", "Enter"],
1432
+ ["aiq_custom", "Custom"],
1433
+ ["aiq_reason_proof", "Normalizes spacing, punctuation, and capitalization while keeping the original meaning intact"],
1434
+ ["aiq_reason_rewrite", "Tightens the wording by removing filler phrases so the point comes through faster"],
1435
+ ["aiq_c_ready", "Ready"],
1436
+ ["aiq_c_docfocus", "Document focus"],
1437
+ ["aiq_c_selfocus", "Selection focus"],
1438
+ ["aiq_c_inplace", "Ask, rewrite, or review the current document in place"],
1439
+ ["aiq_c_docthread", "Document thread"],
1440
+ ["aiq_c_selthread", "Selection thread"],
1441
+ ["aiq_c_newask", "Ready for a new ask"],
1442
+ ["aiq_c_latest", "Latest reply is ready"],
1443
+ ["aiq_c_context", "Context"],
1444
+ ["aiq_c_docactive", "Document active"],
1445
+ ["aiq_c_selactive", "Selection active"],
1446
+ ["aiq_c_nosel", "No current selection"],
1447
+ ["aiq_c_selready", "Selection ready"],
1448
+ ["aiq_c_wholedoc", "Using the whole document as the active source"],
1449
+ ["aiq_c_narrow", "Select text any time to narrow the ask"],
1450
+ ["aiq_c_snapshot", "Document snapshot"],
1451
+ ["aiq_c_scopeswitch", "Context scope switch"],
1452
+ ["aiq_c_unavail", "Unavailable until text is selected"],
1453
+ ["aiq_c_activescope", "Active scope"],
1454
+ ["aiq_c_usingwhole", "AI Chat is using the whole document"],
1455
+ ["aiq_c_quickstarts", "Quick starts"],
1456
+ ["aiq_c_readychoose", "Ready to choose"],
1457
+ ["aiq_c_choosestarter", "Choose a starter for the current document or keep typing your own prompt in Compose"],
1458
+ ["aiq_c_starterbtns", "Starter buttons for the current document"],
1459
+ ["aiq_c_loadsstarter", "Loads this starter into Compose for the current document"],
1460
+ ["aiq_c_summarystarter", "Summary starter"],
1461
+ ["aiq_c_reviewstarter", "Review starter"],
1462
+ ["aiq_c_langstarter", "Language starter"],
1463
+ ["aiq_c_structstarter", "Structure starter"],
1464
+ ["aiq_c_expandstarter", "Expand starter"],
1465
+ ["aiq_c_starterready", "Starter ready"],
1466
+ ["aiq_c_p_summarize", "Summarize the current content and suggest the clearest next edit"],
1467
+ ["aiq_c_p_proofread", "Proofread the current content and prepare a cleaner version I can review"],
1468
+ ["aiq_c_p_translate", "Translate the current content into Spanish and prepare a reviewable version"],
1469
+ ["aiq_c_p_headings", "Suggest better section titles and subheadings for this content"],
1470
+ ["aiq_c_p_expand", "Add a short supporting paragraph that strengthens this content"],
1471
+ ["aiq_c_conversation", "Conversation"],
1472
+ ["aiq_c_firstex", "First exchange"],
1473
+ ["aiq_c_waitfirst", "Waiting for first ask"],
1474
+ ["aiq_c_firstanswer", "Your first answer, draft, or reviewable change for the current document will appear here"],
1475
+ ["aiq_c_askexplore", "Ask and explore"],
1476
+ ["aiq_c_getanswers", "Get answers, summaries, headings, or rewrite direction grounded in the current context"],
1477
+ ["aiq_c_prepedits", "Prepare reviewable edits"],
1478
+ ["aiq_c_previewinline", "Preview inline changes, apply structured plans, or turn responses into review-ready suggestions"],
1479
+ ["aiq_c_usestarters", "Use starters or type your own"],
1480
+ ["aiq_c_choosecurated", "Choose one of the curated quick starts above or write a custom prompt in the composer below"],
1481
+ ["aiq_c_compose", "Compose"],
1482
+ ["aiq_c_readyask", "Ready to ask about the current document"],
1483
+ ["aiq_c_entersends", "Enter sends"],
1484
+ ["aiq_c_composeactions", "Compose actions"],
1485
+ ["aiq_c_openask", "Open Ask AI"],
1486
+ ["aiq_c_senddisabled", "Send disabled"],
1487
+ ["aiq_c_responseready", "AI response ready"],
1488
+ ["aiq_c_replyready", "Reply ready"],
1489
+ ["aiq_c_textchange", "This is a text change"],
1490
+ ["aiq_c_exchange", "AI chat exchange"],
1491
+ ["aiq_c_customex", "Custom exchange"],
1492
+ ["aiq_c_request", "Request"],
1493
+ ["aiq_c_curexchange", "Current exchange"],
1494
+ ["aiq_c_you", "You"],
1495
+ ["aiq_c_prompt", "Prompt"],
1496
+ ["aiq_c_promptsent", "Prompt sent"],
1497
+ ["aiq_c_waitingai", "Waiting on AI for the current document context"],
1498
+ ["aiq_c_whatyouasked", "What you asked"],
1499
+ ["aiq_c_usingdoc", "Using current document"],
1500
+ ["aiq_c_response", "Response"],
1501
+ ["aiq_c_answer", "Answer"],
1502
+ ["aiq_c_answerready", "Answer ready"],
1503
+ ["aiq_c_readanswer", "Read the answer grounded in the current document context"],
1504
+ ["aiq_c_copyonly", "Copy only"],
1505
+ ["aiq_c_copyonlygroup", "Copy only group"],
1506
+ ["aiq_c_copyprep", "Copy this prepared reply without changing the document"],
1507
+ ["aiq_c_copyprepai", "Copy this prepared AI reply without changing the document"],
1508
+ ["aiq_c_copytext", "Copy text"],
1509
+ ["aiq_c_actions", "Actions"],
1510
+ ["aiq_c_ai", "AI"]
1511
+ ];
1512
+ var AIQ_PATTERNS = [
1513
+ ["aiq_p_queue", new RegExp("^Queue (\\d+) of (\\d+)$")],
1514
+ ["aiq_p_step", new RegExp("^(\\d+) steps?$")],
1515
+ ["aiq_p_keeps", new RegExp("^Keeps (\\d+) words? across (\\d+) sentences?$")],
1516
+ ["aiq_p_queuein", new RegExp("^It is queue (\\d+) of (\\d+) in this view$")],
1517
+ ["aiq_p_enterfocuses", new RegExp("^Enter focuses (.+)$")],
1518
+ ["aiq_p_entercolon", new RegExp("^Enter: (.+)$")],
1519
+ ["aiq_p_focus", new RegExp("^[Ff]ocus (.+)$")],
1520
+ ["aiq_p_reviewopens", new RegExp("^Review opens on (.+)$")],
1521
+ ["aiq_p_openfocus", new RegExp("^Open this AI suggestion in review and focus (.+)$")],
1522
+ ["aiq_p_auses", new RegExp("^A uses (.+)$")],
1523
+ ["aiq_p_ruses", new RegExp("^R uses (.+)$")],
1524
+ ["aiq_p_exchanges", new RegExp("^(\\d+) exchanges?$")],
1525
+ ["aiq_p_messages", new RegExp("^(\\d+) messages?$")],
1526
+ ["aiq_p_words", new RegExp("^(\\d+) words?$")],
1527
+ ["aid_chars", new RegExp("^(\\d+) chars?$")],
1528
+ ["aiq_p_paragraphs", new RegExp("^(\\d+) paragraphs?$")],
1529
+ ["aic_pill_starters", new RegExp("^(\\d+) starters?$")],
1530
+ ["aic_pill_words", new RegExp("^(\\d+) words in scope$")],
1531
+ ["aiq_p_exchangen", new RegExp("^Exchange (\\d+)$")],
1532
+ ["aiq_p_turn", new RegExp("^Turn (\\d+)$")],
1533
+ ["aiq_p_convdoc", new RegExp("^(\\d+) exchanges?, (\\d+) messages? in this conversation for the current document$")],
1534
+ ["aiq_p_convsel", new RegExp("^(\\d+) exchanges?, (\\d+) messages? in this conversation for the current selection$")],
1535
+ ["aiq_p_threaddoc", new RegExp("^(\\d+) exchanges?, (\\d+) messages? in this document thread$")],
1536
+ ["aiq_p_threadsel", new RegExp("^(\\d+) exchanges?, (\\d+) messages? in this selection thread$")],
1537
+ ["aiq_p_responding", new RegExp("^Responding to (.+) request$")],
1538
+ ["aiq_p_actions", new RegExp("^Actions: (.+)$")]
1539
+ ];
1540
+ var aiqIndex = null;
1541
+ function aiqLocale() {
1542
+ var l = config && config.text_uilocale;
1543
+ return l && !/^en(-|$)/i.test(l) ? l : "";
1544
+ }
1545
+ function aiqCjk() { return /^(zh|ja)/i.test(aiqLocale()); }
1546
+ function aiqExact(s) {
1547
+ if (!aiqIndex) {
1548
+ aiqIndex = {};
1549
+ for (var i = 0; i < AIQ_EXACT.length; i++) {
1550
+ var en = AIQ_EXACT[i][1];
1551
+ if (!aiqIndex["=" + en]) aiqIndex["=" + en] = AIQ_EXACT[i][0];
1552
+ if (!aiqIndex["~" + en.toLowerCase()]) aiqIndex["~" + en.toLowerCase()] = AIQ_EXACT[i][0];
1553
+ }
1554
+ }
1555
+ var key = aiqIndex["=" + s] || aiqIndex["~" + s.toLowerCase()];
1556
+ return (key && config["text_" + key]) || null;
1557
+ }
1558
+ // a phrase without its closing stop: the catalog, then the patterns (a word capture must translate too)
1559
+ function aiqCore(s) {
1560
+ var v = aiqExact(s);
1561
+ if (v) return v;
1562
+ for (var i = 0; i < AIQ_PATTERNS.length; i++) {
1563
+ var m = AIQ_PATTERNS[i][1].exec(s);
1564
+ if (!m) continue;
1565
+ var tpl = config["text_" + AIQ_PATTERNS[i][0]];
1566
+ if (!tpl) return null;
1567
+ var out = tpl;
1568
+ for (var j = 1; j < m.length; j++) {
1569
+ var cap = m[j], tr = /^[0-9]+$/.test(cap) ? cap : aiqCore(cap);
1570
+ if (tr == null) return null;
1571
+ out = out.split("{" + (j - 1) + "}").join(tr);
1572
+ }
1573
+ return out;
1574
+ }
1575
+ return null;
1576
+ }
1577
+ function aiqPiece(piece) {
1578
+ var body = piece, punct = "";
1579
+ var last = body.charAt(body.length - 1);
1580
+ if (last === "." || last === ":") { punct = last; body = body.substring(0, body.length - 1); }
1581
+ var v = aiqCore(body);
1582
+ if (v == null) return null;
1583
+ if (punct) {
1584
+ var end = v.charAt(v.length - 1);
1585
+ var closed = ".:!?".indexOf(end) >= 0 || end === String.fromCharCode(0x3002) || end === String.fromCharCode(0xff1a);
1586
+ if (!closed) v += aiqCjk() ? String.fromCharCode(punct === ":" ? 0xff1a : 0x3002) : punct;
1587
+ }
1588
+ return v;
1589
+ }
1590
+ function aiqText(text) {
1591
+ var t = String(text == null ? "" : text).replace(/^\s+|\s+$/g, "");
1592
+ if (!t || !/[A-Za-z]/.test(t)) return null;
1593
+ var whole = aiqPiece(t);
1594
+ if (whole != null) return whole;
1595
+ var dash = t.indexOf(" - ");
1596
+ if (dash > 0) {
1597
+ var a = aiqText(t.substring(0, dash)), b = aiqText(t.substring(dash + 3));
1598
+ if (a != null || b != null) return (a != null ? a : t.substring(0, dash)) + " - " + (b != null ? b : t.substring(dash + 3));
1599
+ }
1600
+ var pieces = t.match(/[^.:!?]+[.:!?]*/g);
1601
+ if (!pieces || pieces.length < 2) return null;
1602
+ var out = "", prevTranslated = false, changed = false;
1603
+ for (var i = 0; i < pieces.length; i++) {
1604
+ var p = pieces[i].replace(/^\s+|\s+$/g, "");
1605
+ if (!p) continue;
1606
+ var tr = aiqPiece(p);
1607
+ if (tr != null) changed = true;
1608
+ var local = tr != null || !/[A-Za-z]/.test(p);
1609
+ var cur = tr != null ? tr : p;
1610
+ out += !out ? cur : ((local && prevTranslated && aiqCjk()) ? "" : " ") + cur;
1611
+ prevTranslated = local;
1612
+ }
1613
+ return changed ? out : null;
1614
+ }
1615
+ var aiqCjkStop = null, aiqCjkGap = null;
1616
+ function aiqFinish(s) {
1617
+ if (s == null || !aiqCjk()) return s;
1618
+ if (!aiqCjkStop) {
1619
+ var cjk = String.fromCharCode(0x4e00) + "-" + String.fromCharCode(0x9fff) + String.fromCharCode(0x3040) + "-" + String.fromCharCode(0x30ff) + String.fromCharCode(0x201d) + String.fromCharCode(0x300d);
1620
+ aiqCjkStop = new RegExp("([" + cjk + "])\\.", "g");
1621
+ aiqCjkGap = new RegExp("([" + String.fromCharCode(0x3002) + String.fromCharCode(0xff1a) + "])\\s+(?=[^A-Za-z0-9\\s])", "g");
1622
+ }
1623
+ return s.replace(aiqCjkStop, "$1" + String.fromCharCode(0x3002)).replace(aiqCjkGap, "$1");
1624
+ }
1625
+ var AIQ_SKIP = ".rte-ai-inline-preview-compare-text, .rte-ai-chat-message-text, .rte-ai-chat-message-request-body, textarea, input, script, style, svg";
1626
+ function aiqLocalize(root) {
1627
+ if (!root || !aiqLocale()) return;
1628
+ var stack = [root];
1629
+ while (stack.length) {
1630
+ var el = stack.pop();
1631
+ if (el.nodeType !== 1) continue;
1632
+ if (el !== root && el.matches && el.matches(AIQ_SKIP)) continue;
1633
+ for (var a = 0; a < 3; a++) {
1634
+ var name = a === 0 ? "aria-label" : (a === 1 ? "title" : "placeholder");
1635
+ var val = el.getAttribute(name);
1636
+ if (val) { var tv = aiqFinish(aiqText(val) || val); if (tv !== val) el.setAttribute(name, tv); }
1637
+ }
1638
+ for (var c = el.firstChild; c; c = c.nextSibling) {
1639
+ if (c.nodeType === 1) stack.push(c);
1640
+ else if (c.nodeType === 3 && /[A-Za-z.]/.test(c.data)) {
1641
+ var tt = aiqFinish(aiqText(c.data) || c.data.replace(/^\s+|\s+$/g, ""));
1642
+ if (tt) {
1643
+ var lead = /^\s*/.exec(c.data)[0], trail = /\s*$/.exec(c.data)[0];
1644
+ if (lead + tt + trail !== c.data) c.data = lead + tt + trail;
1645
+ }
1646
+ }
1647
+ }
1648
+ }
1649
+ }
1650
+ // keep a root localized as the toolkit rebuilds it (idempotent: a translated string has nothing left to match)
1651
+ function aiqWatch(root, select) {
1652
+ if (!root || !aiqLocale() || root.__aiqWatched) return;
1653
+ root.__aiqWatched = true;
1654
+ var run = function () {
1655
+ if (!select) { aiqLocalize(root); return; }
1656
+ var list = root.querySelectorAll(select);
1657
+ for (var i = 0; i < list.length; i++) aiqLocalize(list[i]);
1658
+ };
1659
+ run();
1660
+ var win = root.ownerDocument && root.ownerDocument.defaultView;
1661
+ var MO = win && win.MutationObserver;
1662
+ if (!MO) return;
1663
+ var pending = false;
1664
+ new MO(function () {
1665
+ if (pending) return;
1666
+ pending = true;
1667
+ win.setTimeout(function () { pending = false; run(); }, 0);
1668
+ }).observe(root, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ["aria-label", "title", "placeholder"] });
1669
+ }
1670
+ function aiqWatchCards() {
1671
+ var editable = editor.getEditable ? editor.getEditable() : null;
1672
+ if (editable) aiqWatch(editable, ".rte-ai-inline-preview");
1673
+ }
1674
+
1140
1675
  function ensureSentence(text) {
1141
1676
  var clean = normalizeText(text);
1142
1677
  if (!clean) {
@@ -1157,6 +1692,24 @@ function RTE_Plugin_AIToolkit() {
1157
1692
  return normalizeText(language || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
1158
1693
  }
1159
1694
 
1695
+ // 2026-10-18 the Translate-to list names the built-in languages in the page's language (Intl.DisplayNames in the
1696
+ // pack's locale, text_uilocale) at the display site only; values and the language a request names are unchanged,
1697
+ // and an application's own label is kept. tools/i18n/ai-translate-languages.mjs
1698
+ var TRANSLATE_BUILTIN = { spanish: ["es", "Spanish"], french: ["fr", "French"], german: ["de", "German"], italian: ["it", "Italian"], portuguese: ["pt", "Portuguese"], japanese: ["ja", "Japanese"] };
1699
+ function getTranslateLanguageDisplay(entry) {
1700
+ var label = entry.label || entry.value;
1701
+ var builtIn = TRANSLATE_BUILTIN[normalizeLanguageValue(entry.value)];
1702
+ var locale = config && config.text_uilocale;
1703
+ if (!builtIn || label !== builtIn[1] || !locale || /^en(-|$)/i.test(locale)) return label;
1704
+ try {
1705
+ if (typeof Intl !== "undefined" && Intl.DisplayNames) {
1706
+ var n = new Intl.DisplayNames([locale], { type: "language" }).of(builtIn[0]);
1707
+ if (n && n !== builtIn[0]) return n;
1708
+ }
1709
+ } catch (e) { /* the English label */ }
1710
+ return label;
1711
+ }
1712
+
1160
1713
  function getTranslateLanguageLabel(language) {
1161
1714
  var normalized = normalizeLanguageValue(language);
1162
1715
  var languages = getTranslateLanguages();
@@ -1187,27 +1740,27 @@ function RTE_Plugin_AIToolkit() {
1187
1740
  function getSuggestionTypeLabel(type) {
1188
1741
  switch (getSuggestionTypeValue(type)) {
1189
1742
  case "proofread":
1190
- return "Proofread";
1743
+ return (obj.__aiT ? obj.__aiT("air_type_proofread", "Proofread") : "Proofread");
1191
1744
  case "rewrite":
1192
- return "Rewrite";
1745
+ return (obj.__aiT ? obj.__aiT("air_type_rewrite", "Rewrite") : "Rewrite");
1193
1746
  case "translate":
1194
- return "Translate";
1747
+ return (obj.__aiT ? obj.__aiT("air_type_translate", "Translate") : "Translate");
1195
1748
  case "justify":
1196
- return "Justify";
1749
+ return (obj.__aiT ? obj.__aiT("air_type_justify", "Justify") : "Justify");
1197
1750
  case "summarize":
1198
- return "Summarize";
1751
+ return (obj.__aiT ? obj.__aiT("air_type_summarize", "Summarize") : "Summarize");
1199
1752
  case "shorten":
1200
- return "Shorten";
1753
+ return (obj.__aiT ? obj.__aiT("air_type_shorten", "Shorten") : "Shorten");
1201
1754
  case "expand":
1202
- return "Expand";
1755
+ return (obj.__aiT ? obj.__aiT("air_type_expand", "Expand") : "Expand");
1203
1756
  case "comment":
1204
- return "Comment";
1757
+ return (obj.__aiT ? obj.__aiT("air_type_comment", "Comment") : "Comment");
1205
1758
  case "paragraph":
1206
- return "Paragraph";
1759
+ return (obj.__aiT ? obj.__aiT("air_type_paragraph", "Paragraph") : "Paragraph");
1207
1760
  case "chat":
1208
- return "Chat";
1761
+ return (obj.__aiT ? obj.__aiT("air_type_chat", "Chat") : "Chat");
1209
1762
  default:
1210
- return "Other";
1763
+ return (obj.__aiT ? obj.__aiT("air_type_other", "Other") : "Other");
1211
1764
  }
1212
1765
  }
1213
1766
 
@@ -1358,7 +1911,7 @@ function RTE_Plugin_AIToolkit() {
1358
1911
  if (options.redoNextLabel) {
1359
1912
  parts.push(options.redoNextLabel);
1360
1913
  }
1361
- return parts.join(". ");
1914
+ return joinSentences(parts);
1362
1915
  }
1363
1916
 
1364
1917
  function buildSuggestionSourceLabel(type, snapshot, language, scope) {
@@ -1383,11 +1936,11 @@ function RTE_Plugin_AIToolkit() {
1383
1936
  var languageValue = getTranslateLanguageValue(language);
1384
1937
  var languageLabel = getTranslateLanguageLabel(languageValue);
1385
1938
  var leadMap = {
1386
- spanish: "Version en español",
1387
- french: "Version française",
1939
+ spanish: "Version en espa\xf1ol",
1940
+ french: "Version fran\xe7aise",
1388
1941
  german: "Deutsche Fassung",
1389
1942
  italian: "Versione italiana",
1390
- portuguese: "Versão em português",
1943
+ portuguese: "Vers\xe3o em portugu\xeas",
1391
1944
  japanese: "Japanese draft"
1392
1945
  };
1393
1946
  var lead = leadMap[languageValue] || (languageLabel + " version");
@@ -1402,20 +1955,29 @@ function RTE_Plugin_AIToolkit() {
1402
1955
  }
1403
1956
 
1404
1957
  var words = clean.split(/\s+/);
1958
+ // 2026-10-18 an in-place edit ends like the selection it replaces: one that stops before the end of its
1959
+ // paragraph gains no closing stop, one that starts mid-sentence keeps its first letter. Proofreading "Hello world"
1960
+ // in "Hello world, this is a test." gave "Hello world., this is a test.". tools/ai/demo-proofread-fragment.mjs
1961
+ function finishEdit(text) {
1962
+ var s = normalizeText(text);
1963
+ if (!s) return s;
1964
+ if (!options.midStart) s = s.charAt(0).toUpperCase() + s.substring(1);
1965
+ if (!options.midEnd && !/[.!?]$/.test(s)) s += ".";
1966
+ return s;
1967
+ }
1405
1968
  switch (mode) {
1406
1969
  case "proofread":
1407
1970
  var proofed = clean
1408
1971
  .replace(/\s{2,}/g, " ")
1409
1972
  .replace(/\s+([,.;:!?])/g, "$1")
1410
1973
  .replace(/([,.;:!?])([^\s"'\)\]])/g, "$1 $2")
1411
- .replace(/\.{2,}(?!\.)/g, ".")
1412
- .replace(/^(.)/, function (m) { return m.toUpperCase(); });
1413
- return ensureSentence(proofed);
1974
+ .replace(/\.{2,}(?!\.)/g, ".");
1975
+ return finishEdit(proofed);
1414
1976
  case "rewrite":
1415
- return ensureSentence(clean
1416
- .replace(/\bvery\s+/gi, "")
1417
- .replace(/\breally\s+/gi, "")
1418
- .replace(/\bjust\s+/gi, "")
1977
+ return finishEdit(clean
1978
+ .replace(/\bvery\b\s*/gi, "")
1979
+ .replace(/\breally\b\s*/gi, "")
1980
+ .replace(/\bjust\b\s*/gi, "")
1419
1981
  .replace(/\bin order to\b/gi, "to")
1420
1982
  .replace(/\bdue to the fact that\b/gi, "because")
1421
1983
  .replace(/\s{2,}/g, " "));
@@ -1445,7 +2007,7 @@ function RTE_Plugin_AIToolkit() {
1445
2007
  if (!clean) {
1446
2008
  return "Additional paragraph: This section adds a concise supporting explanation so the reader gets more context without losing momentum.";
1447
2009
  }
1448
- return ensureSentence("Additional paragraph: This section adds a concise supporting explanation that reinforces " + clean.split(/\s+/).slice(0, 10).join(" ") + " for the reader.");
2010
+ return ensureSentence("Additional paragraph: This section adds a concise supporting explanation that reinforces " + clean.split(/\s+/).slice(0, 10).join(" ").replace(/[.!?;:,]+$/, "") + " for the reader.");
1449
2011
  }
1450
2012
 
1451
2013
  function truncateText(text, maxLength) {
@@ -1457,7 +2019,9 @@ function RTE_Plugin_AIToolkit() {
1457
2019
  }
1458
2020
 
1459
2021
  function buildChatAnswer(prompt, source) {
1460
- var focus = truncateText(source, 160) || "the current document";
2022
+ // 2026-10-18 the quoted text loses its own closing stop, or the reply read "this is a test.. Ask me" and
2023
+ // "test., I would". tools/ai/aria-double-period.mjs
2024
+ var focus = String(truncateText(source, 160) || "").replace(/[ \t\r\n.!?;:,]+$/, "") || "the current document";
1461
2025
  if (/\b(headings?|titles?)\b/.test(prompt)) {
1462
2026
  return "You could tighten the structure by using shorter headings that mirror the main claims in " + focus + ". Ask me to draft heading options and I can turn them into reviewable suggestions.";
1463
2027
  }
@@ -1996,7 +2560,7 @@ function RTE_Plugin_AIToolkit() {
1996
2560
  if (unavailableReason) {
1997
2561
  parts.push(unavailableReason);
1998
2562
  }
1999
- button.setAttribute("aria-label", parts.join(". "));
2563
+ button.setAttribute("aria-label", joinSentences(parts));
2000
2564
  button.title = detail
2001
2565
  ? (label || "Action") + " - " + detail + (unavailableReason ? " " + unavailableReason : "")
2002
2566
  : (label || "Action") + (unavailableReason ? " - " + unavailableReason : "");
@@ -2011,13 +2575,34 @@ function RTE_Plugin_AIToolkit() {
2011
2575
  }, action, request);
2012
2576
  }
2013
2577
 
2578
+ // Whether a selection starts mid-sentence (text before it in its block that does not end a sentence) and ends
2579
+ // before its block does (text after it). Only for a selection; a document-wide request is a whole text.
2580
+ function selectionSentenceContext(request) {
2581
+ var out = { midStart: false, midEnd: false };
2582
+ try {
2583
+ var snap = request && request.snapshot;
2584
+ var r = snap && snap.hasSelection && request.scope !== "document" ? snap.range : null;
2585
+ if (!r) return out;
2586
+ var doc = r.startContainer.ownerDocument;
2587
+ var blockOf = function (n) {
2588
+ var el = n && n.nodeType === 1 ? n : (n && n.parentNode);
2589
+ while (el && el.nodeType === 1 && !/^(P|LI|TD|TH|H[1-6]|DIV|BLOCKQUOTE|PRE|BODY)$/.test(el.nodeName)) el = el.parentNode;
2590
+ return el && el.nodeType === 1 ? el : null;
2591
+ };
2592
+ var b1 = blockOf(r.startContainer), b2 = blockOf(r.endContainer);
2593
+ if (b1) { var pre = doc.createRange(); pre.selectNodeContents(b1); pre.setEnd(r.startContainer, r.startOffset); var before = pre.toString(); out.midStart = /\S/.test(before) && !/[.!?]["'\u201d)\]]*\s*$/.test(before); }
2594
+ if (b2) { var post = doc.createRange(); post.selectNodeContents(b2); post.setStart(r.endContainer, r.endOffset); out.midEnd = /\S/.test(post.toString()); }
2595
+ } catch (e) { /* a whole text */ }
2596
+ return out;
2597
+ }
2598
+
2014
2599
  function defaultResolveAction(request) {
2015
2600
  switch (request.mode) {
2016
2601
  case "proofread":
2017
2602
  var proofReason = "Normalizes spacing, punctuation, and capitalization while keeping the original meaning intact.";
2018
2603
  return {
2019
2604
  operations: [
2020
- buildLegacyOperation(request.action && request.action.target ? request.action.target : "selection-preview", buildDemoResult("proofread", request.source), { reason: proofReason }, request, request.action)
2605
+ buildLegacyOperation(request.action && request.action.target ? request.action.target : "selection-preview", buildDemoResult("proofread", request.source, selectionSentenceContext(request)), { reason: proofReason }, request, request.action)
2021
2606
  ],
2022
2607
  reason: proofReason
2023
2608
  };
@@ -2025,7 +2610,7 @@ function RTE_Plugin_AIToolkit() {
2025
2610
  var rewriteReason = "Tightens the wording by removing filler phrases so the point comes through faster.";
2026
2611
  return {
2027
2612
  operations: [
2028
- buildLegacyOperation(request.action && request.action.target ? request.action.target : "selection-preview", buildDemoResult("rewrite", request.source), { reason: rewriteReason }, request, request.action)
2613
+ buildLegacyOperation(request.action && request.action.target ? request.action.target : "selection-preview", buildDemoResult("rewrite", request.source, selectionSentenceContext(request)), { reason: rewriteReason }, request, request.action)
2029
2614
  ],
2030
2615
  reason: rewriteReason
2031
2616
  };
@@ -2048,7 +2633,7 @@ function RTE_Plugin_AIToolkit() {
2048
2633
  operations: [
2049
2634
  {
2050
2635
  type: "preview-suggestion",
2051
- text: buildDemoResult("rewrite", request.source),
2636
+ text: buildDemoResult("rewrite", request.source, selectionSentenceContext(request)),
2052
2637
  reason: "This rewrite reduces filler language, clarifies the main point, and makes the sentence easier to review before accepting."
2053
2638
  }
2054
2639
  ],
@@ -2534,6 +3119,8 @@ function RTE_Plugin_AIToolkit() {
2534
3119
  scope: suggestion.snapshot && suggestion.snapshot.hasSelection ? "selection" : "document",
2535
3120
  text: suggestion.text || "",
2536
3121
  blockPath: suggestion.blockPath || "",
3122
+ mentions: cleanLedgerMentions(suggestion.mentions),
3123
+ reactions: cleanLedgerReactions(suggestion.reactions),
2537
3124
  replies: Array.isArray(suggestion.replies) ? suggestion.replies : []
2538
3125
  };
2539
3126
  }
@@ -2548,7 +3135,7 @@ function RTE_Plugin_AIToolkit() {
2548
3135
  if (!html) return "";
2549
3136
  html = String(html);
2550
3137
  if (typeof editor.sanitizeHtml === "function") html = editor.sanitizeHtml(html);
2551
- var doc = new DOMParser().parseFromString("<body>" + html + "</body>", "text/html");
3138
+ var doc = new DOMParser().parseFromString(__RTE_TT("<body>" + html + "</body>"), "text/html");
2552
3139
  var styled = doc.body.querySelectorAll("[style]");
2553
3140
  for (var i = 0; i < styled.length; i++) {
2554
3141
  var st = styled[i].style;
@@ -2560,6 +3147,33 @@ function RTE_Plugin_AIToolkit() {
2560
3147
  }
2561
3148
  return doc.body.innerHTML;
2562
3149
  }
3150
+ // 2026-10-12 comment mentions: the { id, name } pairs a comment names, kept with the comment in the shared store.
3151
+ function cleanLedgerMentions(list) {
3152
+ var out = [];
3153
+ if (!Array.isArray(list)) return out;
3154
+ for (var i = 0; i < list.length; i++) {
3155
+ var m = list[i];
3156
+ if (m && m.id != null && m.name) out.push({ id: String(m.id), name: String(m.name) });
3157
+ }
3158
+ return out;
3159
+ }
3160
+ // 2026-10-15 comment reactions: [{ emoji, users: [id] }], kept with the comment like mentions.
3161
+ function cleanLedgerReactions(list) {
3162
+ var out = [];
3163
+ if (!Array.isArray(list)) return out;
3164
+ for (var i = 0; i < list.length && out.length < 20; i++) {
3165
+ var r = list[i];
3166
+ if (!r || typeof r.emoji !== "string" || !r.emoji || r.emoji.length > 8 || !Array.isArray(r.users)) continue;
3167
+ var users = [];
3168
+ for (var j = 0; j < r.users.length; j++) {
3169
+ var u = r.users[j] == null ? "" : String(r.users[j]);
3170
+ if (u && users.indexOf(u) < 0) users.push(u);
3171
+ }
3172
+ if (users.length) out.push({ emoji: r.emoji, users: users });
3173
+ }
3174
+ return out;
3175
+ }
3176
+
2563
3177
 
2564
3178
  function normalizeLedgerEntry(raw) {
2565
3179
  if (!raw || !raw.id) return null;
@@ -2592,6 +3206,8 @@ function RTE_Plugin_AIToolkit() {
2592
3206
  sourceLabel: raw.sourceLabel || "",
2593
3207
  snapshot: raw.snapshot || { hasSelection: raw.scope === "selection" },
2594
3208
  text: raw.text || "",
3209
+ mentions: cleanLedgerMentions(raw.mentions),
3210
+ reactions: cleanLedgerReactions(raw.reactions),
2595
3211
  blockPath: raw.blockPath || "",
2596
3212
  replies: Array.isArray(raw.replies) ? raw.replies : []
2597
3213
  };
@@ -2634,6 +3250,8 @@ function RTE_Plugin_AIToolkit() {
2634
3250
  hasSelection: raw.scope === "selection"
2635
3251
  },
2636
3252
  text: raw.text || "",
3253
+ mentions: cleanLedgerMentions(raw.mentions),
3254
+ reactions: cleanLedgerReactions(raw.reactions),
2637
3255
  blockPath: raw.blockPath || "",
2638
3256
  replies: Array.isArray(raw.replies) ? raw.replies : []
2639
3257
  };
@@ -2689,6 +3307,8 @@ function RTE_Plugin_AIToolkit() {
2689
3307
  createdAt: suggestion.createdAt || 0,
2690
3308
  decidedAt: suggestion.decidedAt || 0,
2691
3309
  sourceLabel: suggestion.sourceLabel || "",
3310
+ mentions: cleanLedgerMentions(suggestion.mentions),
3311
+ reactions: cleanLedgerReactions(suggestion.reactions),
2692
3312
  snapshot: {
2693
3313
  hasSelection: !!(suggestion.snapshot && suggestion.snapshot.hasSelection)
2694
3314
  }
@@ -2988,7 +3608,7 @@ function RTE_Plugin_AIToolkit() {
2988
3608
 
2989
3609
  if (suggestion.status === "pending") {
2990
3610
  if (resolvedWrapper) {
2991
- resolvedWrapper.outerHTML = createInlinePreviewHtml(suggestion);
3611
+ resolvedWrapper.outerHTML = __RTE_TT(createInlinePreviewHtml(suggestion));
2992
3612
  updated = true;
2993
3613
  }
2994
3614
  continue;
@@ -2997,7 +3617,7 @@ function RTE_Plugin_AIToolkit() {
2997
3617
  if (suggestion.status === "accepted" || suggestion.status === "rejected" || suggestion.status === "stale") {
2998
3618
  var resolvedHtml = getResolvedSuggestionHtml(suggestion, suggestion.status);
2999
3619
  if (previewWrapper) {
3000
- previewWrapper.outerHTML = resolvedHtml;
3620
+ previewWrapper.outerHTML = __RTE_TT(resolvedHtml);
3001
3621
  updated = true;
3002
3622
  }
3003
3623
  else if (resolvedWrapper && (
@@ -3006,7 +3626,7 @@ function RTE_Plugin_AIToolkit() {
3006
3626
  ? (suggestion.resultHtml || textToInlineHtml(suggestion.resultText || ""))
3007
3627
  : (suggestion.originalHtml || textToInlineHtml(suggestion.originalText || "")))
3008
3628
  )) {
3009
- resolvedWrapper.outerHTML = resolvedHtml;
3629
+ resolvedWrapper.outerHTML = __RTE_TT(resolvedHtml);
3010
3630
  updated = true;
3011
3631
  }
3012
3632
  }
@@ -3792,7 +4412,7 @@ function RTE_Plugin_AIToolkit() {
3792
4412
  }
3793
4413
 
3794
4414
  remoteChip.textContent = sharedUpdateCount > 1 ? sharedUpdateCount + " shared updates" : "Shared update";
3795
- remoteChip.setAttribute("aria-label", "Shared AI review updates");
4415
+ remoteChip.setAttribute("aria-label", uiT("aiu_sharedupdates", "Shared AI review updates"));
3796
4416
 
3797
4417
  var referenceNode = anchorNode && anchorNode.parentNode === wrapper ? anchorNode : (wrapper.querySelector ? wrapper.querySelector(".rte-ai-inline-preview-actions") : null);
3798
4418
  if (referenceNode && referenceNode.parentNode === wrapper) {
@@ -3988,7 +4608,7 @@ function RTE_Plugin_AIToolkit() {
3988
4608
  wrappers[i].appendChild(openedChip);
3989
4609
  }
3990
4610
  }
3991
- openedChip.textContent = "Queue opened";
4611
+ openedChip.textContent = uiT("aiu_queueopened", "Queue opened");
3992
4612
  openedChip.setAttribute("aria-label", "AI review queue opened");
3993
4613
  }
3994
4614
  else if (openedChip && openedChip.parentNode) {
@@ -4019,7 +4639,7 @@ function RTE_Plugin_AIToolkit() {
4019
4639
  wrappers[i].appendChild(focusChip);
4020
4640
  }
4021
4641
  }
4022
- focusChip.textContent = "Focus " + queueFocusLabel;
4642
+ focusChip.textContent = uiT("aiu_focus", "Focus {0}", aiqText(queueFocusLabel) || queueFocusLabel);
4023
4643
  focusChip.setAttribute("aria-label", "Queue opens on " + queueFocusLabel);
4024
4644
  }
4025
4645
  else if (focusChip && focusChip.parentNode) {
@@ -4105,9 +4725,9 @@ function RTE_Plugin_AIToolkit() {
4105
4725
  actionsNodeForSharedState.appendChild(sharedSeenButton);
4106
4726
  }
4107
4727
  }
4108
- sharedSeenButton.textContent = "Got it";
4109
- sharedSeenButton.setAttribute("aria-label", "Clear shared AI review update notice");
4110
- sharedSeenButton.title = "Clear shared AI review update notice";
4728
+ sharedSeenButton.textContent = uiT("aiu_gotit", "Got it");
4729
+ sharedSeenButton.setAttribute("aria-label", uiT("aiu_clearshared", "Clear shared AI review update notice"));
4730
+ sharedSeenButton.title = uiT("aiu_clearshared", "Clear shared AI review update notice");
4111
4731
  applyInlineReviewButtonShortcutState(sharedSeenButton, "Clear shared AI review update notice", inlineButtonShortcutOptions);
4112
4732
  }
4113
4733
  else if (sharedSeenButton && sharedSeenButton.parentNode) {
@@ -4163,8 +4783,8 @@ function RTE_Plugin_AIToolkit() {
4163
4783
  }
4164
4784
  }
4165
4785
  }
4166
- queueChip.textContent = "Item " + pendingPosition.index + " of " + pendingPosition.total;
4167
- queueChip.setAttribute("aria-label", "Current AI review queue position");
4786
+ queueChip.textContent = uiT("aiu_itemof", "Item {0} of {1}", pendingPosition.index, pendingPosition.total);
4787
+ queueChip.setAttribute("aria-label", uiT("aiu_queuepos", "Current AI review queue position"));
4168
4788
  }
4169
4789
  else if (queueChip && queueChip.parentNode) {
4170
4790
  queueChip.parentNode.removeChild(queueChip);
@@ -4240,7 +4860,7 @@ function RTE_Plugin_AIToolkit() {
4240
4860
  wrappers[i].appendChild(undoNextChip);
4241
4861
  }
4242
4862
  }
4243
- undoNextChip.textContent = "Then " + undoInlineNextItem.label;
4863
+ undoNextChip.textContent = uiT("aiu_then", "Then {0}", aiqText(undoInlineNextItem.label) || undoInlineNextItem.label);
4244
4864
  undoNextChip.setAttribute("aria-label", "Undo after this: " + undoInlineNextItem.detail);
4245
4865
  }
4246
4866
  else if (undoNextChip && undoNextChip.parentNode) {
@@ -4276,7 +4896,7 @@ function RTE_Plugin_AIToolkit() {
4276
4896
  wrappers[i].appendChild(redoNextChip);
4277
4897
  }
4278
4898
  }
4279
- redoNextChip.textContent = "Then " + redoInlineNextItem.label;
4899
+ redoNextChip.textContent = uiT("aiu_then", "Then {0}", aiqText(redoInlineNextItem.label) || redoInlineNextItem.label);
4280
4900
  redoNextChip.setAttribute("aria-label", "Redo after this: " + redoInlineNextItem.detail);
4281
4901
  }
4282
4902
  else if (redoNextChip && redoNextChip.parentNode) {
@@ -4295,7 +4915,7 @@ function RTE_Plugin_AIToolkit() {
4295
4915
  }
4296
4916
  }
4297
4917
  shortcutsChip.textContent = getSuggestionQueueShortcutHint(suggestion);
4298
- shortcutsChip.setAttribute("aria-label", "Inline queue preview shortcut");
4918
+ shortcutsChip.setAttribute("aria-label", uiT("aiu_previewshortcut", "Inline queue preview shortcut"));
4299
4919
  }
4300
4920
  else if (!shortcutsChip) {
4301
4921
  shortcutsChip = document.createElement("span");
@@ -4475,7 +5095,7 @@ function RTE_Plugin_AIToolkit() {
4475
5095
  wrappers[i].appendChild(focusChip);
4476
5096
  }
4477
5097
  }
4478
- focusChip.textContent = "Focus " + reviewFocusLabel;
5098
+ focusChip.textContent = uiT("aiu_focus", "Focus {0}", aiqText(reviewFocusLabel) || reviewFocusLabel);
4479
5099
  focusChip.setAttribute("aria-label", "Review opens on " + reviewFocusLabel);
4480
5100
  }
4481
5101
  else if (focusChip && focusChip.parentNode) {
@@ -4942,13 +5562,13 @@ function RTE_Plugin_AIToolkit() {
4942
5562
  var bodyNode = ownerDocument.createElement("span");
4943
5563
  bodyNode.className = "rte-ai-inline-preview-shortcuts-body";
4944
5564
  shortcutsChip.appendChild(bodyNode);
4945
- var pieces = String(text || "").split(/(\s+·\s+|\s+-\s+)/);
5565
+ var pieces = String(text || "").split(/(\s+\xb7\s+|\s+-\s+)/);
4946
5566
  for (var pieceIndex = 0; pieceIndex < pieces.length; pieceIndex++) {
4947
5567
  var piece = pieces[pieceIndex];
4948
5568
  if (!piece) {
4949
5569
  continue;
4950
5570
  }
4951
- if (/^\s+[·-]\s+$/.test(piece)) {
5571
+ if (/^\s+[\xb7-]\s+$/.test(piece)) {
4952
5572
  var separator = ownerDocument.createElement("span");
4953
5573
  separator.className = "rte-ai-inline-preview-shortcuts-separator";
4954
5574
  separator.innerText = piece.replace(/^\s+|\s+$/g, "");
@@ -4987,17 +5607,17 @@ function RTE_Plugin_AIToolkit() {
4987
5607
  shortcutsChip.classList.toggle("is-preview", state === "preview");
4988
5608
  if (state === "preview") {
4989
5609
  renderInlineReviewShortcutDisplayContent(shortcutsChip, "Queue shortcut", getSuggestionQueueShortcutHint(options.suggestion));
4990
- shortcutsChip.setAttribute("aria-label", "Inline queue preview shortcut");
5610
+ shortcutsChip.setAttribute("aria-label", uiT("aiu_previewshortcut", "Inline queue preview shortcut"));
4991
5611
  return true;
4992
5612
  }
4993
5613
  if (state && state !== "wrapper") {
4994
5614
  shortcutsChip.classList.add("is-action");
4995
5615
  renderInlineReviewShortcutDisplayContent(shortcutsChip, "Keyboard guide", getInlineReviewActionShortcutHint(state, options));
4996
- shortcutsChip.setAttribute("aria-label", "Inline AI review action shortcuts");
5616
+ shortcutsChip.setAttribute("aria-label", uiT("aiu_actionshortcuts", "Inline AI review action shortcuts"));
4997
5617
  return true;
4998
5618
  }
4999
5619
  renderInlineReviewShortcutDisplayContent(shortcutsChip, "Keyboard guide", getInlineReviewWrapperShortcutHint(options));
5000
- shortcutsChip.setAttribute("aria-label", "Inline AI review shortcuts");
5620
+ shortcutsChip.setAttribute("aria-label", uiT("aiu_reviewshortcuts", "Inline AI review shortcuts"));
5001
5621
  return true;
5002
5622
  }
5003
5623
 
@@ -5141,13 +5761,13 @@ function RTE_Plugin_AIToolkit() {
5141
5761
  function getSuggestionStatusLabel(status) {
5142
5762
  switch (status) {
5143
5763
  case "accepted":
5144
- return "Accepted";
5764
+ return (obj.__aiT ? obj.__aiT("air_status_accepted", "Accepted") : "Accepted");
5145
5765
  case "rejected":
5146
- return "Rejected";
5766
+ return (obj.__aiT ? obj.__aiT("air_status_rejected", "Rejected") : "Rejected");
5147
5767
  case "stale":
5148
- return "Needs refresh";
5768
+ return (obj.__aiT ? obj.__aiT("air_status_stale", "Needs refresh") : "Needs refresh");
5149
5769
  default:
5150
- return "Pending";
5770
+ return (obj.__aiT ? obj.__aiT("air_status_pending", "Pending") : "Pending");
5151
5771
  }
5152
5772
  }
5153
5773
 
@@ -5407,7 +6027,7 @@ function RTE_Plugin_AIToolkit() {
5407
6027
  if (summaryData.detail) {
5408
6028
  parts.push(summaryData.detail);
5409
6029
  }
5410
- return parts.join(". ");
6030
+ return joinSentences(parts);
5411
6031
  }
5412
6032
 
5413
6033
  function createInlinePreviewActionSummaryHtml(previewState) {
@@ -5566,10 +6186,10 @@ function RTE_Plugin_AIToolkit() {
5566
6186
  }
5567
6187
  var ariaLabel = label ? (label + " actions") : "AI suggestion actions";
5568
6188
  if (detailText) {
5569
- ariaLabel += ". " + detailText;
6189
+ ariaLabel = joinSentences([ariaLabel, detailText]);
5570
6190
  }
5571
6191
  if (isCurrentGroup && badgeText) {
5572
- ariaLabel += ". Current lane. " + badgeText;
6192
+ ariaLabel = joinSentences([ariaLabel, "Current lane", badgeText]);
5573
6193
  }
5574
6194
  groupNode.setAttribute("aria-label", ariaLabel);
5575
6195
  }
@@ -5618,7 +6238,7 @@ function RTE_Plugin_AIToolkit() {
5618
6238
  button.setAttribute("aria-keyshortcuts", shortcutKey ? ("Enter Space " + shortcutKey) : "Enter Space");
5619
6239
  button.setAttribute("data-rte-ai-enter-target-label", "Enter");
5620
6240
  if (ariaLabel && !/current enter target/i.test(ariaLabel)) {
5621
- button.setAttribute("aria-label", ariaLabel + ". Current Enter target.");
6241
+ button.setAttribute("aria-label", joinSentences([ariaLabel, "Current Enter target."]));
5622
6242
  }
5623
6243
  if (title && !/enter or space activates this action/i.test(title)) {
5624
6244
  title += ". Enter or Space activates this action";
@@ -5627,7 +6247,7 @@ function RTE_Plugin_AIToolkit() {
5627
6247
  }
5628
6248
  }
5629
6249
  if (title && !/current enter target/i.test(title)) {
5630
- button.title = title + ". Current Enter target.";
6250
+ button.title = joinSentences([title, "Current Enter target."]);
5631
6251
  }
5632
6252
  }
5633
6253
  else {
@@ -5703,7 +6323,7 @@ function RTE_Plugin_AIToolkit() {
5703
6323
  labelNode.className = "rte-ai-inline-preview-reason-label";
5704
6324
  reasonNode.appendChild(labelNode);
5705
6325
  }
5706
- labelNode.textContent = "Why";
6326
+ labelNode.textContent = uiT("air_why", "Why");
5707
6327
  if (!copyNode) {
5708
6328
  copyNode = document.createElement("span");
5709
6329
  copyNode.className = "rte-ai-inline-preview-reason-copy";
@@ -6161,7 +6781,32 @@ function RTE_Plugin_AIToolkit() {
6161
6781
  + "</" + resolvedTag + ">";
6162
6782
  }
6163
6783
 
6784
+ // 2026-10-18 The card is inserted into the editing document - an iframe by default - but the toolkit's stylesheet
6785
+ // was linked into the host page only, so every card rendered as unstyled run-on text. The host page's copy is
6786
+ // linked into the editing document once (never into the saved HTML). tools/ai/inline-card-styled.mjs
6787
+ function ensureCardStylesheet() {
6788
+ try {
6789
+ var editable = editor.getEditable ? editor.getEditable() : null;
6790
+ var doc = editable && editable.ownerDocument;
6791
+ if (!doc || doc === document || doc.querySelector("link[data-rte-ai-card-css]")) return;
6792
+ var src = document.querySelector("link[data-rte-auto-injected=aitoolkit]");
6793
+ if (!src) {
6794
+ var all = document.querySelectorAll("link[rel=stylesheet]");
6795
+ for (var i = 0; i < all.length; i++) {
6796
+ if (/(?:^|\/)aitoolkit(?:\.min)?\.css(?:[?#].*)?$/i.test(all[i].getAttribute("href") || "")) { src = all[i]; break; }
6797
+ }
6798
+ }
6799
+ if (!src || !src.href) return;
6800
+ var link = doc.createElement("link");
6801
+ link.rel = "stylesheet";
6802
+ link.href = src.href;
6803
+ link.setAttribute("data-rte-ai-card-css", "1");
6804
+ (doc.head || doc.documentElement).appendChild(link);
6805
+ } catch (e) { /* the card stays unstyled, as before */ }
6806
+ }
6807
+
6164
6808
  function createInlinePreviewHtml(previewState) {
6809
+ ensureCardStylesheet();
6165
6810
  var isBlock = !!previewState.isBlock;
6166
6811
  var wrapperTag = isBlock ? "div" : "span";
6167
6812
  var partTag = isBlock ? "div" : "span";
@@ -6239,6 +6884,7 @@ function RTE_Plugin_AIToolkit() {
6239
6884
  editor.__aiBindInlinePreviewEventHandlers();
6240
6885
  }
6241
6886
  editor.insertHTML(createInlinePreviewHtml(previewState));
6887
+ aiqWatchCards();
6242
6888
  persistSuggestionStore();
6243
6889
  emitReviewLogEvent("suggestion-created", previewState);
6244
6890
  if (editor.__aiReviewPanel && editor.__aiReviewPanel.isConnected) {
@@ -6290,7 +6936,7 @@ function RTE_Plugin_AIToolkit() {
6290
6936
  info.className = "rte-ai-menu-info is-minimal is-detached";
6291
6937
  var titleRow = append(info, "div", "", "rte-ai-menu-title-row");
6292
6938
  var titleIcon = append(titleRow, "span", "", "rte-ai-menu-title-icon");
6293
- titleIcon.innerHTML = config.svgCode_aiassist || "";
6939
+ titleIcon.innerHTML = __RTE_TT(config.svgCode_aiassist || "");
6294
6940
  append(titleRow, "div", "", "rte-ai-menu-title", config.text_aiassist || "Ask AI");
6295
6941
 
6296
6942
  // 2026-05-27 (v20260527a): type-to-filter search bar pinned to the
@@ -6322,12 +6968,12 @@ function RTE_Plugin_AIToolkit() {
6322
6968
  // still click anywhere in the input to focus the textbox.
6323
6969
  var searchIcon = append(searchRow, "span", "", "rte-ai-menu-search-icon");
6324
6970
  searchIcon.setAttribute("aria-hidden", "true");
6325
- searchIcon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6"/><path d="M19 19l-3.4-3.4"/></svg>';
6971
+ searchIcon.innerHTML = __RTE_TT('<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6"/><path d="M19 19l-3.4-3.4"/></svg>');
6326
6972
  var searchInput = panel.ownerDocument.createElement("input");
6327
6973
  searchInput.type = "text";
6328
6974
  searchInput.className = "rte-ai-menu-search-input";
6329
- searchInput.placeholder = "Filter actions…";
6330
- searchInput.setAttribute("aria-label", "Filter AI actions");
6975
+ searchInput.placeholder = (obj.__aiT ? obj.__aiT("aia_filter", "Filter actions\u2026") : "Filter actions\u2026");
6976
+ searchInput.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aia_filteraria", "Filter AI actions") : "Filter AI actions"));
6331
6977
  searchInput.setAttribute("autocomplete", "off");
6332
6978
  searchInput.setAttribute("spellcheck", "false");
6333
6979
  searchRow.appendChild(searchInput);
@@ -6387,10 +7033,10 @@ function RTE_Plugin_AIToolkit() {
6387
7033
  // 26px row floor and 2-col grid measure identically.
6388
7034
  if (action.description) {
6389
7035
  button.title = action.description;
6390
- button.setAttribute("aria-label", (action.title || action.id) + " — " + action.description);
7036
+ button.setAttribute("aria-label", (action.title || action.id) + " \u2014 " + action.description);
6391
7037
  }
6392
7038
  var icon = append(button, "span", "", "rte-ai-menu-item-icon");
6393
- icon.innerHTML = getActionIconSvg(action);
7039
+ icon.innerHTML = __RTE_TT(getActionIconSvg(action));
6394
7040
  var body = append(button, "span", "", "rte-ai-menu-item-body");
6395
7041
  append(body, "span", "", "rte-ai-menu-item-title", action.title || action.id);
6396
7042
  append(body, "span", "", "rte-ai-menu-item-copy", action.description || "");
@@ -6455,7 +7101,7 @@ function RTE_Plugin_AIToolkit() {
6455
7101
  if (!searchRow.__aiEmptyHint) {
6456
7102
  var hint = panel.ownerDocument.createElement("div");
6457
7103
  hint.className = "rte-ai-menu-empty-hint";
6458
- hint.textContent = "No actions match.";
7104
+ hint.textContent = (obj.__aiT ? obj.__aiT("aia_nomatch", "No actions match.") : "No actions match.");
6459
7105
  panel.appendChild(hint);
6460
7106
  searchRow.__aiEmptyHint = hint;
6461
7107
  }
@@ -6644,6 +7290,19 @@ function RTE_Plugin_AIToolkit() {
6644
7290
  });
6645
7291
  }
6646
7292
 
7293
+ // 2026-10-17 a locked document stays locked: in read-only mode (Ask AI opened through the API) and in comments-only
7294
+ // mode (its button stays enabled there) "Replace document", applyResult() and executeResolvedAction() rewrote the
7295
+ // document. Text-changing operations are refused and the refusal is announced; opening a panel and adding a
7296
+ // comment still work. tools/ai/ai-locked.mjs
7297
+ function documentLocked() {
7298
+ try { return !!((editor.getReadOnly && editor.getReadOnly()) || (editor.getCommentsOnly && editor.getCommentsOnly())); } catch (e) { return false; }
7299
+ }
7300
+
7301
+ function refuseLocked() {
7302
+ announceReviewStatus(config.text_aitoolkitlocked || "This document is read-only, so the AI result was not applied.");
7303
+ return false;
7304
+ }
7305
+
6647
7306
  function executeOperations(operations, options) {
6648
7307
  options = options || {};
6649
7308
  var snapshot = options.snapshot || captureSelectionSnapshot();
@@ -6680,6 +7339,7 @@ function RTE_Plugin_AIToolkit() {
6680
7339
  });
6681
7340
  }
6682
7341
 
7342
+ if (documentLocked() && !/^open-|comment/i.test(String(operation.type))) return refuseLocked();
6683
7343
  switch (operation.type) {
6684
7344
  case "open-chat-panel":
6685
7345
  openChatPanel({ focusComposer: true });
@@ -6742,7 +7402,7 @@ function RTE_Plugin_AIToolkit() {
6742
7402
  return false;
6743
7403
  }
6744
7404
 
6745
- wrapper.outerHTML = getResolvedSuggestionHtml(previewState, "accepted");
7405
+ wrapper.outerHTML = __RTE_TT(getResolvedSuggestionHtml(previewState, "accepted"));
6746
7406
  previewState.status = "accepted";
6747
7407
  previewState.decidedAt = new Date().getTime();
6748
7408
  setLastReviewDecision(previewState, "accepted");
@@ -6780,7 +7440,7 @@ function RTE_Plugin_AIToolkit() {
6780
7440
  return false;
6781
7441
  }
6782
7442
 
6783
- wrapper.outerHTML = getResolvedSuggestionHtml(previewState, "rejected");
7443
+ wrapper.outerHTML = __RTE_TT(getResolvedSuggestionHtml(previewState, "rejected"));
6784
7444
  previewState.status = "rejected";
6785
7445
  previewState.decidedAt = new Date().getTime();
6786
7446
  setLastReviewDecision(previewState, "rejected");
@@ -6816,7 +7476,7 @@ function RTE_Plugin_AIToolkit() {
6816
7476
 
6817
7477
  suggestion.status = "pending";
6818
7478
  suggestion.decidedAt = 0;
6819
- wrapper.outerHTML = createInlinePreviewHtml(suggestion);
7479
+ wrapper.outerHTML = __RTE_TT(createInlinePreviewHtml(suggestion));
6820
7480
  getReviewState().showResolved = false;
6821
7481
  pushRedoReviewDecision(suggestion, undoable.status);
6822
7482
  popLastReviewDecision();
@@ -6855,7 +7515,7 @@ function RTE_Plugin_AIToolkit() {
6855
7515
  return false;
6856
7516
  }
6857
7517
 
6858
- wrapper.outerHTML = getResolvedSuggestionHtml(suggestion, redoable.status);
7518
+ wrapper.outerHTML = __RTE_TT(getResolvedSuggestionHtml(suggestion, redoable.status));
6859
7519
  suggestion.status = redoable.status;
6860
7520
  suggestion.decidedAt = new Date().getTime();
6861
7521
  getReviewState().showResolved = false;
@@ -6891,6 +7551,7 @@ function RTE_Plugin_AIToolkit() {
6891
7551
  }
6892
7552
 
6893
7553
  function applyResult(result, options) {
7554
+ if (documentLocked()) return refuseLocked();
6894
7555
  var clean = normalizeText(result);
6895
7556
  if (!clean) {
6896
7557
  return false;
@@ -6903,7 +7564,9 @@ function RTE_Plugin_AIToolkit() {
6903
7564
  var snapshot = options.snapshot || captureSelectionSnapshot();
6904
7565
 
6905
7566
  if (mode === "document") {
6906
- editor.setHTMLCode(textToHtml(clean));
7567
+ // 2026-10-03 undoable: an AI rewrite of the whole document is one Ctrl+Z back to exactly
7568
+ // what was there, edits made just before included (tools/import/replace-undo-plugins.mjs)
7569
+ editor.setHTMLCode(textToHtml(clean), { undoable: true });
6907
7570
  syncSuggestionStates();
6908
7571
  persistSuggestionStore();
6909
7572
  if (editor.__aiReviewPanel && editor.__aiReviewPanel.isConnected) {
@@ -7116,7 +7779,7 @@ function RTE_Plugin_AIToolkit() {
7116
7779
  if (options.redoNextLabel) {
7117
7780
  parts.push(options.redoNextLabel);
7118
7781
  }
7119
- return parts.join(". ");
7782
+ return joinSentences(parts);
7120
7783
  }
7121
7784
 
7122
7785
  function buildReviewQueuePositionData(visibleSuggestions, activeSuggestionId, suggestion) {
@@ -7701,7 +8364,7 @@ function RTE_Plugin_AIToolkit() {
7701
8364
  || "Locate";
7702
8365
  actionNode.innerText = focusLabel;
7703
8366
  if (detailNode) {
7704
- detailNode.innerText = "Press Enter to reopen this suggestion and land on " + focusLabel + ".";
8367
+ detailNode.innerText = uiT("aiu_reopenhint", "Press Enter to reopen this suggestion and land on {0}.", aiqText(focusLabel) || focusLabel);
7705
8368
  }
7706
8369
  if (item && item.setAttribute) {
7707
8370
  item.setAttribute("data-rte-ai-review-open-action-label", focusLabel);
@@ -7727,7 +8390,7 @@ function RTE_Plugin_AIToolkit() {
7727
8390
  if (!titleNode) {
7728
8391
  titleNode = append(moduleNode, "span", "", "rte-ai-review-item-condensed-shortcuts-title");
7729
8392
  }
7730
- titleNode.textContent = "More keys";
8393
+ titleNode.textContent = uiT("aiu_morekeys", "More keys");
7731
8394
  var bodyNode = moduleNode.querySelector ? moduleNode.querySelector(".rte-ai-review-item-condensed-shortcuts-body") : null;
7732
8395
  if (!bodyNode) {
7733
8396
  bodyNode = append(moduleNode, "div", "", "rte-ai-review-shortcuts rte-ai-review-item-condensed-shortcuts-body");
@@ -7897,7 +8560,7 @@ function RTE_Plugin_AIToolkit() {
7897
8560
  parts.push(options.queueTransitionData.followupDetail);
7898
8561
  }
7899
8562
  }
7900
- return parts.join(". ");
8563
+ return joinSentences(parts);
7901
8564
  }
7902
8565
 
7903
8566
  function updateReviewCondensedPreviewAria(item, suggestion, panel) {
@@ -8007,7 +8670,7 @@ function RTE_Plugin_AIToolkit() {
8007
8670
  wrapper.appendChild(focusChip);
8008
8671
  }
8009
8672
  }
8010
- focusChip.textContent = "Focus " + reviewFocusLabel;
8673
+ focusChip.textContent = uiT("aiu_focus", "Focus {0}", aiqText(reviewFocusLabel) || reviewFocusLabel);
8011
8674
  focusChip.setAttribute("aria-label", "Review opens on " + reviewFocusLabel);
8012
8675
  }
8013
8676
  else if (focusChip && focusChip.parentNode) {
@@ -8222,7 +8885,7 @@ function RTE_Plugin_AIToolkit() {
8222
8885
  focusActionLabel: getReviewFocusActionDisplayLabel(findSuggestionById(nextOverallPendingId), preferredQueueFocusAction)
8223
8886
  } : null,
8224
8887
  undoAction,
8225
- { id: "all-types", label: "All types" },
8888
+ { id: "all-types", label: (obj.__aiT ? obj.__aiT("air_alltypes", "All types") : "All types") },
8226
8889
  hasResolved && !showResolved ? { id: "show-resolved", label: "Show resolved" } : null,
8227
8890
  { id: "open-chat", label: "AI Chat" }
8228
8891
  ])
@@ -8250,7 +8913,7 @@ function RTE_Plugin_AIToolkit() {
8250
8913
  announceMessage: "",
8251
8914
  actions: [
8252
8915
  undoAction,
8253
- typeFilter !== "all" ? { id: "all-types", label: "All types" } : null,
8916
+ typeFilter !== "all" ? { id: "all-types", label: (obj.__aiT ? obj.__aiT("air_alltypes", "All types") : "All types") } : null,
8254
8917
  hasResolved && !showResolved ? { id: "show-resolved", label: "Show resolved" } : null,
8255
8918
  { id: "open-chat", label: "AI Chat" }
8256
8919
  ]
@@ -9416,7 +10079,7 @@ function RTE_Plugin_AIToolkit() {
9416
10079
  if (contextCardData.copy) {
9417
10080
  parts.push(contextCardData.copy);
9418
10081
  }
9419
- return parts.join(". ");
10082
+ return joinSentences(parts);
9420
10083
  }
9421
10084
 
9422
10085
  function getChatMessageStateTitle(message) {
@@ -9629,7 +10292,7 @@ function RTE_Plugin_AIToolkit() {
9629
10292
  if (isRecommended) {
9630
10293
  parts.push("Recommended next step.");
9631
10294
  }
9632
- button.setAttribute("aria-label", parts.join(". "));
10295
+ button.setAttribute("aria-label", joinSentences(parts));
9633
10296
  button.title = detail
9634
10297
  ? label + " - " + detail + (isRecommended ? " Recommended next step." : "")
9635
10298
  : label + (isRecommended ? " - Recommended next step." : "");
@@ -9864,7 +10527,7 @@ function RTE_Plugin_AIToolkit() {
9864
10527
  if (actionLabels.length) {
9865
10528
  parts.push("Actions: " + actionLabels.join(", "));
9866
10529
  }
9867
- return parts.join(". ");
10530
+ return joinSentences(parts);
9868
10531
  }
9869
10532
 
9870
10533
  function syncChatActionAccessibility(actionsNode, recommendedActionId, message, currentSnapshot) {
@@ -9874,8 +10537,8 @@ function RTE_Plugin_AIToolkit() {
9874
10537
  var recommendedLabel = getChatRecommendedActionLabel(recommendedActionId);
9875
10538
  actionsNode.setAttribute("role", "group");
9876
10539
  actionsNode.setAttribute("aria-label", recommendedLabel
9877
- ? "AI chat actions. Recommended next step: " + recommendedLabel + "."
9878
- : "AI chat actions");
10540
+ ? (obj.__aiT ? obj.__aiT("aic_actionsrec", "AI chat actions. Recommended next step: {0}.", recommendedLabel) : "AI chat actions. Recommended next step: " + recommendedLabel + ".")
10541
+ : (obj.__aiT ? obj.__aiT("aic_actions", "AI chat actions") : "AI chat actions"));
9879
10542
  var groupNodes = actionsNode.querySelectorAll(".rte-ai-chat-message-action-group");
9880
10543
  for (var groupIndex = 0; groupIndex < groupNodes.length; groupIndex++) {
9881
10544
  var groupNode = groupNodes[groupIndex];
@@ -10001,7 +10664,7 @@ function RTE_Plugin_AIToolkit() {
10001
10664
  if (planItem.detail) {
10002
10665
  parts.push(planItem.detail);
10003
10666
  }
10004
- return parts.join(". ");
10667
+ return joinSentences(parts);
10005
10668
  }
10006
10669
 
10007
10670
  function buildChatPreviewPlanItems(message, recommendedActionId, currentSnapshot) {
@@ -10128,7 +10791,7 @@ function RTE_Plugin_AIToolkit() {
10128
10791
  parts.push("+" + resolvedPreview.hiddenPlanCount + " more");
10129
10792
  }
10130
10793
  }
10131
- return parts.join(". ");
10794
+ return joinSentences(parts);
10132
10795
  }
10133
10796
 
10134
10797
  function summarizeChatPromptCopy(text) {
@@ -10227,7 +10890,7 @@ function RTE_Plugin_AIToolkit() {
10227
10890
  if (message.text) {
10228
10891
  parts.push(message.text);
10229
10892
  }
10230
- return parts.join(". ");
10893
+ return joinSentences(parts);
10231
10894
  }
10232
10895
 
10233
10896
  function buildChatAssistantRequestCardAriaLabel(requestCardData, exchangeLabel) {
@@ -10250,7 +10913,7 @@ function RTE_Plugin_AIToolkit() {
10250
10913
  if (requestCardData.promptText) {
10251
10914
  parts.push(requestCardData.promptText);
10252
10915
  }
10253
- return parts.join(". ");
10916
+ return joinSentences(parts);
10254
10917
  }
10255
10918
 
10256
10919
  function buildChatMessageAriaLabel(messages, messageIndex, currentSnapshot) {
@@ -10322,7 +10985,7 @@ function RTE_Plugin_AIToolkit() {
10322
10985
  parts.push(actionSummary.title);
10323
10986
  }
10324
10987
  }
10325
- return parts.join(". ");
10988
+ return joinSentences(parts);
10326
10989
  }
10327
10990
 
10328
10991
  function buildChatMessageStateCardAriaLabel(message) {
@@ -10342,7 +11005,7 @@ function RTE_Plugin_AIToolkit() {
10342
11005
  if (stateDetail) {
10343
11006
  parts.push(stateDetail);
10344
11007
  }
10345
- return parts.join(". ");
11008
+ return joinSentences(parts);
10346
11009
  }
10347
11010
 
10348
11011
  function getChatMessageThreadRole(messages, messageIndex) {
@@ -10496,7 +11159,7 @@ function RTE_Plugin_AIToolkit() {
10496
11159
  if (bannerData.detail) {
10497
11160
  parts.push(bannerData.detail);
10498
11161
  }
10499
- return parts.join(". ");
11162
+ return joinSentences(parts);
10500
11163
  }
10501
11164
 
10502
11165
  function buildChatExchangeGroupAriaLabel(messages, requestIndex, currentSnapshot) {
@@ -10552,7 +11215,7 @@ function RTE_Plugin_AIToolkit() {
10552
11215
  if (bannerData && bannerData.isCurrent) {
10553
11216
  parts.push("Current exchange");
10554
11217
  }
10555
- return parts.join(". ");
11218
+ return joinSentences(parts);
10556
11219
  }
10557
11220
 
10558
11221
  function buildChatExchangeOutcomeData(messages, messageIndex, currentSnapshot) {
@@ -10636,7 +11299,7 @@ function RTE_Plugin_AIToolkit() {
10636
11299
  if (outcomeData.detail) {
10637
11300
  parts.push(outcomeData.detail);
10638
11301
  }
10639
- return parts.join(". ");
11302
+ return joinSentences(parts);
10640
11303
  }
10641
11304
 
10642
11305
  function getChatContextWordCount(text) {
@@ -10779,46 +11442,51 @@ function RTE_Plugin_AIToolkit() {
10779
11442
  }
10780
11443
  }
10781
11444
  }
10782
- return parts.join(". ");
11445
+ return joinSentences(parts);
10783
11446
  }
10784
11447
 
10785
11448
  function buildChatSendActionData(state, context, activePrompt) {
10786
11449
  var scopeLabel = context && context.scope === "selection" ? "selection" : "document";
11450
+ // 2026-10-18 every sentence reads a config.text_ key per scope (word order and gender differ by language);
11451
+ // the English fallbacks are the sentences this built before. tools/i18n/ai-chat-language.mjs
11452
+ var sc = scopeLabel === "selection" ? "sel" : "doc";
11453
+ var L = function (key, en, arg) { var s = (obj.__aiT ? obj.__aiT(key, en) : en); return arg === undefined ? s : s.split("{0}").join(String(arg)); };
11454
+ var LS = function (key, enSel, enDoc, arg) { return L(key + "_" + sc, sc === "sel" ? enSel : enDoc, arg); };
10787
11455
  var hasCustomDraft = !!normalizeText(state && state.draft ? state.draft : "");
10788
11456
  if (state && state.busy) {
10789
11457
  return {
10790
- text: "Thinking...",
11458
+ text: L("aic_thinking", "Thinking..."),
10791
11459
  disabled: true,
10792
- ariaLabel: "Send disabled while AI Chat is thinking about the current " + scopeLabel + ".",
10793
- title: "AI Chat is thinking about the current " + scopeLabel + "."
11460
+ ariaLabel: LS("aic_busyaria", "Send disabled while AI Chat is thinking about the current selection.", "Send disabled while AI Chat is thinking about the current document."),
11461
+ title: LS("aic_busytitle", "AI Chat is thinking about the current selection.", "AI Chat is thinking about the current document.")
10794
11462
  };
10795
11463
  }
10796
11464
  if (activePrompt && activePrompt.label) {
10797
11465
  return {
10798
- text: "Send starter",
11466
+ text: L("aic_sendstarter", "Send starter"),
10799
11467
  disabled: false,
10800
- ariaLabel: "Send starter. Send the " + activePrompt.label + " starter for the current " + scopeLabel + ".",
10801
- title: "Send the " + activePrompt.label + " starter for the current " + scopeLabel + "."
11468
+ ariaLabel: LS("aic_starteraria", "Send starter. Send the {0} starter for the current selection.", "Send starter. Send the {0} starter for the current document.", activePrompt.label),
11469
+ title: LS("aic_startertitle", "Send the {0} starter for the current selection.", "Send the {0} starter for the current document.", activePrompt.label)
10802
11470
  };
10803
11471
  }
10804
11472
  if (hasCustomDraft) {
10805
11473
  return {
10806
- text: "Send prompt",
11474
+ text: L("aic_sendprompt", "Send prompt"),
10807
11475
  disabled: false,
10808
- ariaLabel: "Send prompt. Send the custom prompt for the current " + scopeLabel + ".",
10809
- title: "Send the custom prompt for the current " + scopeLabel + "."
11476
+ ariaLabel: LS("aic_promptaria", "Send prompt. Send the custom prompt for the current selection.", "Send prompt. Send the custom prompt for the current document."),
11477
+ title: LS("aic_prompttitle", "Send the custom prompt for the current selection.", "Send the custom prompt for the current document.")
10810
11478
  };
10811
11479
  }
10812
11480
  return {
10813
- text: "Send",
11481
+ text: L("aic_send", "Send"),
10814
11482
  disabled: true,
10815
- ariaLabel: "Send disabled. Type a prompt or load a starter for the current " + scopeLabel + " first.",
10816
- title: "Type a prompt or load a starter for the current " + scopeLabel + " first."
11483
+ ariaLabel: LS("aic_emptyaria", "Send disabled. Type a prompt or load a starter for the current selection first.", "Send disabled. Type a prompt or load a starter for the current document first."),
11484
+ title: LS("aic_emptytitle", "Type a prompt or load a starter for the current selection first.", "Type a prompt or load a starter for the current document first.")
10817
11485
  };
10818
11486
  }
10819
11487
 
10820
11488
  function buildChatComposerActionsAriaLabel(sendActionData) {
10821
- return "Compose actions. Open Ask AI. " + (sendActionData && sendActionData.ariaLabel ? sendActionData.ariaLabel : "Send.");
11489
+ return (obj.__aiT ? obj.__aiT("aic_composeactions", "Compose actions. Open Ask AI.") : "Compose actions. Open Ask AI.") + " " + (sendActionData && sendActionData.ariaLabel ? sendActionData.ariaLabel : (obj.__aiT ? obj.__aiT("aic_sendp", "Send.") : "Send."));
10822
11490
  }
10823
11491
 
10824
11492
  function getLatestChatMessage(state) {
@@ -11032,7 +11700,7 @@ function RTE_Plugin_AIToolkit() {
11032
11700
  parts.push(summaryData.changeGlance.detail);
11033
11701
  }
11034
11702
  }
11035
- return parts.join(". ");
11703
+ return joinSentences(parts);
11036
11704
  }
11037
11705
 
11038
11706
  function buildChatFeedIntroData(state, context, activePrompt) {
@@ -11104,7 +11772,7 @@ function RTE_Plugin_AIToolkit() {
11104
11772
  if (feedIntroData.detail) {
11105
11773
  parts.push(feedIntroData.detail);
11106
11774
  }
11107
- return parts.join(". ");
11775
+ return joinSentences(parts);
11108
11776
  }
11109
11777
 
11110
11778
  function buildChatFeedSectionAriaLabel(state, context, activePrompt) {
@@ -11133,7 +11801,7 @@ function RTE_Plugin_AIToolkit() {
11133
11801
  if (summaryData && summaryData.detail) {
11134
11802
  parts.push(summaryData.detail);
11135
11803
  }
11136
- return parts.join(". ");
11804
+ return joinSentences(parts);
11137
11805
  }
11138
11806
  var feedIntroData = buildChatFeedIntroData(state, context, activePrompt);
11139
11807
  if (feedIntroData.title) {
@@ -11149,7 +11817,7 @@ function RTE_Plugin_AIToolkit() {
11149
11817
  if (feedIntroData.detail) {
11150
11818
  parts.push(feedIntroData.detail);
11151
11819
  }
11152
- return parts.join(". ");
11820
+ return joinSentences(parts);
11153
11821
  }
11154
11822
 
11155
11823
  function getChatConversationSummaryTitle(state, context) {
@@ -11256,7 +11924,7 @@ function RTE_Plugin_AIToolkit() {
11256
11924
  parts.push(titleText);
11257
11925
  }
11258
11926
  if (!headerData) {
11259
- return parts.join(". ");
11927
+ return joinSentences(parts);
11260
11928
  }
11261
11929
  if (headerData.statusLabel) {
11262
11930
  parts.push(headerData.statusLabel);
@@ -11274,7 +11942,7 @@ function RTE_Plugin_AIToolkit() {
11274
11942
  }
11275
11943
  }
11276
11944
  }
11277
- return parts.join(". ");
11945
+ return joinSentences(parts);
11278
11946
  }
11279
11947
 
11280
11948
  function buildChatContextData(context) {
@@ -11354,7 +12022,7 @@ function RTE_Plugin_AIToolkit() {
11354
12022
  if (contextData.previewSummary) {
11355
12023
  parts.push(contextData.previewSummary);
11356
12024
  }
11357
- return parts.join(". ");
12025
+ return joinSentences(parts);
11358
12026
  }
11359
12027
 
11360
12028
  function buildChatContextBarAriaLabel(contextData) {
@@ -11369,7 +12037,7 @@ function RTE_Plugin_AIToolkit() {
11369
12037
  }
11370
12038
  }
11371
12039
  }
11372
- return parts.join(". ");
12040
+ return joinSentences(parts);
11373
12041
  }
11374
12042
 
11375
12043
  function buildChatContextScopeButtonAriaLabel(scopeId, contextData) {
@@ -11405,7 +12073,7 @@ function RTE_Plugin_AIToolkit() {
11405
12073
  if (contextData.previewSummary) {
11406
12074
  parts.push(contextData.previewSummary);
11407
12075
  }
11408
- return parts.join(". ");
12076
+ return joinSentences(parts);
11409
12077
  }
11410
12078
 
11411
12079
  function buildChatQuickStartData(state, prompts, activePrompt, context) {
@@ -11458,7 +12126,7 @@ function RTE_Plugin_AIToolkit() {
11458
12126
  if (quickStartData.detail) {
11459
12127
  parts.push(quickStartData.detail);
11460
12128
  }
11461
- return parts.join(". ");
12129
+ return joinSentences(parts);
11462
12130
  }
11463
12131
 
11464
12132
  function buildChatQuickButtonAriaLabel(prompt, isActivePrompt, context) {
@@ -11477,7 +12145,7 @@ function RTE_Plugin_AIToolkit() {
11477
12145
  parts.push(isActivePrompt
11478
12146
  ? "Current starter loaded in Compose for the current " + scopeLabel
11479
12147
  : "Loads this starter into Compose for the current " + scopeLabel);
11480
- return parts.join(". ");
12148
+ return joinSentences(parts);
11481
12149
  }
11482
12150
 
11483
12151
  function buildChatQuickRowAriaLabel(context) {
@@ -11512,21 +12180,21 @@ function RTE_Plugin_AIToolkit() {
11512
12180
  // The capability-cards data is still attached to emptyStateData.aria
11513
12181
  // so screen-reader users hear the long form via the aria-label.
11514
12182
  return {
11515
- title: "Start with AI Chat",
11516
- detail: context.scope === "selection"
11517
- ? "Ask anything about the current selection."
11518
- : "Ask anything about the current document.",
12183
+ title: (obj.__aiT ? obj.__aiT("aic_starttitle", "Start with AI Chat") : "Start with AI Chat"),
12184
+ detail: (context.scope === "selection"
12185
+ ? (obj.__aiT ? obj.__aiT("aic_empty_sel", "Ask anything about the current selection") : "Ask anything about the current selection")
12186
+ : (obj.__aiT ? obj.__aiT("aic_empty_doc", "Ask anything about the current document") : "Ask anything about the current document")) + ".",
11519
12187
  metaPills: [
11520
12188
  {
11521
- text: context.scope === "selection" ? "Selection context" : "Document context",
12189
+ text: context.scope === "selection" ? (obj.__aiT ? obj.__aiT("aic_pill_sel", "Selection context") : "Selection context") : (obj.__aiT ? obj.__aiT("aic_pill_doc", "Document context") : "Document context"),
11522
12190
  className: "is-scope"
11523
12191
  },
11524
12192
  {
11525
- text: (prompts && prompts.length ? prompts.length : 0) + " starters",
12193
+ text: (obj.__aiT ? obj.__aiT("aic_pill_starters", "{0} starters", prompts && prompts.length ? prompts.length : 0) : (prompts && prompts.length ? prompts.length : 0) + " starters"),
11526
12194
  className: "is-prompts"
11527
12195
  },
11528
12196
  {
11529
- text: getChatContextWordCount(context.source) + " words in scope",
12197
+ text: (obj.__aiT ? obj.__aiT("aic_pill_words", "{0} words in scope", getChatContextWordCount(context.source)) : getChatContextWordCount(context.source) + " words in scope"),
11530
12198
  className: "is-size"
11531
12199
  }
11532
12200
  ],
@@ -11545,7 +12213,7 @@ function RTE_Plugin_AIToolkit() {
11545
12213
  if (capabilityCard.copy) {
11546
12214
  parts.push(capabilityCard.copy);
11547
12215
  }
11548
- return parts.join(". ");
12216
+ return joinSentences(parts);
11549
12217
  }
11550
12218
 
11551
12219
  function buildChatEmptyStateAriaLabel(emptyStateData) {
@@ -11571,7 +12239,7 @@ function RTE_Plugin_AIToolkit() {
11571
12239
  parts.push(buildChatEmptyCapabilityAriaLabel(emptyStateData.capabilityCards[cardIndex]));
11572
12240
  }
11573
12241
  }
11574
- return parts.join(". ");
12242
+ return joinSentences(parts);
11575
12243
  }
11576
12244
 
11577
12245
  function clearChatNode(node) {
@@ -11616,17 +12284,17 @@ function RTE_Plugin_AIToolkit() {
11616
12284
  }
11617
12285
  var headerStatusNode = panel.querySelector(".rte-ai-chat-header-status.is-status");
11618
12286
  if (headerStatusNode) {
11619
- headerStatusNode.innerHTML = "";
12287
+ headerStatusNode.innerHTML = __RTE_TT("");
11620
12288
  headerStatusNode.innerText = headerData.statusLabel;
11621
12289
  }
11622
12290
  var headerScopeNode = panel.querySelector(".rte-ai-chat-header-status.is-scope");
11623
12291
  if (headerScopeNode) {
11624
- headerScopeNode.innerHTML = "";
12292
+ headerScopeNode.innerHTML = __RTE_TT("");
11625
12293
  headerScopeNode.innerText = headerData.scopeFocusLabel;
11626
12294
  }
11627
12295
  var headerSubtitleNode = panel.querySelector(".rte-ai-chat-subtitle");
11628
12296
  if (headerSubtitleNode) {
11629
- headerSubtitleNode.innerHTML = "";
12297
+ headerSubtitleNode.innerHTML = __RTE_TT("");
11630
12298
  headerSubtitleNode.innerText = headerData.subtitle;
11631
12299
  }
11632
12300
  syncChatPillGroup(panel.querySelector(".rte-ai-chat-header-meta"), headerData.metaPills, "rte-ai-chat-header-meta-pill");
@@ -11667,7 +12335,7 @@ function RTE_Plugin_AIToolkit() {
11667
12335
  syncChatPillGroup(panel.querySelector(".rte-ai-chat-context-summary-meta"), contextData.summaryPills, "rte-ai-chat-context-summary-pill");
11668
12336
  var contextSummaryTextNode = panel.querySelector(".rte-ai-chat-context-summary-text");
11669
12337
  if (contextSummaryTextNode) {
11670
- contextSummaryTextNode.innerHTML = "";
12338
+ contextSummaryTextNode.innerHTML = __RTE_TT("");
11671
12339
  contextSummaryTextNode.innerText = contextData.detail;
11672
12340
  }
11673
12341
  var contextPreviewNode = panel.querySelector(".rte-ai-chat-context-preview");
@@ -11677,13 +12345,13 @@ function RTE_Plugin_AIToolkit() {
11677
12345
  }
11678
12346
  var contextPreviewTitleNode = panel.querySelector(".rte-ai-chat-context-preview-title");
11679
12347
  if (contextPreviewTitleNode) {
11680
- contextPreviewTitleNode.innerHTML = "";
12348
+ contextPreviewTitleNode.innerHTML = __RTE_TT("");
11681
12349
  contextPreviewTitleNode.innerText = contextData.previewTitle;
11682
12350
  }
11683
12351
  syncChatPillGroup(panel.querySelector(".rte-ai-chat-context-preview-meta"), contextData.previewMetaPills, "rte-ai-chat-context-preview-pill");
11684
12352
  var contextPreviewCopyNode = panel.querySelector(".rte-ai-chat-context-preview-copy");
11685
12353
  if (contextPreviewCopyNode) {
11686
- contextPreviewCopyNode.innerHTML = "";
12354
+ contextPreviewCopyNode.innerHTML = __RTE_TT("");
11687
12355
  contextPreviewCopyNode.innerText = contextData.previewSummary;
11688
12356
  }
11689
12357
 
@@ -11710,7 +12378,7 @@ function RTE_Plugin_AIToolkit() {
11710
12378
  syncChatPillGroup(panel.querySelector(".rte-ai-chat-quick-summary-meta"), quickStartData.metaPills, "rte-ai-chat-quick-summary-pill");
11711
12379
  var quickSummaryTextNode = panel.querySelector(".rte-ai-chat-quick-summary-text");
11712
12380
  if (quickSummaryTextNode) {
11713
- quickSummaryTextNode.innerHTML = "";
12381
+ quickSummaryTextNode.innerHTML = __RTE_TT("");
11714
12382
  quickSummaryTextNode.innerText = quickStartData.detail;
11715
12383
  }
11716
12384
 
@@ -11739,12 +12407,12 @@ function RTE_Plugin_AIToolkit() {
11739
12407
  var composerStatusPillNode = panel.querySelector(".rte-ai-chat-status-pill");
11740
12408
  if (composerStatusPillNode) {
11741
12409
  composerStatusPillNode.className = "rte-ai-chat-status-pill is-" + composerData.statusTone;
11742
- composerStatusPillNode.innerHTML = "";
12410
+ composerStatusPillNode.innerHTML = __RTE_TT("");
11743
12411
  composerStatusPillNode.innerText = composerData.statusLabel;
11744
12412
  }
11745
12413
  var composerStatusTextNode = panel.querySelector(".rte-ai-chat-status-text");
11746
12414
  if (composerStatusTextNode) {
11747
- composerStatusTextNode.innerHTML = "";
12415
+ composerStatusTextNode.innerHTML = __RTE_TT("");
11748
12416
  composerStatusTextNode.innerText = composerData.statusText;
11749
12417
  }
11750
12418
  var composerActionsNode = panel.querySelector(".rte-ai-chat-composer-actions");
@@ -11774,12 +12442,12 @@ function RTE_Plugin_AIToolkit() {
11774
12442
  }
11775
12443
  var feedIntroTitleNode = panel.querySelector(".rte-ai-chat-feed-intro-title");
11776
12444
  if (feedIntroTitleNode) {
11777
- feedIntroTitleNode.innerHTML = "";
12445
+ feedIntroTitleNode.innerHTML = __RTE_TT("");
11778
12446
  feedIntroTitleNode.innerText = feedIntroData.title;
11779
12447
  }
11780
12448
  var feedIntroDetailNode = panel.querySelector(".rte-ai-chat-feed-intro-detail");
11781
12449
  if (feedIntroDetailNode) {
11782
- feedIntroDetailNode.innerHTML = "";
12450
+ feedIntroDetailNode.innerHTML = __RTE_TT("");
11783
12451
  feedIntroDetailNode.innerText = feedIntroData.detail;
11784
12452
  }
11785
12453
  syncChatPillGroup(panel.querySelector(".rte-ai-chat-feed-intro-meta"), feedIntroData.metaPills, "rte-ai-chat-feed-intro-pill");
@@ -11790,12 +12458,12 @@ function RTE_Plugin_AIToolkit() {
11790
12458
  }
11791
12459
  var emptyTitleNode = panel.querySelector(".rte-ai-chat-empty-title");
11792
12460
  if (emptyTitleNode && emptyStateData) {
11793
- emptyTitleNode.innerHTML = "";
12461
+ emptyTitleNode.innerHTML = __RTE_TT("");
11794
12462
  emptyTitleNode.innerText = emptyStateData.title;
11795
12463
  }
11796
12464
  var emptyDetailNode = panel.querySelector(".rte-ai-chat-empty-detail");
11797
12465
  if (emptyDetailNode && emptyStateData) {
11798
- emptyDetailNode.innerHTML = "";
12466
+ emptyDetailNode.innerHTML = __RTE_TT("");
11799
12467
  emptyDetailNode.innerText = emptyStateData.detail;
11800
12468
  }
11801
12469
  syncChatPillGroup(panel.querySelector(".rte-ai-chat-empty-meta"), emptyStateData ? emptyStateData.metaPills : [], "rte-ai-chat-empty-pill");
@@ -11930,6 +12598,8 @@ function RTE_Plugin_AIToolkit() {
11930
12598
  panel.setAttribute("aria-label", config.text_aichat || "AI Chat");
11931
12599
  panel.tabIndex = -1;
11932
12600
  editor.__aiChatPanel = panel;
12601
+ aiqWatch(panel);
12602
+ appendDemoNotice(panel);
11933
12603
  panel.onkeydown = function (e) {
11934
12604
  if (e.key === "Escape") {
11935
12605
  e.preventDefault();
@@ -11955,7 +12625,7 @@ function RTE_Plugin_AIToolkit() {
11955
12625
  var headerCopy = append(header, "div", "", "rte-ai-chat-header-copy");
11956
12626
  var titleRow = append(headerCopy, "div", "", "rte-ai-chat-title-row");
11957
12627
  var titleIcon = append(titleRow, "span", "", "rte-ai-chat-title-icon");
11958
- titleIcon.innerHTML = config.svgCode_aiassist_chat || config.svgCode_aiassist || "";
12628
+ titleIcon.innerHTML = __RTE_TT(config.svgCode_aiassist_chat || config.svgCode_aiassist || "");
11959
12629
  append(titleRow, "div", "", "rte-ai-chat-title", headerTitleText);
11960
12630
  var headerActions = append(header, "div", "", "rte-ai-chat-header-actions");
11961
12631
  // 2026-05-15 (v20260515c): New chat button → icon-only. Sat next
@@ -11971,8 +12641,8 @@ function RTE_Plugin_AIToolkit() {
11971
12641
  var clearButton = append(headerActions, "button", "", "secondary rte-ai-chat-header-button is-icon-only");
11972
12642
  clearButton.type = "button";
11973
12643
  clearButton.setAttribute("data-rte-ai-chat-header-action", "new-chat");
11974
- clearButton.title = "Start a fresh chat";
11975
- clearButton.setAttribute("aria-label", "Start a fresh chat");
12644
+ clearButton.title = (obj.__aiT ? obj.__aiT("aic_newchat", "Start a fresh chat") : "Start a fresh chat");
12645
+ clearButton.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aic_newchat", "Start a fresh chat") : "Start a fresh chat"));
11976
12646
  setReviewV2ButtonContent(clearButton, "refresh", "");
11977
12647
  clearButton.onclick = function () {
11978
12648
  resetChatConversationState(state, "Started a fresh AI chat.");
@@ -11981,9 +12651,9 @@ function RTE_Plugin_AIToolkit() {
11981
12651
  var closeButton = append(headerActions, "button", "", "rte-ai-panel-close-button");
11982
12652
  closeButton.type = "button";
11983
12653
  closeButton.setAttribute("data-rte-ai-chat-header-action", "close");
11984
- closeButton.setAttribute("aria-label", "Close AI chat");
11985
- closeButton.title = "Close";
11986
- closeButton.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12"/><path d="M18 6L6 18"/></svg>';
12654
+ closeButton.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aic_closechat", "Close AI chat") : "Close AI chat"));
12655
+ closeButton.title = (obj.__aiT ? obj.__aiT("aic_close", "Close") : "Close");
12656
+ closeButton.innerHTML = __RTE_TT('<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12"/><path d="M18 6L6 18"/></svg>');
11987
12657
  closeButton.onclick = function () {
11988
12658
  closeChatPanel({ restoreFocus: true });
11989
12659
  };
@@ -12026,7 +12696,7 @@ function RTE_Plugin_AIToolkit() {
12026
12696
  selectionButton.disabled = !context.snapshot.hasSelection;
12027
12697
  selectionButton.setAttribute("data-rte-ai-chat-scope-target", "selection");
12028
12698
  selectionButton.setAttribute("aria-label", buildChatContextScopeButtonAriaLabel("selection", contextData));
12029
- selectionButton.title = "Use selection";
12699
+ selectionButton.title = (obj.__aiT ? obj.__aiT("aic_usesel", "Use selection") : "Use selection");
12030
12700
  setReviewV2ButtonContent(selectionButton, "cursorText", "");
12031
12701
  selectionButton.onclick = function () {
12032
12702
  var currentSnapshot = captureSelectionSnapshot();
@@ -12038,7 +12708,7 @@ function RTE_Plugin_AIToolkit() {
12038
12708
  documentButton.type = "button";
12039
12709
  documentButton.setAttribute("data-rte-ai-chat-scope-target", "document");
12040
12710
  documentButton.setAttribute("aria-label", buildChatContextScopeButtonAriaLabel("document", contextData));
12041
- documentButton.title = "Use document";
12711
+ documentButton.title = (obj.__aiT ? obj.__aiT("aic_usedoc", "Use document") : "Use document");
12042
12712
  setReviewV2ButtonContent(documentButton, "document", "");
12043
12713
  documentButton.onclick = function () {
12044
12714
  state.scope = "document";
@@ -12093,7 +12763,7 @@ function RTE_Plugin_AIToolkit() {
12093
12763
  if (recentSessions.length > 1) {
12094
12764
  var historySection = append(panel, "div", "", "rte-ai-chat-stack rte-ai-chat-history-shell is-minimal");
12095
12765
  historySection.setAttribute("role", "group");
12096
- historySection.setAttribute("aria-label", "Recent AI chats");
12766
+ historySection.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aic_recent", "Recent AI chats") : "Recent AI chats"));
12097
12767
  var historyRow = append(historySection, "div", "", "rte-ai-chat-quick-row rte-ai-chat-history-row");
12098
12768
  for (var s = 0; s < recentSessions.length; s++) {
12099
12769
  (function (session, index) {
@@ -12160,10 +12830,10 @@ function RTE_Plugin_AIToolkit() {
12160
12830
  // reference drops with the chips.
12161
12831
  var hasQuickStarts = !!(prompts && prompts.length);
12162
12832
  var emptyHintBase = context.scope === "selection"
12163
- ? "Ask anything about the current selection"
12164
- : "Ask anything about the current document";
12833
+ ? (obj.__aiT ? obj.__aiT("aic_empty_sel", "Ask anything about the current selection") : "Ask anything about the current selection")
12834
+ : (obj.__aiT ? obj.__aiT("aic_empty_doc", "Ask anything about the current document") : "Ask anything about the current document");
12165
12835
  append(emptyState, "div", "", "rte-ai-chat-empty-detail",
12166
- hasQuickStarts ? (emptyHintBase + " — or tap a quick start above.") : (emptyHintBase + "."));
12836
+ hasQuickStarts ? (emptyHintBase + (obj.__aiT ? obj.__aiT("aic_orquick", " \u2014 or tap a quick start above.") : " \u2014 or tap a quick start above.")) : (emptyHintBase + "."));
12167
12837
  }
12168
12838
  var activeExchangeGroup = null;
12169
12839
  var activeExchangeIndex = 0;
@@ -12300,11 +12970,11 @@ function RTE_Plugin_AIToolkit() {
12300
12970
  var avatarKind = message.isError ? "error" : message.role;
12301
12971
  var avatar = append(messageHeader, "span", "", "rte-ai-chat-message-avatar is-" + avatarKind);
12302
12972
  avatar.setAttribute("aria-hidden", "true");
12303
- avatar.innerHTML = avatarKind === "user"
12973
+ avatar.innerHTML = __RTE_TT(avatarKind === "user"
12304
12974
  ? '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"><circle cx="8" cy="5.5" r="2.8"/><path d="M8 9.4c-2.8 0-5.2 1.5-5.2 3.4 0 .4.3.7.7.7h9c.4 0 .7-.3.7-.7 0-1.9-2.4-3.4-5.2-3.4z"/></svg>'
12305
12975
  : (avatarKind === "error"
12306
12976
  ? '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="8" cy="8" r="6.5"/><path d="M8 5v3.5M8 11.2v.1"/></svg>'
12307
- : '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"><rect x="3" y="5" width="10" height="8" rx="2"/><circle cx="8" cy="3" r="0.9"/><line x1="8" y1="3.9" x2="8" y2="5" stroke="currentColor" stroke-width="1"/><circle cx="6" cy="9" r="1.1" fill="#ffffff"/><circle cx="10" cy="9" r="1.1" fill="#ffffff"/></svg>');
12977
+ : '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true"><rect x="3" y="5" width="10" height="8" rx="2"/><circle cx="8" cy="3" r="0.9"/><line x1="8" y1="3.9" x2="8" y2="5" stroke="currentColor" stroke-width="1"/><circle cx="6" cy="9" r="1.1" fill="#ffffff"/><circle cx="10" cy="9" r="1.1" fill="#ffffff"/></svg>'));
12308
12978
  append(messageHeader, "span", "", "rte-ai-chat-message-role is-" + (message.isError ? "error" : message.role), getChatMessageRoleLabel(message));
12309
12979
  append(item, "div", "", "rte-ai-chat-message-text", message.text || "");
12310
12980
  // 2026-05-08: per-message Copy + Regenerate row for assistant
@@ -12315,8 +12985,8 @@ function RTE_Plugin_AIToolkit() {
12315
12985
  var msgActions = append(item, "div", "", "rte-ai-chat-message-actions-row");
12316
12986
  var msgCopyBtn = append(msgActions, "button", "", "rte-ai-chat-message-action is-copy");
12317
12987
  msgCopyBtn.type = "button";
12318
- msgCopyBtn.title = "Copy message text";
12319
- setReviewV2ButtonContent(msgCopyBtn, "checkAll", "Copy");
12988
+ msgCopyBtn.title = (obj.__aiT ? obj.__aiT("aic_copymsg", "Copy message text") : "Copy message text");
12989
+ setReviewV2ButtonContent(msgCopyBtn, "checkAll", (obj.__aiT ? obj.__aiT("aic_copy", "Copy") : "Copy"));
12320
12990
  msgCopyBtn.onclick = (function (text, btn) {
12321
12991
  return function (e) {
12322
12992
  if (e && e.stopPropagation) e.stopPropagation();
@@ -12341,10 +13011,10 @@ function RTE_Plugin_AIToolkit() {
12341
13011
  }
12342
13012
  if (copied) {
12343
13013
  btn.classList.add("is-just-copied");
12344
- setReviewV2ButtonContent(btn, "check", "Copied");
13014
+ setReviewV2ButtonContent(btn, "check", (obj.__aiT ? obj.__aiT("aic_copied", "Copied") : "Copied"));
12345
13015
  setTimeout(function () {
12346
13016
  btn.classList.remove("is-just-copied");
12347
- setReviewV2ButtonContent(btn, "checkAll", "Copy");
13017
+ setReviewV2ButtonContent(btn, "checkAll", (obj.__aiT ? obj.__aiT("aic_copy", "Copy") : "Copy"));
12348
13018
  }, 1400);
12349
13019
  }
12350
13020
  };
@@ -12362,8 +13032,8 @@ function RTE_Plugin_AIToolkit() {
12362
13032
  if (precedingUserPrompt) {
12363
13033
  var msgRegenBtn = append(msgActions, "button", "", "rte-ai-chat-message-action is-regen");
12364
13034
  msgRegenBtn.type = "button";
12365
- msgRegenBtn.title = "Regenerate response";
12366
- setReviewV2ButtonContent(msgRegenBtn, "refresh", "Regenerate");
13035
+ msgRegenBtn.title = (obj.__aiT ? obj.__aiT("aic_regen", "Regenerate response") : "Regenerate response");
13036
+ setReviewV2ButtonContent(msgRegenBtn, "refresh", (obj.__aiT ? obj.__aiT("aid_regenerate", "Regenerate") : "Regenerate"));
12367
13037
  msgRegenBtn.onclick = (function (prompt) {
12368
13038
  return function (e) {
12369
13039
  if (e && e.stopPropagation) e.stopPropagation();
@@ -12622,7 +13292,7 @@ function RTE_Plugin_AIToolkit() {
12622
13292
  moreToggle.type = "button";
12623
13293
  moreToggle.setAttribute("data-rte-ai-chat-action", "more");
12624
13294
  moreToggle.setAttribute("aria-expanded", "false");
12625
- moreToggle.title = "More apply options";
13295
+ moreToggle.title = (obj.__aiT ? obj.__aiT("ai2_moreopts", "More apply options") : "More apply options");
12626
13296
  setReviewV2ButtonContent(moreToggle, "more", "More");
12627
13297
  moreToggle.onclick = (function (host) {
12628
13298
  return function (e) {
@@ -12711,8 +13381,8 @@ function RTE_Plugin_AIToolkit() {
12711
13381
  // 51 chars → 14 chars. Same shape Claude composer ("Ask
12712
13382
  // anything") / ChatGPT composer ("Message ChatGPT") / Notion AI
12713
13383
  // ("Tell AI what to do…") ship.
12714
- composerArea.placeholder = "Ask anything…";
12715
- composerArea.setAttribute("aria-label", "Compose next AI request for the current " + (context.scope === "selection" ? "selection" : "document"));
13384
+ composerArea.placeholder = (obj.__aiT ? obj.__aiT("aic_askanything", "Ask anything\u2026") : "Ask anything\u2026");
13385
+ composerArea.setAttribute("aria-label", context.scope === "selection" ? (obj.__aiT ? obj.__aiT("aic_compose_sel", "Compose next AI request for the current selection") : "Compose next AI request for the current selection") : (obj.__aiT ? obj.__aiT("aic_compose_doc", "Compose next AI request for the current document") : "Compose next AI request for the current document"));
12716
13386
  composerArea.value = state.draft || "";
12717
13387
  composerArea.disabled = !!state.busy;
12718
13388
  composerArea.oninput = function () {
@@ -12839,7 +13509,7 @@ function RTE_Plugin_AIToolkit() {
12839
13509
  }
12840
13510
 
12841
13511
  function getReviewTypeOptions(suggestions) {
12842
- var map = { all: "All types" };
13512
+ var map = { all: (obj.__aiT ? obj.__aiT("air_alltypes", "All types") : "All types") };
12843
13513
  for (var i = 0; i < suggestions.length; i++) {
12844
13514
  var typeValue = getSuggestionTypeValue(suggestions[i] && suggestions[i].suggestionType || "");
12845
13515
  if (!map[typeValue]) {
@@ -13782,7 +14452,7 @@ function RTE_Plugin_AIToolkit() {
13782
14452
  if (summaryData.detail) {
13783
14453
  parts.push(summaryData.detail);
13784
14454
  }
13785
- return parts.join(". ");
14455
+ return joinSentences(parts);
13786
14456
  }
13787
14457
 
13788
14458
  function syncActiveReviewActionSummary(preferredAction) {
@@ -14005,10 +14675,10 @@ function RTE_Plugin_AIToolkit() {
14005
14675
  if (isTarget) {
14006
14676
  button.setAttribute("data-rte-ai-review-enter-target-label", "Enter");
14007
14677
  if (ariaLabel && !/current enter target/i.test(ariaLabel)) {
14008
- button.setAttribute("aria-label", ariaLabel + ". Current Enter target.");
14678
+ button.setAttribute("aria-label", joinSentences([ariaLabel, "Current Enter target."]));
14009
14679
  }
14010
14680
  if (title && !/current enter target/i.test(title)) {
14011
- button.title = title + ". Current Enter target.";
14681
+ button.title = joinSentences([title, "Current Enter target."]);
14012
14682
  }
14013
14683
  }
14014
14684
  else {
@@ -14163,7 +14833,7 @@ function RTE_Plugin_AIToolkit() {
14163
14833
  if (prefixMatch) {
14164
14834
  var label = ownerDocument.createElement("span");
14165
14835
  label.className = "rte-ai-review-shortcuts-label";
14166
- label.innerText = "Shortcuts:";
14836
+ label.innerText = uiT("aiu_shortcuts", "Shortcuts:");
14167
14837
  node.appendChild(label);
14168
14838
  node.appendChild(ownerDocument.createTextNode(" "));
14169
14839
  }
@@ -14255,7 +14925,7 @@ function RTE_Plugin_AIToolkit() {
14255
14925
  titleNode.className = "rte-ai-review-item-shortcuts-title";
14256
14926
  moduleNode.appendChild(titleNode);
14257
14927
  }
14258
- titleNode.textContent = "Keyboard guide";
14928
+ titleNode.textContent = uiT("aiq_kbguide", "Keyboard guide");
14259
14929
  var bodyNode = moduleNode.querySelector ? moduleNode.querySelector(".rte-ai-review-item-shortcuts-body") : null;
14260
14930
  if (!bodyNode) {
14261
14931
  bodyNode = document.createElement("div");
@@ -14717,7 +15387,7 @@ function RTE_Plugin_AIToolkit() {
14717
15387
  var iconSpan = btn.ownerDocument.createElement("span");
14718
15388
  iconSpan.className = "rte-ai-review-v2-action-icon";
14719
15389
  iconSpan.setAttribute("aria-hidden", "true");
14720
- iconSpan.innerHTML = REVIEW_V2_ICONS[iconKey];
15390
+ iconSpan.innerHTML = __RTE_TT(REVIEW_V2_ICONS[iconKey]);
14721
15391
  btn.appendChild(iconSpan);
14722
15392
  }
14723
15393
  if (label) {
@@ -14787,15 +15457,15 @@ function RTE_Plugin_AIToolkit() {
14787
15457
  // Header
14788
15458
  var header = append(panel, "div", "", "rte-ai-review-v2-header");
14789
15459
  var iconWrap = append(header, "span", "", "rte-ai-review-v2-icon");
14790
- iconWrap.innerHTML = config.svgCode_aiassist_review || config.svgCode_aiassist || "";
15460
+ iconWrap.innerHTML = __RTE_TT(config.svgCode_aiassist_review || config.svgCode_aiassist || "");
14791
15461
  append(header, "div", "", "rte-ai-review-v2-title", config.text_aireview || "AI Review");
14792
15462
  // Pending count moved into the "To review" tab badge below; the
14793
15463
  // header pill would have duplicated that signal.
14794
15464
  var closeBtn = append(header, "button", "", "rte-ai-review-v2-close");
14795
15465
  closeBtn.type = "button";
14796
- closeBtn.setAttribute("aria-label", "Close AI review");
14797
- closeBtn.title = "Close";
14798
- closeBtn.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12"/><path d="M18 6L6 18"/></svg>';
15466
+ closeBtn.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("air_closearia", "Close AI review") : "Close AI review"));
15467
+ closeBtn.title = (obj.__aiT ? obj.__aiT("air_close", "Close") : "Close");
15468
+ closeBtn.innerHTML = __RTE_TT('<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12"/><path d="M18 6L6 18"/></svg>');
14799
15469
  closeBtn.onclick = function () {
14800
15470
  closeReviewPanel();
14801
15471
  editor.focus();
@@ -14819,7 +15489,7 @@ function RTE_Plugin_AIToolkit() {
14819
15489
  var iconSpan = tab.ownerDocument.createElement("span");
14820
15490
  iconSpan.className = "rte-ai-review-v2-tab-icon";
14821
15491
  iconSpan.setAttribute("aria-hidden", "true");
14822
- iconSpan.innerHTML = REVIEW_V2_ICONS[iconKey];
15492
+ iconSpan.innerHTML = __RTE_TT(REVIEW_V2_ICONS[iconKey]);
14823
15493
  tab.appendChild(iconSpan);
14824
15494
  }
14825
15495
  var labelSpan = tab.ownerDocument.createElement("span");
@@ -14840,9 +15510,9 @@ function RTE_Plugin_AIToolkit() {
14840
15510
  return tab;
14841
15511
  }
14842
15512
 
14843
- makeTab("pending", "inbox", "To review", pendingCountForTabs);
14844
- makeTab("resolved", "history", "History", resolvedCountForTabs);
14845
- makeTab("all", "filter", "All", counts.total);
15513
+ makeTab("pending", "inbox", (obj.__aiT ? obj.__aiT("air_tab_pending", "To review") : "To review"), pendingCountForTabs);
15514
+ makeTab("resolved", "history", (obj.__aiT ? obj.__aiT("air_tab_history", "History") : "History"), resolvedCountForTabs);
15515
+ makeTab("all", "filter", (obj.__aiT ? obj.__aiT("air_tab_all", "All") : "All"), counts.total);
14846
15516
 
14847
15517
  // Toolbar: type filter + (on Pending tab only) Accept all / Reject all
14848
15518
  var toolbar = append(panel, "div", "", "rte-ai-review-v2-toolbar");
@@ -14872,14 +15542,14 @@ function RTE_Plugin_AIToolkit() {
14872
15542
  var acceptAllBtn = append(toolbar, "button", "", "rte-ai-review-v2-batch is-accept");
14873
15543
  acceptAllBtn.type = "button";
14874
15544
  acceptAllBtn.disabled = !pendingForBatch;
14875
- setReviewV2ButtonContent(acceptAllBtn, "checkAll", "Accept all");
15545
+ setReviewV2ButtonContent(acceptAllBtn, "checkAll", (obj.__aiT ? obj.__aiT("air_acceptall", "Accept all") : "Accept all"));
14876
15546
  acceptAllBtn.onclick = function () {
14877
15547
  if (acceptPendingSuggestionsByType(typeFilter)) renderReviewPanelV2(true);
14878
15548
  };
14879
15549
  var rejectAllBtn = append(toolbar, "button", "", "rte-ai-review-v2-batch is-reject");
14880
15550
  rejectAllBtn.type = "button";
14881
15551
  rejectAllBtn.disabled = !pendingForBatch;
14882
- setReviewV2ButtonContent(rejectAllBtn, "x", "Reject all");
15552
+ setReviewV2ButtonContent(rejectAllBtn, "x", (obj.__aiT ? obj.__aiT("air_rejectall", "Reject all") : "Reject all"));
14883
15553
  rejectAllBtn.onclick = function () {
14884
15554
  if (rejectPendingSuggestionsByType(typeFilter)) renderReviewPanelV2(true);
14885
15555
  };
@@ -14923,11 +15593,11 @@ function RTE_Plugin_AIToolkit() {
14923
15593
  }
14924
15594
  var activeV2Suggestion = findSuggestionById(activeId);
14925
15595
  var resolvedProgress = counts.total ? Math.max(0, Math.min(100, Math.round((resolvedCountForTabs / counts.total) * 100))) : 0;
14926
- var queueScopeLabel = typeFilter === "all" ? "All types" : getSuggestionTypeLabel(typeFilter);
15596
+ var queueScopeLabel = typeFilter === "all" ? (obj.__aiT ? obj.__aiT("air_alltypes", "All types") : "All types") : getSuggestionTypeLabel(typeFilter);
14927
15597
  var v2Center = append(panel, "div", "", "rte-ai-review-v2-center");
14928
15598
  panel.insertBefore(v2Center, body);
14929
15599
  v2Center.setAttribute("role", "group");
14930
- v2Center.setAttribute("aria-label", "AI review summary");
15600
+ v2Center.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("air_summary", "AI review summary") : "AI review summary"));
14931
15601
  function appendReviewV2CenterTile(className, label, value, detail) {
14932
15602
  var tile = append(v2Center, "div", "", "rte-ai-review-v2-center-tile " + className);
14933
15603
  append(tile, "span", "", "rte-ai-review-v2-center-label", label);
@@ -14935,19 +15605,19 @@ function RTE_Plugin_AIToolkit() {
14935
15605
  append(tile, "span", "", "rte-ai-review-v2-center-detail", detail);
14936
15606
  return tile;
14937
15607
  }
14938
- appendReviewV2CenterTile("is-queue", "Queue", pendingForBatch + " pending", queueScopeLabel + (activeTab === "pending" ? " to review" : " filter"));
14939
- appendReviewV2CenterTile("is-progress", "Progress", resolvedProgress + "% done", resolvedCountForTabs + " resolved of " + counts.total);
15608
+ appendReviewV2CenterTile("is-queue", (obj.__aiT ? obj.__aiT("air_queue", "Queue") : "Queue"), (obj.__aiT ? obj.__aiT("air_npending", "{0} pending", pendingForBatch) : pendingForBatch + " pending"), activeTab === "pending" ? (obj.__aiT ? obj.__aiT("air_toreview", "{0} to review", queueScopeLabel) : queueScopeLabel + " to review") : (obj.__aiT ? obj.__aiT("air_filter", "{0} filter", queueScopeLabel) : queueScopeLabel + " filter"));
15609
+ appendReviewV2CenterTile("is-progress", (obj.__aiT ? obj.__aiT("air_progress", "Progress") : "Progress"), (obj.__aiT ? obj.__aiT("air_pctdone", "{0}% done", resolvedProgress) : resolvedProgress + "% done"), (obj.__aiT ? obj.__aiT("air_resolvedof", "{0} resolved of {1}").split("{0}").join(String(resolvedCountForTabs)).split("{1}").join(String(counts.total)) : resolvedCountForTabs + " resolved of " + counts.total));
14940
15610
  var nextV2Value = activeV2Suggestion
14941
- ? ((activeV2Suggestion.status === "pending" || activeV2Suggestion.status === "stale") ? "Review current" : "Inspect item")
14942
- : (pendingForBatch ? "Pick an item" : (counts.total ? "All caught up" : "Start with Ask AI"));
15611
+ ? ((activeV2Suggestion.status === "pending" || activeV2Suggestion.status === "stale") ? (obj.__aiT ? obj.__aiT("air_reviewcurrent", "Review current") : "Review current") : (obj.__aiT ? obj.__aiT("air_inspect", "Inspect item") : "Inspect item"))
15612
+ : (pendingForBatch ? (obj.__aiT ? obj.__aiT("air_pick", "Pick an item") : "Pick an item") : (counts.total ? (obj.__aiT ? obj.__aiT("air_caughtup", "All caught up") : "All caught up") : (obj.__aiT ? obj.__aiT("air_startask", "Start with Ask AI") : "Start with Ask AI")));
14943
15613
  var nextV2Detail = activeV2Suggestion
14944
15614
  ? summarizeSuggestionText(activeV2Suggestion.originalText || activeV2Suggestion.resultText || "AI suggestion", 72)
14945
- : (pendingForBatch ? "Select a card below to compare the change." : (counts.total ? "History keeps accepted and rejected changes." : "AI suggestions will appear here."));
14946
- var nextV2Tile = appendReviewV2CenterTile("is-next", "Next step", nextV2Value, nextV2Detail);
15615
+ : (pendingForBatch ? (obj.__aiT ? obj.__aiT("air_d_pick", "Select a card below to compare the change.") : "Select a card below to compare the change.") : (counts.total ? (obj.__aiT ? obj.__aiT("air_d_history", "History keeps accepted and rejected changes.") : "History keeps accepted and rejected changes.") : (obj.__aiT ? obj.__aiT("air_d_empty", "AI suggestions will appear here.") : "AI suggestions will appear here.")));
15616
+ var nextV2Tile = appendReviewV2CenterTile("is-next", (obj.__aiT ? obj.__aiT("air_next", "Next step") : "Next step"), nextV2Value, nextV2Detail);
14947
15617
  if (activeV2Suggestion) {
14948
- var focusV2Button = append(nextV2Tile, "button", "", "rte-ai-review-v2-center-action", "Focus");
15618
+ var focusV2Button = append(nextV2Tile, "button", "", "rte-ai-review-v2-center-action", (obj.__aiT ? obj.__aiT("air_focus", "Focus") : "Focus"));
14949
15619
  focusV2Button.type = "button";
14950
- focusV2Button.setAttribute("aria-label", "Focus the current AI review item");
15620
+ focusV2Button.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("air_focusaria", "Focus the current AI review item") : "Focus the current AI review item"));
14951
15621
  focusV2Button.onclick = function () {
14952
15622
  setActiveSuggestionId(activeId);
14953
15623
  renderReviewPanelV2(true);
@@ -14959,19 +15629,19 @@ function RTE_Plugin_AIToolkit() {
14959
15629
  if (counts.total === 0) {
14960
15630
  // Truly empty store — guide the user to AI Chat / Ask AI.
14961
15631
  append(empty, "div", "", "rte-ai-review-v2-empty-art is-prompt");
14962
- append(empty, "div", "", "rte-ai-review-v2-empty-title", "No AI suggestions yet");
14963
- append(empty, "div", "", "rte-ai-review-v2-empty-detail", "Ask AI or AI Chat will drop suggestions here for you to accept or reject.");
15632
+ append(empty, "div", "", "rte-ai-review-v2-empty-title", (obj.__aiT ? obj.__aiT("air_e_none_t", "No AI suggestions yet") : "No AI suggestions yet"));
15633
+ append(empty, "div", "", "rte-ai-review-v2-empty-detail", (obj.__aiT ? obj.__aiT("air_e_none_d", "Ask AI or AI Chat will drop suggestions here for you to accept or reject.") : "Ask AI or AI Chat will drop suggestions here for you to accept or reject."));
14964
15634
  } else if (activeTab === "pending") {
14965
15635
  // History exists but Pending is empty — celebrate.
14966
15636
  append(empty, "div", "", "rte-ai-review-v2-empty-art is-clear");
14967
- append(empty, "div", "", "rte-ai-review-v2-empty-title", "Nothing to review");
14968
- append(empty, "div", "", "rte-ai-review-v2-empty-detail", "You're all caught up. Switch to History to see past decisions.");
15637
+ append(empty, "div", "", "rte-ai-review-v2-empty-title", (obj.__aiT ? obj.__aiT("air_e_done_t", "Nothing to review") : "Nothing to review"));
15638
+ append(empty, "div", "", "rte-ai-review-v2-empty-detail", (obj.__aiT ? obj.__aiT("air_e_done_d", "You're all caught up. Switch to History to see past decisions.") : "You're all caught up. Switch to History to see past decisions."));
14969
15639
  } else if (activeTab === "resolved") {
14970
- append(empty, "div", "", "rte-ai-review-v2-empty-title", "No history yet");
14971
- append(empty, "div", "", "rte-ai-review-v2-empty-detail", "Decisions you make on the To review tab will show up here.");
15640
+ append(empty, "div", "", "rte-ai-review-v2-empty-title", (obj.__aiT ? obj.__aiT("air_e_nohist_t", "No history yet") : "No history yet"));
15641
+ append(empty, "div", "", "rte-ai-review-v2-empty-detail", (obj.__aiT ? obj.__aiT("air_e_nohist_d", "Decisions you make on the To review tab will show up here.") : "Decisions you make on the To review tab will show up here."));
14972
15642
  } else {
14973
- append(empty, "div", "", "rte-ai-review-v2-empty-title", "No suggestions match this filter");
14974
- append(empty, "div", "", "rte-ai-review-v2-empty-detail", "Try a different type, or pick a different tab.");
15643
+ append(empty, "div", "", "rte-ai-review-v2-empty-title", (obj.__aiT ? obj.__aiT("air_e_nomatch_t", "No suggestions match this filter") : "No suggestions match this filter"));
15644
+ append(empty, "div", "", "rte-ai-review-v2-empty-detail", (obj.__aiT ? obj.__aiT("air_e_nomatch_d", "Try a different type, or pick a different tab.") : "Try a different type, or pick a different tab."));
14975
15645
  }
14976
15646
  } else {
14977
15647
  for (var vi = 0; vi < visible.length; vi++) {
@@ -14986,9 +15656,9 @@ function RTE_Plugin_AIToolkit() {
14986
15656
  append(head, "span", "", "rte-ai-review-v2-item-type", getSuggestionTypeLabel(sug.suggestionType));
14987
15657
  }
14988
15658
  if (sug.snapshot && sug.snapshot.hasSelection) {
14989
- append(head, "span", "", "rte-ai-review-v2-item-scope", "Selection");
15659
+ append(head, "span", "", "rte-ai-review-v2-item-scope", (obj.__aiT ? obj.__aiT("air_scope_sel", "Selection") : "Selection"));
14990
15660
  } else {
14991
- append(head, "span", "", "rte-ai-review-v2-item-scope", "Document");
15661
+ append(head, "span", "", "rte-ai-review-v2-item-scope", (obj.__aiT ? obj.__aiT("air_scope_doc", "Document") : "Document"));
14992
15662
  }
14993
15663
 
14994
15664
  var titleText = summarizeSuggestionText(sug.originalText || sug.resultText || "AI suggestion", 120);
@@ -14998,15 +15668,15 @@ function RTE_Plugin_AIToolkit() {
14998
15668
  if (sug.originalText && sug.resultText && normalizeText(sug.originalText) !== normalizeText(sug.resultText)) {
14999
15669
  var compare = append(item, "div", "", "rte-ai-review-v2-compare");
15000
15670
  var before = append(compare, "div", "", "rte-ai-review-v2-before");
15001
- append(before, "span", "", "rte-ai-review-v2-compare-label", "Current");
15671
+ append(before, "span", "", "rte-ai-review-v2-compare-label", (obj.__aiT ? obj.__aiT("air_current", "Current") : "Current"));
15002
15672
  append(before, "div", "", "rte-ai-review-v2-compare-text", normalizeText(sug.originalText));
15003
15673
  // Decorative arrow on the centerline of the
15004
15674
  // grid pointing from Current -> Suggested.
15005
15675
  var compareArrow = append(compare, "span", "", "rte-ai-review-v2-compare-arrow");
15006
15676
  compareArrow.setAttribute("aria-hidden", "true");
15007
- compareArrow.innerHTML = REVIEW_V2_ICONS.arrowRight;
15677
+ compareArrow.innerHTML = __RTE_TT(REVIEW_V2_ICONS.arrowRight);
15008
15678
  var after = append(compare, "div", "", "rte-ai-review-v2-after");
15009
- append(after, "span", "", "rte-ai-review-v2-compare-label", "Suggested");
15679
+ append(after, "span", "", "rte-ai-review-v2-compare-label", (obj.__aiT ? obj.__aiT("air_suggested", "Suggested") : "Suggested"));
15010
15680
  append(after, "div", "", "rte-ai-review-v2-compare-text", normalizeText(sug.resultText));
15011
15681
  }
15012
15682
  if (sug.reason) {
@@ -15015,17 +15685,17 @@ function RTE_Plugin_AIToolkit() {
15015
15685
  var bulbWrap = reason.ownerDocument.createElement("span");
15016
15686
  bulbWrap.className = "rte-ai-review-v2-reason-icon";
15017
15687
  bulbWrap.setAttribute("aria-hidden", "true");
15018
- bulbWrap.innerHTML = REVIEW_V2_ICONS.bulb;
15688
+ bulbWrap.innerHTML = __RTE_TT(REVIEW_V2_ICONS.bulb);
15019
15689
  reasonLabel.appendChild(bulbWrap);
15020
15690
  var reasonLabelText = reason.ownerDocument.createElement("span");
15021
- reasonLabelText.textContent = "Why";
15691
+ reasonLabelText.textContent = (obj.__aiT ? obj.__aiT("air_why", "Why") : "Why");
15022
15692
  reasonLabel.appendChild(reasonLabelText);
15023
15693
  append(reason, "span", "", "rte-ai-review-v2-reason-text", sug.reason);
15024
15694
  }
15025
15695
  var actions = append(item, "div", "", "rte-ai-review-v2-actions");
15026
15696
  var locateBtn = append(actions, "button", "", "rte-ai-review-v2-action is-locate");
15027
15697
  locateBtn.type = "button";
15028
- setReviewV2ButtonContent(locateBtn, "target", "Locate");
15698
+ setReviewV2ButtonContent(locateBtn, "target", (obj.__aiT ? obj.__aiT("air_locate", "Locate") : "Locate"));
15029
15699
  locateBtn.onclick = function (e) {
15030
15700
  if (e && e.stopPropagation) e.stopPropagation();
15031
15701
  locateSuggestion(sug.id);
@@ -15033,14 +15703,14 @@ function RTE_Plugin_AIToolkit() {
15033
15703
  if (sug.status === "pending" || sug.status === "stale") {
15034
15704
  var acceptBtn = append(actions, "button", "", "rte-ai-review-v2-action is-accept");
15035
15705
  acceptBtn.type = "button";
15036
- setReviewV2ButtonContent(acceptBtn, "check", "Accept");
15706
+ setReviewV2ButtonContent(acceptBtn, "check", (obj.__aiT ? obj.__aiT("air_accept", "Accept") : "Accept"));
15037
15707
  acceptBtn.onclick = function (e) {
15038
15708
  if (e && e.stopPropagation) e.stopPropagation();
15039
15709
  applyReviewDecision(sug.id, "accept", { focusAction: "accept" });
15040
15710
  };
15041
15711
  var rejectBtn = append(actions, "button", "", "rte-ai-review-v2-action is-reject");
15042
15712
  rejectBtn.type = "button";
15043
- setReviewV2ButtonContent(rejectBtn, "x", "Reject");
15713
+ setReviewV2ButtonContent(rejectBtn, "x", (obj.__aiT ? obj.__aiT("air_reject", "Reject") : "Reject"));
15044
15714
  rejectBtn.onclick = function (e) {
15045
15715
  if (e && e.stopPropagation) e.stopPropagation();
15046
15716
  applyReviewDecision(sug.id, "reject", { focusAction: "reject" });
@@ -15289,7 +15959,7 @@ function RTE_Plugin_AIToolkit() {
15289
15959
  }
15290
15960
  var titleRow = append(headerCopy, "div", "", "rte-ai-review-title-row");
15291
15961
  var titleIcon = append(titleRow, "span", "", "rte-ai-review-title-icon");
15292
- titleIcon.innerHTML = config.svgCode_aiassist_review || config.svgCode_aiassist || "";
15962
+ titleIcon.innerHTML = __RTE_TT(config.svgCode_aiassist_review || config.svgCode_aiassist || "");
15293
15963
  append(titleRow, "div", "", "rte-ai-review-title", config.text_aireview || "AI Review");
15294
15964
  if (hasRemoteReviewSync()) {
15295
15965
  editor.__aiReviewSyncBadgeNode = append(titleRow, "span", "", "rte-ai-review-sync-badge", "");
@@ -15332,7 +16002,7 @@ function RTE_Plugin_AIToolkit() {
15332
16002
  var newestSharedButton = append(headerGroupContext, "button", "", "secondary rte-ai-review-header-button is-shared", "Newest shared");
15333
16003
  newestSharedButton.type = "button";
15334
16004
  newestSharedButton.setAttribute("data-rte-ai-review-focus-key", "newest-shared");
15335
- newestSharedButton.setAttribute("aria-label", "Jump to the newest shared AI review update");
16005
+ newestSharedButton.setAttribute("aria-label", uiT("aiu_jumpnewest", "Jump to the newest shared AI review update"));
15336
16006
  newestSharedButton.onclick = function () {
15337
16007
  locateSuggestion(latestRemoteSuggestionId);
15338
16008
  };
@@ -15342,7 +16012,7 @@ function RTE_Plugin_AIToolkit() {
15342
16012
  var currentPendingButton = append(headerGroupContext, "button", "", "secondary rte-ai-review-header-button is-locate-current", "Locate current");
15343
16013
  currentPendingButton.type = "button";
15344
16014
  currentPendingButton.setAttribute("data-rte-ai-review-focus-key", "locate-current");
15345
- currentPendingButton.setAttribute("aria-label", "Locate the current active AI review item in the editor");
16015
+ currentPendingButton.setAttribute("aria-label", uiT("aiu_locatecurrent", "Locate the current active AI review item in the editor"));
15346
16016
  currentPendingButton.onclick = function () {
15347
16017
  locateSuggestion(activeFilteredSuggestionId);
15348
16018
  };
@@ -15353,7 +16023,7 @@ function RTE_Plugin_AIToolkit() {
15353
16023
  var previousPendingButton = append(headerGroupMove, "button", "", "secondary rte-ai-review-header-button is-nav", "Previous");
15354
16024
  previousPendingButton.type = "button";
15355
16025
  previousPendingButton.setAttribute("data-rte-ai-review-focus-key", "previous-pending");
15356
- previousPendingButton.setAttribute("aria-label", "Jump to the previous pending AI review item in the current filter");
16026
+ previousPendingButton.setAttribute("aria-label", uiT("aiu_jumpprev", "Jump to the previous pending AI review item in the current filter"));
15357
16027
  previousPendingButton.onclick = function () {
15358
16028
  activateReviewSuggestionWithDefaultActionFocus(previousPendingSuggestionId, { focusPanel: true });
15359
16029
  };
@@ -15363,7 +16033,7 @@ function RTE_Plugin_AIToolkit() {
15363
16033
  var nextPendingButton = append(headerGroupMove, "button", "", "secondary rte-ai-review-header-button is-nav", "Next pending");
15364
16034
  nextPendingButton.type = "button";
15365
16035
  nextPendingButton.setAttribute("data-rte-ai-review-focus-key", "next-pending");
15366
- nextPendingButton.setAttribute("aria-label", "Jump to the next pending AI review item in the current filter");
16036
+ nextPendingButton.setAttribute("aria-label", uiT("aiu_jumpnext", "Jump to the next pending AI review item in the current filter"));
15367
16037
  nextPendingButton.onclick = function () {
15368
16038
  activateReviewSuggestionWithDefaultActionFocus(nextPendingSuggestionId, { focusPanel: true });
15369
16039
  };
@@ -15395,9 +16065,9 @@ function RTE_Plugin_AIToolkit() {
15395
16065
  var closeButton = append(headerUtility, "button", "", "rte-ai-panel-close-button is-review-close");
15396
16066
  closeButton.type = "button";
15397
16067
  closeButton.setAttribute("data-rte-ai-review-focus-key", "close-review");
15398
- closeButton.setAttribute("aria-label", "Close AI review");
15399
- closeButton.title = "Close";
15400
- closeButton.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12"/><path d="M18 6L6 18"/></svg>';
16068
+ closeButton.setAttribute("aria-label", uiT("air_closearia", "Close AI review"));
16069
+ closeButton.title = uiT("air_close", "Close");
16070
+ closeButton.innerHTML = __RTE_TT('<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6l12 12"/><path d="M18 6L6 18"/></svg>');
15401
16071
  closeButton.onclick = function () {
15402
16072
  closeReviewPanel();
15403
16073
  };
@@ -15513,7 +16183,7 @@ function RTE_Plugin_AIToolkit() {
15513
16183
  var dismissActivityButton = append(activityCard, "button", "", "secondary rte-ai-review-activity-button", "Got it");
15514
16184
  dismissActivityButton.type = "button";
15515
16185
  dismissActivityButton.setAttribute("data-rte-ai-review-focus-key", "dismiss-activity");
15516
- dismissActivityButton.setAttribute("aria-label", "Dismiss shared AI review update notice");
16186
+ dismissActivityButton.setAttribute("aria-label", uiT("aiu_dismissshared", "Dismiss shared AI review update notice"));
15517
16187
  dismissActivityButton.onclick = function () {
15518
16188
  markReviewActivitySeen();
15519
16189
  renderReviewPanel(true);
@@ -15552,7 +16222,7 @@ function RTE_Plugin_AIToolkit() {
15552
16222
  acceptAllButton.setAttribute("data-rte-ai-review-focus-key", "accept-all");
15553
16223
  acceptAllButton.disabled = typeFilter === "all" ? !counts.pending : !filteredPendingCount;
15554
16224
  if (typeFilter !== "all") {
15555
- acceptAllButton.innerText = "Accept filtered";
16225
+ acceptAllButton.innerText = uiT("aiu_acceptfiltered", "Accept filtered");
15556
16226
  }
15557
16227
  acceptAllButton.onclick = function () {
15558
16228
  if (acceptPendingSuggestionsByType(typeFilter)) {
@@ -15564,7 +16234,7 @@ function RTE_Plugin_AIToolkit() {
15564
16234
  rejectAllButton.setAttribute("data-rte-ai-review-focus-key", "reject-all");
15565
16235
  rejectAllButton.disabled = typeFilter === "all" ? !counts.pending : !filteredPendingCount;
15566
16236
  if (typeFilter !== "all") {
15567
- rejectAllButton.innerText = "Reject filtered";
16237
+ rejectAllButton.innerText = uiT("aiu_rejectfiltered", "Reject filtered");
15568
16238
  }
15569
16239
  rejectAllButton.onclick = function () {
15570
16240
  if (rejectPendingSuggestionsByType(typeFilter)) {
@@ -15857,7 +16527,7 @@ function RTE_Plugin_AIToolkit() {
15857
16527
  appendPanelSectionLabel(previewSection, "rte-ai-review-section-label is-feed", emptyState.previewItems.length > 1 ? "Next queues" : "Next queue");
15858
16528
  var emptyPreviewList = append(previewSection, "div", "", "rte-ai-review-empty-previews");
15859
16529
  emptyPreviewList.setAttribute("role", "toolbar");
15860
- emptyPreviewList.setAttribute("aria-label", "Next review queues");
16530
+ emptyPreviewList.setAttribute("aria-label", uiT("aiu_nextqueues", "Next review queues"));
15861
16531
  for (var previewIndex = 0; previewIndex < emptyState.previewItems.length; previewIndex++) {
15862
16532
  var previewItem = emptyState.previewItems[previewIndex];
15863
16533
  var previewTag = previewItem.typeValue ? "button" : "div";
@@ -15903,7 +16573,7 @@ function RTE_Plugin_AIToolkit() {
15903
16573
  if (previewItem.reasonText) {
15904
16574
  previewAriaParts.push("Why this next: " + previewItem.reasonText);
15905
16575
  }
15906
- previewRow.setAttribute("aria-label", previewAriaParts.join(". "));
16576
+ previewRow.setAttribute("aria-label", joinSentences(previewAriaParts));
15907
16577
  previewRow.setAttribute("data-rte-ai-primary-label", previewItem.actionLabel || previewItem.label || "Open next queue");
15908
16578
  if (previewItem.focusActionLabel) {
15909
16579
  previewRow.setAttribute("data-rte-ai-empty-focus-label", previewItem.focusActionLabel);
@@ -16433,6 +17103,7 @@ function RTE_Plugin_AIToolkit() {
16433
17103
  modeChip.className = "rte-ai-dialog-mode-chip is-detached";
16434
17104
  modeChip.setAttribute("aria-hidden", "true");
16435
17105
 
17106
+ appendDemoNotice(dialoginner);
16436
17107
  var grid = append(dialoginner, "div", "", "demo-ai-dialog-grid");
16437
17108
  var dialogIdSuffix = String(Date.now()) + "-" + String(Math.floor(Math.random() * 1000000));
16438
17109
  var modeHelpId = "rte-ai-mode-help-" + dialogIdSuffix;
@@ -16441,11 +17112,11 @@ function RTE_Plugin_AIToolkit() {
16441
17112
  var guidanceId = "rte-ai-apply-guidance-" + dialogIdSuffix;
16442
17113
  var statusId = "rte-ai-dialog-status-" + dialogIdSuffix;
16443
17114
  grid.setAttribute("role", "form");
16444
- grid.setAttribute("aria-label", "Ask AI controls");
17115
+ grid.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_controls", "Ask AI controls") : "Ask AI controls"));
16445
17116
 
16446
17117
  var compactControls = append(grid, "div", "", "demo-ai-compact-controls");
16447
17118
  compactControls.setAttribute("role", "group");
16448
- compactControls.setAttribute("aria-label", "Ask AI setup");
17119
+ compactControls.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_setup", "Ask AI setup") : "Ask AI setup"));
16449
17120
 
16450
17121
  // 2026-05-08 compact pass: dropped the per-field uppercase mini-labels
16451
17122
  // ("Action" / "Language" / "Use" / blank-spacer) and the duplicate
@@ -16454,7 +17125,7 @@ function RTE_Plugin_AIToolkit() {
16454
17125
  // select's title attribute (still surfaced on hover).
16455
17126
  var actionField = append(compactControls, "div", "", "demo-ai-field demo-ai-action-field");
16456
17127
  var modeSelect = append(actionField, "select");
16457
- modeSelect.setAttribute("aria-label", "AI action");
17128
+ modeSelect.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_action", "AI action") : "AI action"));
16458
17129
  // 2026-05-17 (v20260517a): the mode help line is back on-screen,
16459
17130
  // attached directly to the dialog grid (full-width row under the
16460
17131
  // controls strip). The prior hover-only `title` attribute meant
@@ -16481,7 +17152,7 @@ function RTE_Plugin_AIToolkit() {
16481
17152
 
16482
17153
  var languageField = append(compactControls, "div", "", "demo-ai-field demo-ai-language-field");
16483
17154
  var languageSelect = append(languageField, "select");
16484
- languageSelect.setAttribute("aria-label", "Translation target language");
17155
+ languageSelect.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_targetlang", "Translation target language") : "Translation target language"));
16485
17156
  populateDialogModes();
16486
17157
  if (options.presetMode) {
16487
17158
  // 2026-05-19 (v20260519a): only honor presetMode when it
@@ -16532,11 +17203,11 @@ function RTE_Plugin_AIToolkit() {
16532
17203
  // can override with `.demo-ai-scope-select { display: none }` +
16533
17204
  // `.demo-ai-scope-row.is-detached { display: flex !important }`.
16534
17205
  var scopeSelect = append(scopeField, "select", "", "demo-ai-scope-select");
16535
- scopeSelect.setAttribute("aria-label", "Source for Ask AI");
17206
+ scopeSelect.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_source", "Source for Ask AI") : "Source for Ask AI"));
16536
17207
  scopeSelect.setAttribute("data-rte-ai-dialog-action", "scope");
16537
- var scopeOptionSelection = append(scopeSelect, "option", "", "", "Selection");
17208
+ var scopeOptionSelection = append(scopeSelect, "option", "", "", (obj.__aiT ? obj.__aiT("aid_selection", "Selection") : "Selection"));
16538
17209
  scopeOptionSelection.value = "selection";
16539
- var scopeOptionDocument = append(scopeSelect, "option", "", "", "Document");
17210
+ var scopeOptionDocument = append(scopeSelect, "option", "", "", (obj.__aiT ? obj.__aiT("aid_document", "Document") : "Document"));
16540
17211
  scopeOptionDocument.value = "document";
16541
17212
 
16542
17213
  // 2026-06-01 (v20260601a): scope caption surfaced inline when only
@@ -16561,21 +17232,21 @@ function RTE_Plugin_AIToolkit() {
16561
17232
  var loadRow = scopeField.ownerDocument.createElement("div");
16562
17233
  loadRow.className = "demo-actions-row demo-ai-scope-row is-detached";
16563
17234
  loadRow.setAttribute("role", "group");
16564
- loadRow.setAttribute("aria-label", "Source scope");
17235
+ loadRow.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_scope", "Source scope") : "Source scope"));
16565
17236
  var loadSelectionButton = scopeField.ownerDocument.createElement("button");
16566
17237
  loadSelectionButton.type = "button";
16567
17238
  loadSelectionButton.className = "is-icon-only";
16568
17239
  loadSelectionButton.setAttribute("data-rte-ai-dialog-action", "load-selection");
16569
- loadSelectionButton.title = "Use selection";
16570
- loadSelectionButton.setAttribute("aria-label", "Use selection");
17240
+ loadSelectionButton.title = (obj.__aiT ? obj.__aiT("aid_usesel", "Use selection") : "Use selection");
17241
+ loadSelectionButton.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_usesel", "Use selection") : "Use selection"));
16571
17242
  setReviewV2ButtonContent(loadSelectionButton, "cursorText", "");
16572
17243
  loadRow.appendChild(loadSelectionButton);
16573
17244
  var loadDocumentButton = scopeField.ownerDocument.createElement("button");
16574
17245
  loadDocumentButton.type = "button";
16575
17246
  loadDocumentButton.className = "secondary is-icon-only";
16576
17247
  loadDocumentButton.setAttribute("data-rte-ai-dialog-action", "load-document");
16577
- loadDocumentButton.title = "Use document";
16578
- loadDocumentButton.setAttribute("aria-label", "Use document");
17248
+ loadDocumentButton.title = (obj.__aiT ? obj.__aiT("aid_usedoc", "Use document") : "Use document");
17249
+ loadDocumentButton.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_usedoc", "Use document") : "Use document"));
16579
17250
  setReviewV2ButtonContent(loadDocumentButton, "document", "");
16580
17251
  loadRow.appendChild(loadDocumentButton);
16581
17252
  // 2026-05-17 (v20260523a): append the legacy scope row to
@@ -16589,20 +17260,20 @@ function RTE_Plugin_AIToolkit() {
16589
17260
  var runField = append(compactControls, "div", "", "demo-ai-field demo-ai-run-field");
16590
17261
  var runRow = append(runField, "div", "", "demo-actions-row demo-ai-run-row");
16591
17262
  runRow.setAttribute("role", "group");
16592
- runRow.setAttribute("aria-label", "Ask AI run actions");
17263
+ runRow.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_run", "Ask AI run actions") : "Ask AI run actions"));
16593
17264
  var runButton = append(runRow, "button", "", "is-primary");
16594
17265
  runButton.type = "button";
16595
17266
  runButton.setAttribute("data-rte-ai-dialog-action", "run");
16596
17267
  // Sparkle glyph + "Generate" — same icon vocabulary as AI Chat
16597
17268
  // composer's Send and AI Review V2's Accept buttons.
16598
- setReviewV2ButtonContent(runButton, "sparkles", "Generate");
17269
+ setReviewV2ButtonContent(runButton, "sparkles", (obj.__aiT ? obj.__aiT("aid_generate", "Generate") : "Generate"));
16599
17270
  // 2026-05-09 (v20260509j): surface the Ctrl/Cmd+Enter shortcut on
16600
17271
  // hover and to screen readers. Same idiom every modern AI surface
16601
17272
  // uses (ChatGPT Send: "Send (⌘↵)", Notion AI Generate: "Generate
16602
17273
  // (Ctrl+Enter)"). aria-keyshortcuts is the WAI-ARIA standard way
16603
17274
  // to advertise a keyboard shortcut so screen readers announce
16604
17275
  // "Generate, Control+Enter" alongside the button name.
16605
- runButton.title = "Generate AI suggestion (Ctrl+Enter)";
17276
+ runButton.title = (obj.__aiT ? obj.__aiT("ai2_gentitle", "Generate AI suggestion (Ctrl+Enter)") : "Generate AI suggestion (Ctrl+Enter)");
16606
17277
  runButton.setAttribute("aria-keyshortcuts", "Control+Enter");
16607
17278
  // 2026-05-17 (v20260517a): visible Ctrl+↵ / ⌘↵ chip baked into
16608
17279
  // the Generate button. Pre-v20260517a the shortcut only lived in
@@ -16614,7 +17285,7 @@ function RTE_Plugin_AIToolkit() {
16614
17285
  // from navigator.platform so the hint matches the actual key
16615
17286
  // the user would press. */
16616
17287
  var isMacOs = /Mac|iPad|iPhone|iPod/i.test((navigator.platform || "") + " " + (navigator.userAgent || ""));
16617
- var runKbdLabel = isMacOs ? "⌘⏎" : "Ctrl ⏎";
17288
+ var runKbdLabel = isMacOs ? "\u2318\u23ce" : "Ctrl \u23ce";
16618
17289
  function refreshRunKbdHint() {
16619
17290
  if (!runButton) return;
16620
17291
  var existing = runButton.querySelector(".demo-ai-kbd-hint");
@@ -16632,8 +17303,8 @@ function RTE_Plugin_AIToolkit() {
16632
17303
  // 540px dialog width.
16633
17304
  var copyButton = append(runRow, "button", "", "secondary is-icon-only");
16634
17305
  copyButton.type = "button";
16635
- copyButton.title = "Copy result into source";
16636
- copyButton.setAttribute("aria-label", "Copy result into source");
17306
+ copyButton.title = uiT("aiu_copytosource", "Copy result into source");
17307
+ copyButton.setAttribute("aria-label", uiT("aiu_copytosource", "Copy result into source"));
16637
17308
  copyButton.setAttribute("data-rte-ai-dialog-action", "copy-to-source");
16638
17309
  setReviewV2ButtonContent(copyButton, "checkAll", "");
16639
17310
 
@@ -16664,7 +17335,7 @@ function RTE_Plugin_AIToolkit() {
16664
17335
 
16665
17336
  var textGrid = append(grid, "div", "", "demo-ai-text-grid");
16666
17337
  textGrid.setAttribute("role", "group");
16667
- textGrid.setAttribute("aria-label", "Ask AI source and result");
17338
+ textGrid.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_srcresult", "Ask AI source and result") : "Ask AI source and result"));
16668
17339
 
16669
17340
  // 2026-05-09 (v20260509j): dropped the "Source" / "Result"
16670
17341
  // text-tag spans. They had no CSS rule (the class was
@@ -16682,7 +17353,7 @@ function RTE_Plugin_AIToolkit() {
16682
17353
  // Pre-result it was already hidden (CSS .is-pre-result cascade); the
16683
17354
  // remaining showing case was post-result + source-expanded, but the
16684
17355
  // user only reaches that flow by clicking the "Edit" toggle pill —
16685
- // which already names the field's role ("Edit the prompt and retry").
17356
+ // which already names the field's role ((obj.__aiT ? obj.__aiT("ai2_editretry", "Edit the prompt and retry") : "Edit the prompt and retry")).
16686
17357
  // Restating "Source" 4px above the textarea was a third copy of the
16687
17358
  // same fact (toggle label + textarea placeholder + this eyebrow).
16688
17359
  // Saves ~16px vertical when the user expands source for retry/refine.
@@ -16692,7 +17363,7 @@ function RTE_Plugin_AIToolkit() {
16692
17363
  var sourceHelp = append(sourceField, "span", "", "demo-ai-field-help is-detached", "Edit the source text or prompt that Ask AI will use.");
16693
17364
  sourceHelp.id = sourceHelpId;
16694
17365
  var sourceArea = append(sourceField, "textarea");
16695
- sourceArea.setAttribute("aria-label", "Source text for Ask AI");
17366
+ sourceArea.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_srctext", "Source text for Ask AI") : "Source text for Ask AI"));
16696
17367
  sourceArea.setAttribute("aria-describedby", sourceHelpId + " " + modeHelpId);
16697
17368
  // 2026-05-26 (v20260526a): reparent the run-row (Generate + Copy
16698
17369
  // buttons) from the compact-controls strip INTO the source field,
@@ -16743,7 +17414,7 @@ function RTE_Plugin_AIToolkit() {
16743
17414
  // verb phrase, no inline shortcut hint.
16744
17415
  // Earlier baseline:
16745
17416
  // v20260509j: 95 → 47 chars after the scope toggle landed.
16746
- sourceArea.placeholder = "Type or paste text…";
17417
+ sourceArea.placeholder = (obj.__aiT ? obj.__aiT("aid_typeorpaste", "Type or paste text\u2026") : "Type or paste text\u2026");
16747
17418
 
16748
17419
  // 2026-06-03 (v20260603a): live character counter pinned to the
16749
17420
  // bottom-LEFT corner of the source textarea. Pre-pass users
@@ -16777,8 +17448,8 @@ function RTE_Plugin_AIToolkit() {
16777
17448
  return;
16778
17449
  }
16779
17450
  var label = len > 999
16780
- ? (Math.round(len / 100) / 10) + "K chars"
16781
- : len + (len === 1 ? " char" : " chars");
17451
+ ? (obj.__aiT ? obj.__aiT("aid_kchars", "{0}K chars", Math.round(len / 100) / 10) : (Math.round(len / 100) / 10) + "K chars")
17452
+ : (len === 1 ? (obj.__aiT ? obj.__aiT("aid_char", "1 char") : "1 char") : (obj.__aiT ? obj.__aiT("aid_chars", "{0} chars", len) : len + " chars"));
16782
17453
  sourceCounter.textContent = label;
16783
17454
  sourceCounter.classList.add("is-visible");
16784
17455
  }
@@ -16811,7 +17482,7 @@ function RTE_Plugin_AIToolkit() {
16811
17482
  // without throwing.
16812
17483
  var resultLabel = document.createElement("span");
16813
17484
  resultLabel.className = "demo-ai-text-label-inline is-detached";
16814
- resultLabel.innerText = "Result";
17485
+ resultLabel.innerText = uiT("aiu_result", "Result");
16815
17486
  var resultHelp = append(resultField, "span", "", "demo-ai-field-help is-detached", "Review the generated AI suggestion before applying it.");
16816
17487
  resultHelp.id = resultHelpId;
16817
17488
  // 2026-06-03 (v20260603a): real "Copy to clipboard" affordance
@@ -16832,9 +17503,9 @@ function RTE_Plugin_AIToolkit() {
16832
17503
  var copyClipboardButton = append(resultHead, "button", "", "demo-ai-clipboard-toggle");
16833
17504
  copyClipboardButton.type = "button";
16834
17505
  copyClipboardButton.setAttribute("data-rte-ai-dialog-action", "copy-clipboard");
16835
- copyClipboardButton.title = "Copy AI output to clipboard";
16836
- copyClipboardButton.setAttribute("aria-label", "Copy AI output to clipboard");
16837
- setReviewV2ButtonContent(copyClipboardButton, "clipboard", "Copy");
17506
+ copyClipboardButton.title = (obj.__aiT ? obj.__aiT("ai2_copyclip", "Copy AI output to clipboard") : "Copy AI output to clipboard");
17507
+ copyClipboardButton.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("ai2_copyclip", "Copy AI output to clipboard") : "Copy AI output to clipboard"));
17508
+ setReviewV2ButtonContent(copyClipboardButton, "clipboard", (obj.__aiT ? obj.__aiT("ai2_copy", "Copy") : "Copy"));
16838
17509
  var copyClipboardResetTimer = null;
16839
17510
  copyClipboardButton.onclick = function (e) {
16840
17511
  if (e && e.stopPropagation) e.stopPropagation();
@@ -16846,13 +17517,13 @@ function RTE_Plugin_AIToolkit() {
16846
17517
  return;
16847
17518
  }
16848
17519
  copyClipboardButton.classList.add("is-copied");
16849
- setReviewV2ButtonContent(copyClipboardButton, "check", "Copied");
17520
+ setReviewV2ButtonContent(copyClipboardButton, "check", (obj.__aiT ? obj.__aiT("aic_copied", "Copied") : "Copied"));
16850
17521
  if (copyClipboardResetTimer) {
16851
17522
  clearTimeout(copyClipboardResetTimer);
16852
17523
  }
16853
17524
  copyClipboardResetTimer = setTimeout(function () {
16854
17525
  copyClipboardButton.classList.remove("is-copied");
16855
- setReviewV2ButtonContent(copyClipboardButton, "clipboard", "Copy");
17526
+ setReviewV2ButtonContent(copyClipboardButton, "clipboard", (obj.__aiT ? obj.__aiT("ai2_copy", "Copy") : "Copy"));
16856
17527
  copyClipboardResetTimer = null;
16857
17528
  }, 1400);
16858
17529
  });
@@ -16870,8 +17541,8 @@ function RTE_Plugin_AIToolkit() {
16870
17541
  var whyButton = append(resultHead, "button", "", "demo-ai-clipboard-toggle demo-ai-why-toggle");
16871
17542
  whyButton.type = "button";
16872
17543
  whyButton.setAttribute("data-rte-ai-dialog-action", "why");
16873
- whyButton.title = "Why this AI suggestion?";
16874
- whyButton.setAttribute("aria-label", "Show why this AI suggestion was generated");
17544
+ whyButton.title = (obj.__aiT ? obj.__aiT("ai2_whytitle", "Why this AI suggestion?") : "Why this AI suggestion?");
17545
+ whyButton.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("ai2_whyaria", "Show why this AI suggestion was generated") : "Show why this AI suggestion was generated"));
16875
17546
  whyButton.setAttribute("aria-expanded", "false");
16876
17547
  setReviewV2ButtonContent(whyButton, "info", "");
16877
17548
 
@@ -16890,12 +17561,12 @@ function RTE_Plugin_AIToolkit() {
16890
17561
  // saying the same thing twice (open the prompt + edit the prompt).
16891
17562
  // "Edit" preserves the verb-action shape Pass v20260513b adopted,
16892
17563
  // saves ~30px horizontal real estate in the result-head pill row,
16893
- // and the title attribute ("Edit the prompt and retry") still
17564
+ // and the title attribute ((obj.__aiT ? obj.__aiT("ai2_editretry", "Edit the prompt and retry") : "Edit the prompt and retry")) still
16894
17565
  // surfaces the long form on hover + to screen readers via
16895
17566
  // aria-label.
16896
- sourceToggle.title = "Edit the prompt and retry";
16897
- sourceToggle.setAttribute("aria-label", "Edit the prompt and retry");
16898
- sourceToggle.innerText = "Edit";
17567
+ sourceToggle.title = (obj.__aiT ? obj.__aiT("ai2_editretry", "Edit the prompt and retry") : "Edit the prompt and retry");
17568
+ sourceToggle.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("ai2_editretry", "Edit the prompt and retry") : "Edit the prompt and retry"));
17569
+ sourceToggle.innerText = (obj.__aiT ? obj.__aiT("ai2_edit", "Edit") : "Edit");
16899
17570
  sourceField.id = sourceToggle.getAttribute("aria-controls");
16900
17571
  sourceToggle.onclick = function (e) {
16901
17572
  if (e && e.stopPropagation) e.stopPropagation();
@@ -16905,7 +17576,7 @@ function RTE_Plugin_AIToolkit() {
16905
17576
  };
16906
17577
  var resultArea = append(resultField, "textarea");
16907
17578
  resultArea.readOnly = true;
16908
- resultArea.setAttribute("aria-label", "AI suggestion result");
17579
+ resultArea.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("ai2_result", "AI suggestion result") : "AI suggestion result"));
16909
17580
  resultArea.setAttribute("aria-describedby", resultHelpId + " " + statusId);
16910
17581
  // 2026-05-17 (v20260520b): rows=1 lets the CSS min-height: 52px
16911
17582
  // floor (since v20260513a) actually bind. Browser default rows=2
@@ -16921,7 +17592,7 @@ function RTE_Plugin_AIToolkit() {
16921
17592
  // here." (20 chars). Same idiom Notion AI's inline composer
16922
17593
  // uses ("AI response..."). At 20 chars it never line-wraps
16923
17594
  // inside the textarea even at 320px viewport.
16924
- resultArea.placeholder = "Output appears here.";
17595
+ resultArea.placeholder = (obj.__aiT ? obj.__aiT("ai2_outputph", "Output appears here.") : "Output appears here.");
16925
17596
 
16926
17597
  var oldPreview = document.createElement("pre");
16927
17598
  var newPreview = document.createElement("pre");
@@ -16950,7 +17621,7 @@ function RTE_Plugin_AIToolkit() {
16950
17621
  var refineRow = document.createElement("div");
16951
17622
  refineRow.className = "demo-ai-refine-row is-detached";
16952
17623
  refineRow.setAttribute("role", "group");
16953
- refineRow.setAttribute("aria-label", "Refine AI suggestion");
17624
+ refineRow.setAttribute("aria-label", uiT("aiu_refine", "Refine AI suggestion"));
16954
17625
  // Keep the (now empty) refineRow attached to the dialog grid so any
16955
17626
  // external integration that walks `.demo-ai-refine-row` from the
16956
17627
  // dialog root keeps resolving. The `is-detached` class collapses it
@@ -16970,19 +17641,19 @@ function RTE_Plugin_AIToolkit() {
16970
17641
  // setBusyState / focus-traversal walker keeps resolving.
16971
17642
  var refineRetryChip = append(refineRow, "button", "", "secondary demo-ai-refine-chip");
16972
17643
  refineRetryChip.type = "button";
16973
- refineRetryChip.title = "Re-run with the same prompt";
17644
+ refineRetryChip.title = (obj.__aiT ? obj.__aiT("ai2_retrytitle", "Re-run with the same prompt") : "Re-run with the same prompt");
16974
17645
  refineRetryChip.setAttribute("data-rte-ai-dialog-action", "refine-retry");
16975
- setReviewV2ButtonContent(refineRetryChip, "refresh", "Try again");
17646
+ setReviewV2ButtonContent(refineRetryChip, "refresh", (obj.__aiT ? obj.__aiT("ai2_tryagain", "Try again") : "Try again"));
16976
17647
  var refineShorterChip = append(refineRow, "button", "", "secondary demo-ai-refine-chip");
16977
17648
  refineShorterChip.type = "button";
16978
- refineShorterChip.title = "Make the suggestion shorter";
17649
+ refineShorterChip.title = (obj.__aiT ? obj.__aiT("ai2_shortertitle", "Make the suggestion shorter") : "Make the suggestion shorter");
16979
17650
  refineShorterChip.setAttribute("data-rte-ai-dialog-action", "refine-shorter");
16980
- setReviewV2ButtonContent(refineShorterChip, "shrink", "Shorter");
17651
+ setReviewV2ButtonContent(refineShorterChip, "shrink", (obj.__aiT ? obj.__aiT("ai2_shorter", "Shorter") : "Shorter"));
16981
17652
  var refineLongerChip = append(refineRow, "button", "", "secondary demo-ai-refine-chip");
16982
17653
  refineLongerChip.type = "button";
16983
- refineLongerChip.title = "Expand the suggestion with more detail";
17654
+ refineLongerChip.title = (obj.__aiT ? obj.__aiT("ai2_longertitle", "Expand the suggestion with more detail") : "Expand the suggestion with more detail");
16984
17655
  refineLongerChip.setAttribute("data-rte-ai-dialog-action", "refine-longer");
16985
- setReviewV2ButtonContent(refineLongerChip, "expand", "Longer");
17656
+ setReviewV2ButtonContent(refineLongerChip, "expand", (obj.__aiT ? obj.__aiT("ai2_longer", "Longer") : "Longer"));
16986
17657
  // 2026-05-29 (v20260529a): "Why" disclosure folded into the refine
16987
17658
  // chip row as a 4th ghost chip — the standalone Details toggle row
16988
17659
  // that previously sat on its own line between the chip strip and
@@ -17028,8 +17699,8 @@ function RTE_Plugin_AIToolkit() {
17028
17699
  var refineWhyChip = document.createElement("button");
17029
17700
  refineWhyChip.type = "button";
17030
17701
  refineWhyChip.className = "secondary demo-ai-refine-chip demo-ai-refine-why-chip is-detached";
17031
- refineWhyChip.innerText = "Why";
17032
- refineWhyChip.title = "Show why this AI suggestion was generated";
17702
+ refineWhyChip.innerText = uiT("air_why", "Why");
17703
+ refineWhyChip.title = (obj.__aiT ? obj.__aiT("ai2_whyaria", "Show why this AI suggestion was generated") : "Show why this AI suggestion was generated");
17033
17704
  refineWhyChip.setAttribute("data-rte-ai-dialog-action", "refine-why");
17034
17705
  refineWhyChip.setAttribute("aria-expanded", "false");
17035
17706
 
@@ -17102,7 +17773,7 @@ function RTE_Plugin_AIToolkit() {
17102
17773
 
17103
17774
  var applyRow = append(grid, "div", "", "demo-actions-row demo-ai-apply-row");
17104
17775
  applyRow.setAttribute("role", "group");
17105
- applyRow.setAttribute("aria-label", "Ask AI apply actions");
17776
+ applyRow.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("ai2_applyrow", "Ask AI apply actions") : "Ask AI apply actions"));
17106
17777
  var applyPlanButton = append(applyRow, "button", "", "is-primary");
17107
17778
  applyPlanButton.type = "button";
17108
17779
  applyPlanButton.disabled = true;
@@ -17114,34 +17785,34 @@ function RTE_Plugin_AIToolkit() {
17114
17785
  // dialog-level keydown handler (added below) fires this button
17115
17786
  // when the shortcut hits from anywhere in the dialog outside the
17116
17787
  // source textarea.
17117
- applyPlanButton.title = "Apply AI suggestion (Ctrl+Enter)";
17788
+ applyPlanButton.title = (obj.__aiT ? obj.__aiT("ai2_applytitle", "Apply AI suggestion (Ctrl+Enter)") : "Apply AI suggestion (Ctrl+Enter)");
17118
17789
  applyPlanButton.setAttribute("aria-keyshortcuts", "Control+Enter");
17119
- setReviewV2ButtonContent(applyPlanButton, "check", "Apply");
17790
+ setReviewV2ButtonContent(applyPlanButton, "check", (obj.__aiT ? obj.__aiT("ai2_apply", "Apply") : "Apply"));
17120
17791
  var acceptSelectionButton = append(applyRow, "button", "", "");
17121
17792
  acceptSelectionButton.type = "button";
17122
17793
  acceptSelectionButton.disabled = !snapshot.hasSelection;
17123
17794
  acceptSelectionButton.setAttribute("data-rte-ai-dialog-action", "selection");
17124
- setReviewV2ButtonContent(acceptSelectionButton, "check", "Replace selection");
17795
+ setReviewV2ButtonContent(acceptSelectionButton, "check", (obj.__aiT ? obj.__aiT("ai2_replsel", "Replace selection") : "Replace selection"));
17125
17796
  var previewSelectionButton = append(applyRow, "button", "", "secondary");
17126
17797
  previewSelectionButton.type = "button";
17127
17798
  previewSelectionButton.disabled = !snapshot.hasSelection;
17128
17799
  previewSelectionButton.setAttribute("data-rte-ai-dialog-action", "preview");
17129
- setReviewV2ButtonContent(previewSelectionButton, "eyeOn", "Preview");
17800
+ setReviewV2ButtonContent(previewSelectionButton, "eyeOn", (obj.__aiT ? obj.__aiT("ai2_preview", "Preview") : "Preview"));
17130
17801
  var acceptBelowButton = append(applyRow, "button", "", "secondary");
17131
17802
  acceptBelowButton.type = "button";
17132
17803
  acceptBelowButton.setAttribute("data-rte-ai-dialog-action", "insert");
17133
- setReviewV2ButtonContent(acceptBelowButton, "arrowRight", "Insert below");
17804
+ setReviewV2ButtonContent(acceptBelowButton, "arrowRight", (obj.__aiT ? obj.__aiT("ai2_insbelow", "Insert below") : "Insert below"));
17134
17805
  var acceptDocumentButton = append(applyRow, "button", "", "secondary");
17135
17806
  acceptDocumentButton.type = "button";
17136
17807
  acceptDocumentButton.setAttribute("data-rte-ai-dialog-action", "document");
17137
- // 2026-05-20 (v20260520h): "Replace doc" → "Replace document". The
17808
+ // 2026-05-20 (v20260520h): "Replace doc" → (obj.__aiT ? obj.__aiT("ai2_repldoc", "Replace document") : "Replace document"). The
17138
17809
  // abbreviation read as developer shorthand; the full word costs ~28px
17139
17810
  // of horizontal real estate at the apply row, but this button only
17140
17811
  // appears behind the ⋯ More toggle so the row's spare space is
17141
17812
  // available. Vocabulary now matches getOperationPlanButtonLabel's
17142
- // primary path ("Replace document") and the chat panel's secondary
17143
- // button ("Replace document").
17144
- setReviewV2ButtonContent(acceptDocumentButton, "checkAll", "Replace document");
17813
+ // primary path ((obj.__aiT ? obj.__aiT("ai2_repldoc", "Replace document") : "Replace document")) and the chat panel's secondary
17814
+ // button ((obj.__aiT ? obj.__aiT("ai2_repldoc", "Replace document") : "Replace document")).
17815
+ setReviewV2ButtonContent(acceptDocumentButton, "checkAll", (obj.__aiT ? obj.__aiT("ai2_repldoc", "Replace document") : "Replace document"));
17145
17816
  // 2026-05-08 (v20260508t): "More" toggle pinned just before Discard.
17146
17817
  // Hides Replace selection / Preview / Insert below / Replace doc by
17147
17818
  // default so the default surface only shows Apply (smart primary) +
@@ -17165,8 +17836,8 @@ function RTE_Plugin_AIToolkit() {
17165
17836
  applyRowMoreToggle.type = "button";
17166
17837
  applyRowMoreToggle.setAttribute("data-rte-ai-dialog-action", "more");
17167
17838
  applyRowMoreToggle.setAttribute("aria-expanded", "false");
17168
- applyRowMoreToggle.title = "More apply options";
17169
- applyRowMoreToggle.setAttribute("aria-label", "More apply options");
17839
+ applyRowMoreToggle.title = (obj.__aiT ? obj.__aiT("ai2_moreopts", "More apply options") : "More apply options");
17840
+ applyRowMoreToggle.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("ai2_moreopts", "More apply options") : "More apply options"));
17170
17841
  setReviewV2ButtonContent(applyRowMoreToggle, "more", "");
17171
17842
  applyRowMoreToggle.onclick = function (e) {
17172
17843
  if (e && e.stopPropagation) e.stopPropagation();
@@ -17225,7 +17896,7 @@ function RTE_Plugin_AIToolkit() {
17225
17896
  applyGuidance.className = "demo-ai-apply-guidance";
17226
17897
  applyGuidance.style.display = "none";
17227
17898
  applyGuidance.setAttribute("role", "group");
17228
- applyGuidance.setAttribute("aria-label", "Best next step");
17899
+ applyGuidance.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_bestnext", "Best next step") : "Best next step"));
17229
17900
  var applyGuidanceHeader = append(applyGuidance, "div", "", "demo-ai-apply-guidance-header");
17230
17901
  var applyGuidanceTitle = append(applyGuidanceHeader, "span", "", "demo-ai-apply-guidance-title", "Next");
17231
17902
  var applyGuidanceBadge = append(applyGuidanceHeader, "span", "", "demo-ai-apply-guidance-badge");
@@ -17242,7 +17913,7 @@ function RTE_Plugin_AIToolkit() {
17242
17913
  detailsToggle.type = "button";
17243
17914
  detailsToggle.setAttribute("aria-expanded", "false");
17244
17915
  detailsToggle.setAttribute("aria-controls", "rte-ai-details-" + dialogIdSuffix);
17245
- detailsToggle.title = "Show why and the operation plan";
17916
+ detailsToggle.title = uiT("aiu_showwhy", "Show why and the operation plan");
17246
17917
 
17247
17918
  var insightGrid = append(grid, "div", "", "demo-ai-insight-grid is-collapsed");
17248
17919
  insightGrid.id = detailsToggle.getAttribute("aria-controls");
@@ -17314,7 +17985,7 @@ function RTE_Plugin_AIToolkit() {
17314
17985
 
17315
17986
  function populateDialogModes() {
17316
17987
  var modes = getDialogModes();
17317
- modeSelect.innerHTML = "";
17988
+ modeSelect.innerHTML = __RTE_TT("");
17318
17989
  // 2026-05-20 (v20260520g) — Pass 59: group the 7-mode picker
17319
17990
  // into 2 <optgroup> sections so the dropdown reads as one
17320
17991
  // semantic cluster per intent ("Edit text" vs "Transform")
@@ -17403,9 +18074,9 @@ function RTE_Plugin_AIToolkit() {
17403
18074
 
17404
18075
  function populateTranslateLanguages() {
17405
18076
  var languages = getTranslateLanguages();
17406
- languageSelect.innerHTML = "";
18077
+ languageSelect.innerHTML = __RTE_TT("");
17407
18078
  for (var index = 0; index < languages.length; index++) {
17408
- var option = append(languageSelect, "option", "", "", languages[index].label || languages[index].value);
18079
+ var option = append(languageSelect, "option", "", "", getTranslateLanguageDisplay(languages[index]));
17409
18080
  option.value = languages[index].value || languages[index].label;
17410
18081
  }
17411
18082
  if (options.presetLanguage) {
@@ -17481,7 +18152,7 @@ function RTE_Plugin_AIToolkit() {
17481
18152
  else {
17482
18153
  scopeLabel = "Document";
17483
18154
  }
17484
- return scopeLabel + " · " + countLabel + " chars";
18155
+ return scopeLabel + " \xb7 " + countLabel + " chars";
17485
18156
  }
17486
18157
 
17487
18158
  // 2026-05-19 (v20260519a): mirror the active AI mode into the
@@ -17588,7 +18259,7 @@ function RTE_Plugin_AIToolkit() {
17588
18259
  }
17589
18260
  titleSpan.innerText = resolvedLabel || base;
17590
18261
  if (dialoginner.setAttribute) {
17591
- dialoginner.setAttribute("aria-label", resolvedLabel ? (base + " · " + resolvedLabel) : base);
18262
+ dialoginner.setAttribute("aria-label", resolvedLabel ? (base + " \xb7 " + resolvedLabel) : base);
17592
18263
  }
17593
18264
  }
17594
18265
 
@@ -17674,16 +18345,16 @@ function RTE_Plugin_AIToolkit() {
17674
18345
  if (sourceArea && !sourceArea.value) {
17675
18346
  var modeId = modeSelect.value || "";
17676
18347
  var placeholders = {
17677
- proofread: "Paste text to proofread…",
17678
- rewrite: "Paste text to rewrite…",
17679
- shorten: "Paste text to shorten…",
17680
- expand: "Paste text to expand…",
17681
- summarize: "Paste text to summarize…",
17682
- translate: "Paste text to translate…",
17683
- justify: "Paste text to rewrite + explain…",
17684
- explain: "Paste text to explain…"
18348
+ proofread: "Paste text to proofread\u2026",
18349
+ rewrite: "Paste text to rewrite\u2026",
18350
+ shorten: "Paste text to shorten\u2026",
18351
+ expand: "Paste text to expand\u2026",
18352
+ summarize: "Paste text to summarize\u2026",
18353
+ translate: "Paste text to translate\u2026",
18354
+ justify: "Paste text to rewrite + explain\u2026",
18355
+ explain: "Paste text to explain\u2026"
17685
18356
  };
17686
- sourceArea.placeholder = placeholders[modeId] || "Type or paste text…";
18357
+ sourceArea.placeholder = placeholders[modeId] || "Type or paste text\u2026";
17687
18358
  }
17688
18359
  // 2026-05-08 (v20260508s): refresh the header mode-name chip.
17689
18360
  // For Translate the chip carries the target language so users
@@ -17691,7 +18362,7 @@ function RTE_Plugin_AIToolkit() {
17691
18362
  if (modeChip) {
17692
18363
  var chipLabel = modeLabel || "";
17693
18364
  if (modeLabel && modeSelect.value === "translate") {
17694
- chipLabel = modeLabel + " · " + getTranslateLanguageLabel(languageSelect.value || "spanish");
18365
+ chipLabel = modeLabel + " \xb7 " + getTranslateLanguageLabel(languageSelect.value || "spanish");
17695
18366
  }
17696
18367
  modeChip.innerText = chipLabel;
17697
18368
  modeChip.style.display = chipLabel ? "" : "none";
@@ -17860,9 +18531,9 @@ function RTE_Plugin_AIToolkit() {
17860
18531
  var docChars = snap ? (snap.wholeText || "").length : 0;
17861
18532
  scopeOptionSelection.disabled = !hasSelection;
17862
18533
  scopeOptionSelection.text = hasSelection
17863
- ? "Selection"
17864
- : "Selection (none)";
17865
- scopeOptionDocument.text = "Document";
18534
+ ? (obj.__aiT ? obj.__aiT("aid_selection", "Selection") : "Selection")
18535
+ : (obj.__aiT ? obj.__aiT("aid_selectionnone", "Selection (none)") : "Selection (none)");
18536
+ scopeOptionDocument.text = (obj.__aiT ? obj.__aiT("aid_document", "Document") : "Document");
17866
18537
  scopeSelect.value = activeScope === "selection" ? "selection" : "document";
17867
18538
  scopeSelect.setAttribute("title", buildDialogScopeHint(snap, scope));
17868
18539
  // 2026-06-01 (v20260601a): swap the <select> for a quiet inline
@@ -17877,7 +18548,7 @@ function RTE_Plugin_AIToolkit() {
17877
18548
  // 2026-05-20 (v20260520i) — Pass 61: caption mirrors the option
17878
18549
  // label — scope name only, no char-count suffix.
17879
18550
  if (scopeCaption) {
17880
- scopeCaption.innerText = "Document";
18551
+ scopeCaption.innerText = (obj.__aiT ? obj.__aiT("aid_document", "Document") : "Document");
17881
18552
  }
17882
18553
  if (compactControls && compactControls.classList) {
17883
18554
  if (hasSelection) {
@@ -17892,8 +18563,8 @@ function RTE_Plugin_AIToolkit() {
17892
18563
 
17893
18564
  function formatDialogScopeCount(n) {
17894
18565
  if (!n) return "0";
17895
- if (n > 999) return (Math.round(n / 100) / 10) + "K chars";
17896
- return n + " chars";
18566
+ if (n > 999) return (obj.__aiT ? obj.__aiT("aid_kchars", "{0}K chars", Math.round(n / 100) / 10) : (Math.round(n / 100) / 10) + "K chars");
18567
+ return obj.__aiT ? obj.__aiT("aid_chars", "{0} chars", n) : n + " chars";
17897
18568
  }
17898
18569
 
17899
18570
  function markDialogSourceDetached() {
@@ -17940,11 +18611,11 @@ function RTE_Plugin_AIToolkit() {
17940
18611
  }
17941
18612
 
17942
18613
  function getDialogLanguageChangeMessage(language) {
17943
- return "Language → " + getTranslateLanguageLabel(language || "spanish") + ". Re-run to refresh.";
18614
+ return "Language \u2192 " + getTranslateLanguageLabel(language || "spanish") + ". Re-run to refresh.";
17944
18615
  }
17945
18616
 
17946
18617
  function getDialogScopeChangeMessage(scope) {
17947
- return "Scope → " + (scope === "selection" ? "selection" : "document") + ". Re-run to refresh.";
18618
+ return "Scope \u2192 " + (scope === "selection" ? "selection" : "document") + ". Re-run to refresh.";
17948
18619
  }
17949
18620
 
17950
18621
  function getDialogEditorReloadMessage(scope) {
@@ -18029,70 +18700,70 @@ function RTE_Plugin_AIToolkit() {
18029
18700
  : "Select text in the editor, then reload it into Ask AI.";
18030
18701
  }
18031
18702
  if (actionId === "load-document") {
18032
- return "Reload the current editor document into Ask AI.";
18703
+ return (obj.__aiT ? obj.__aiT("aid_reloaddesc", "Reload the current editor document into Ask AI.") : "Reload the current editor document into Ask AI.");
18033
18704
  }
18034
18705
  if (actionId === "run") {
18035
18706
  return actionState.hasSource
18036
- ? "Generate a new AI suggestion from the current source text using the selected mode."
18037
- : "Load or type source text, then run Ask AI to generate a suggestion.";
18707
+ ? (obj.__aiT ? obj.__aiT("aid_gendesc", "Generate a new AI suggestion from the current source text using the selected mode.") : "Generate a new AI suggestion from the current source text using the selected mode.")
18708
+ : (obj.__aiT ? obj.__aiT("aid_loaddesc", "Load or type source text, then run Ask AI to generate a suggestion.") : "Load or type source text, then run Ask AI to generate a suggestion.");
18038
18709
  }
18039
18710
  if (actionId === "rerun") {
18040
18711
  return actionState.staleAction === "source"
18041
- ? "Refresh this suggestion from the current dialog source before applying it."
18042
- : "Refresh this suggestion from the current editor state before applying it.";
18712
+ ? (obj.__aiT ? obj.__aiT("ai2_d_refreshsrc", "Refresh this suggestion from the current dialog source before applying it.") : "Refresh this suggestion from the current dialog source before applying it.")
18713
+ : (obj.__aiT ? obj.__aiT("ai2_d_refreshed", "Refresh this suggestion from the current editor state before applying it.") : "Refresh this suggestion from the current editor state before applying it.");
18043
18714
  }
18044
18715
  if (actionId === "copy-to-source") {
18045
18716
  return actionState.hasResult
18046
- ? "Copy the current AI result back into the source field before rerunning it."
18047
- : "Run Ask AI first, then copy the generated result back into the source field.";
18717
+ ? (obj.__aiT ? obj.__aiT("ai2_d_copyback", "Copy the current AI result back into the source field before rerunning it.") : "Copy the current AI result back into the source field before rerunning it.")
18718
+ : (obj.__aiT ? obj.__aiT("ai2_d_copyfirst", "Run Ask AI first, then copy the generated result back into the source field.") : "Run Ask AI first, then copy the generated result back into the source field.");
18048
18719
  }
18049
18720
  if (actionId === "apply") {
18050
18721
  if (actionState.pendingIndexes.length > 1) {
18051
18722
  return actionState.pendingSelectionDependent && !actionState.hasSelection
18052
- ? "Select text before applying this selection-based AI plan."
18053
- : "Run the remaining " + actionState.pendingIndexes.length + "-step AI plan directly in the editor.";
18723
+ ? (obj.__aiT ? obj.__aiT("ai2_d_selectplan", "Select text before applying this selection-based AI plan.") : "Select text before applying this selection-based AI plan.")
18724
+ : (obj.__aiT ? obj.__aiT("ai2_d_steps", "Run the remaining {0}-step AI plan directly in the editor.", actionState.pendingIndexes.length) : "Run the remaining " + actionState.pendingIndexes.length + "-step AI plan directly in the editor.");
18054
18725
  }
18055
18726
  switch (actionState.pendingOperationType) {
18056
18727
  case "preview-suggestion":
18057
18728
  return actionState.hasSelection
18058
- ? "Show the prepared inline diff in the editor before deciding it."
18059
- : "Select text before previewing this prepared inline diff in the editor.";
18729
+ ? (obj.__aiT ? obj.__aiT("ai2_d_showdiff", "Show the prepared inline diff in the editor before deciding it.") : "Show the prepared inline diff in the editor before deciding it.")
18730
+ : (obj.__aiT ? obj.__aiT("ai2_d_selectdiff", "Select text before previewing this prepared inline diff in the editor.") : "Select text before previewing this prepared inline diff in the editor.");
18060
18731
  case "replace-selection":
18061
18732
  return actionState.hasSelection
18062
- ? "Replace the current selection with this prepared draft."
18063
- : "Select text before replacing it with this prepared draft.";
18733
+ ? (obj.__aiT ? obj.__aiT("ai2_d_replsel", "Replace the current selection with this prepared draft.") : "Replace the current selection with this prepared draft.")
18734
+ : (obj.__aiT ? obj.__aiT("ai2_d_selectrepl", "Select text before replacing it with this prepared draft.") : "Select text before replacing it with this prepared draft.");
18064
18735
  case "replace-document":
18065
- return "Replace the full document with this prepared draft.";
18736
+ return (obj.__aiT ? obj.__aiT("ai2_d_repldoc", "Replace the full document with this prepared draft.") : "Replace the full document with this prepared draft.");
18066
18737
  case "insert-below":
18067
18738
  return actionState.hasSelection
18068
- ? "Insert this prepared draft below the current selection."
18069
- : "Insert this prepared draft below the current content.";
18739
+ ? (obj.__aiT ? obj.__aiT("ai2_d_insbelowsel", "Insert this prepared draft below the current selection.") : "Insert this prepared draft below the current selection.")
18740
+ : (obj.__aiT ? obj.__aiT("ai2_d_insbelow", "Insert this prepared draft below the current content.") : "Insert this prepared draft below the current content.");
18070
18741
  case "add-comment":
18071
- return "Add this prepared AI comment without rewriting the document body.";
18742
+ return (obj.__aiT ? obj.__aiT("ai2_d_comment", "Add this prepared AI comment without rewriting the document body.") : "Add this prepared AI comment without rewriting the document body.");
18072
18743
  default:
18073
- return "Run the prepared AI plan directly in the editor.";
18744
+ return (obj.__aiT ? obj.__aiT("ai2_d_runplan", "Run the prepared AI plan directly in the editor.") : "Run the prepared AI plan directly in the editor.");
18074
18745
  }
18075
18746
  }
18076
18747
  if (actionId === "selection") {
18077
18748
  return actionState.hasSelection
18078
- ? "Replace the current selection with this prepared draft."
18079
- : "Select text before replacing it with this prepared draft.";
18749
+ ? (obj.__aiT ? obj.__aiT("ai2_d_replsel", "Replace the current selection with this prepared draft.") : "Replace the current selection with this prepared draft.")
18750
+ : (obj.__aiT ? obj.__aiT("ai2_d_selectrepl", "Select text before replacing it with this prepared draft.") : "Select text before replacing it with this prepared draft.");
18080
18751
  }
18081
18752
  if (actionId === "preview") {
18082
18753
  return actionState.hasSelection
18083
- ? "Show this change inline in the editor before deciding it."
18084
- : "Select text to preview this change inline in the editor.";
18754
+ ? (obj.__aiT ? obj.__aiT("ai2_d_previewsel", "Show this change inline in the editor before deciding it.") : "Show this change inline in the editor before deciding it.")
18755
+ : (obj.__aiT ? obj.__aiT("ai2_d_previewselect", "Select text to preview this change inline in the editor.") : "Select text to preview this change inline in the editor.");
18085
18756
  }
18086
18757
  if (actionId === "insert") {
18087
18758
  return actionState.hasSelection
18088
- ? "Insert this prepared draft below the current selection."
18089
- : "Insert this prepared draft below the current content.";
18759
+ ? (obj.__aiT ? obj.__aiT("ai2_d_insbelowsel", "Insert this prepared draft below the current selection.") : "Insert this prepared draft below the current selection.")
18760
+ : (obj.__aiT ? obj.__aiT("ai2_d_insbelow", "Insert this prepared draft below the current content.") : "Insert this prepared draft below the current content.");
18090
18761
  }
18091
18762
  if (actionId === "document") {
18092
- return "Replace the full document with this prepared draft.";
18763
+ return (obj.__aiT ? obj.__aiT("ai2_d_repldoc", "Replace the full document with this prepared draft.") : "Replace the full document with this prepared draft.");
18093
18764
  }
18094
18765
  if (actionId === "reject") {
18095
- return "Dismiss this prepared suggestion without changing the document.";
18766
+ return (obj.__aiT ? obj.__aiT("ai2_d_dismiss", "Dismiss this prepared suggestion without changing the document.") : "Dismiss this prepared suggestion without changing the document.");
18096
18767
  }
18097
18768
  return "";
18098
18769
  }
@@ -18180,22 +18851,22 @@ function RTE_Plugin_AIToolkit() {
18180
18851
  var pendingOperationType = actionState.pendingOperationType || "";
18181
18852
  if (actionState.actionScope === "selection") {
18182
18853
  if (actionState.hasSelection && pendingOperationType !== "preview-suggestion") {
18183
- addDialogReadyAlternative(alternatives, covered, "preview", "preview inline");
18854
+ addDialogReadyAlternative(alternatives, covered, "preview", (obj.__aiT ? obj.__aiT("ai2_alt_preview", "preview inline") : "preview inline"));
18184
18855
  }
18185
18856
  if (actionState.hasSelection && pendingOperationType !== "replace-selection") {
18186
- addDialogReadyAlternative(alternatives, covered, "selection", "replace the selection");
18857
+ addDialogReadyAlternative(alternatives, covered, "selection", (obj.__aiT ? obj.__aiT("ai2_alt_selection", "replace the selection") : "replace the selection"));
18187
18858
  }
18188
18859
  if (actionState.canInsert && pendingOperationType !== "insert-below") {
18189
- addDialogReadyAlternative(alternatives, covered, "insert", "insert below");
18860
+ addDialogReadyAlternative(alternatives, covered, "insert", (obj.__aiT ? obj.__aiT("ai2_alt_insert", "insert below") : "insert below"));
18190
18861
  }
18191
18862
  return alternatives;
18192
18863
  }
18193
18864
  if (actionState.actionScope === "document") {
18194
18865
  if (actionState.canReplaceDocument && pendingOperationType !== "replace-document") {
18195
- addDialogReadyAlternative(alternatives, covered, "document", "replace the document");
18866
+ addDialogReadyAlternative(alternatives, covered, "document", (obj.__aiT ? obj.__aiT("ai2_alt_document", "replace the document") : "replace the document"));
18196
18867
  }
18197
18868
  if (actionState.canInsert && pendingOperationType !== "insert-below") {
18198
- addDialogReadyAlternative(alternatives, covered, "insert", "insert below");
18869
+ addDialogReadyAlternative(alternatives, covered, "insert", (obj.__aiT ? obj.__aiT("ai2_alt_insert", "insert below") : "insert below"));
18199
18870
  }
18200
18871
  }
18201
18872
  return alternatives;
@@ -18213,10 +18884,10 @@ function RTE_Plugin_AIToolkit() {
18213
18884
  ? (recommended.label || "").replace(/\s+/g, " ").trim()
18214
18885
  : "";
18215
18886
  if (recommendedLabel) {
18216
- return "Ready · " + recommendedLabel;
18887
+ return "Ready \xb7 " + recommendedLabel;
18217
18888
  }
18218
18889
  if (actionState.actionScope === "selection" && !actionState.hasSelection) {
18219
- return "Ready · Insert below";
18890
+ return "Ready \xb7 Insert below";
18220
18891
  }
18221
18892
  return "Ready";
18222
18893
  }
@@ -18294,25 +18965,25 @@ function RTE_Plugin_AIToolkit() {
18294
18965
  parts.push(detail);
18295
18966
  }
18296
18967
  if (alternativeText) {
18297
- parts.push("Other available actions: " + alternativeText + ".");
18968
+ parts.push(obj.__aiT ? obj.__aiT("aid_otheractions", "Other available actions: {0}.", alternativeText) : "Other available actions: " + alternativeText + ".");
18298
18969
  }
18299
18970
  if (isRecommended) {
18300
- parts.push("Recommended next step.");
18971
+ parts.push((obj.__aiT ? obj.__aiT("aid_recommended", "Recommended next step.") : "Recommended next step."));
18301
18972
  button.setAttribute("data-rte-ai-dialog-recommended", "true");
18302
18973
  }
18303
18974
  else {
18304
18975
  button.removeAttribute("data-rte-ai-dialog-recommended");
18305
18976
  }
18306
- button.setAttribute("aria-label", parts.join(". "));
18977
+ button.setAttribute("aria-label", joinSentences(parts));
18307
18978
  var titleParts = [label];
18308
18979
  if (detail) {
18309
18980
  titleParts.push(detail);
18310
18981
  }
18311
18982
  if (alternativeText) {
18312
- titleParts.push("Other available actions: " + alternativeText + ".");
18983
+ titleParts.push(obj.__aiT ? obj.__aiT("aid_otheractions", "Other available actions: {0}.", alternativeText) : "Other available actions: " + alternativeText + ".");
18313
18984
  }
18314
18985
  if (isRecommended) {
18315
- titleParts.push("Recommended next step.");
18986
+ titleParts.push((obj.__aiT ? obj.__aiT("aid_recommended", "Recommended next step.") : "Recommended next step."));
18316
18987
  }
18317
18988
  // 2026-05-22 (v20260522a): preserve any pre-set keyboard shortcut
18318
18989
  // hint on the button by reading aria-keyshortcuts (the WAI-ARIA
@@ -18322,7 +18993,7 @@ function RTE_Plugin_AIToolkit() {
18322
18993
  // shortcut would only be discoverable via screen-reader
18323
18994
  // announcement of aria-keyshortcuts and never via mouse hover.
18324
18995
  // Formatting matches the Generate button's pre-existing title
18325
- // suffix ("Generate AI suggestion (Ctrl+Enter)").
18996
+ // suffix ((obj.__aiT ? obj.__aiT("ai2_gentitle", "Generate AI suggestion (Ctrl+Enter)") : "Generate AI suggestion (Ctrl+Enter)")).
18326
18997
  var keyHint = button.getAttribute && button.getAttribute("aria-keyshortcuts");
18327
18998
  if (keyHint) {
18328
18999
  var keyHintLabel = keyHint
@@ -18379,7 +19050,7 @@ function RTE_Plugin_AIToolkit() {
18379
19050
  return {
18380
19051
  actionId: "run",
18381
19052
  button: runButton,
18382
- label: "Ask AI",
19053
+ label: (obj.__aiT ? obj.__aiT("aiassist", "Ask AI") : "Ask AI"),
18383
19054
  detail: getDialogActionDetail("run", actionState)
18384
19055
  };
18385
19056
  }
@@ -18411,7 +19082,7 @@ function RTE_Plugin_AIToolkit() {
18411
19082
  return {
18412
19083
  actionId: "document",
18413
19084
  button: acceptDocumentButton,
18414
- label: (acceptDocumentButton.innerText || acceptDocumentButton.textContent || "").replace(/\s+/g, " ").trim() || "Replace document",
19085
+ label: (acceptDocumentButton.innerText || acceptDocumentButton.textContent || "").replace(/\s+/g, " ").trim() || (obj.__aiT ? obj.__aiT("ai2_repldoc", "Replace document") : "Replace document"),
18415
19086
  detail: getDialogActionDetail("document", actionState)
18416
19087
  };
18417
19088
  }
@@ -18460,15 +19131,15 @@ function RTE_Plugin_AIToolkit() {
18460
19131
  applyGuidance.style.display = "none";
18461
19132
  applyGuidanceBadge.innerText = "";
18462
19133
  applyGuidanceDetail.innerText = "";
18463
- applyGuidance.setAttribute("aria-label", "No recommended Ask AI follow-up action yet.");
19134
+ applyGuidance.setAttribute("aria-label", uiT("aiu_norecommended", "No recommended Ask AI follow-up action yet."));
18464
19135
  return;
18465
19136
  }
18466
19137
  applyGuidance.style.display = "";
18467
- applyGuidanceTitle.innerText = "Best next step";
19138
+ applyGuidanceTitle.innerText = (obj.__aiT ? obj.__aiT("aid_bestnext", "Best next step") : "Best next step");
18468
19139
  applyGuidanceBadge.innerText = recommended.label || "";
18469
19140
  var summaryDetail = getDialogRecommendedSummaryDetail(actionState, recommended);
18470
19141
  applyGuidanceDetail.innerText = summaryDetail || "";
18471
- applyGuidance.setAttribute("aria-label", "Best next step: " + (recommended.label || "Action") + "." + (summaryDetail ? " " + summaryDetail : ""));
19142
+ applyGuidance.setAttribute("aria-label", (obj.__aiT ? obj.__aiT("aid_bestnextaria", "Best next step: {0}.", recommended.label || "Action") : "Best next step: " + (recommended.label || "Action") + ".") + (summaryDetail ? " " + summaryDetail : ""));
18472
19143
  }
18473
19144
 
18474
19145
  function rerunDialogPlanFromEditor() {
@@ -18495,7 +19166,7 @@ function RTE_Plugin_AIToolkit() {
18495
19166
  function renderOperationPlan(resolved) {
18496
19167
  var operations = resolved && resolved.operations ? resolved.operations : [];
18497
19168
  var actionState = getDialogActionState();
18498
- planList.innerHTML = "";
19169
+ planList.innerHTML = __RTE_TT("");
18499
19170
  planStatusMessage.innerText = dialoginner.__aiPlanStale
18500
19171
  ? (dialoginner.__aiPlanStaleReason || "Editor changed. Re-run to refresh.")
18501
19172
  : "";
@@ -18515,7 +19186,7 @@ function RTE_Plugin_AIToolkit() {
18515
19186
  planSummary.innerText = "";
18516
19187
  planStatusAction.style.display = "none";
18517
19188
  applyPlanButton.disabled = true;
18518
- applyPlanButton.innerText = "Apply";
19189
+ applyPlanButton.innerText = uiT("aiu_apply", "Apply");
18519
19190
  return;
18520
19191
  }
18521
19192
 
@@ -18598,10 +19269,10 @@ function RTE_Plugin_AIToolkit() {
18598
19269
  + " \u2022 " + skippedCount + " skipped";
18599
19270
  applyPlanButton.disabled = !pendingIndexes.length || !!dialoginner.__aiPlanStale || !actionState.canApplyPlan;
18600
19271
  if (dialoginner.__aiPlanStale) {
18601
- applyPlanButton.innerText = "Re-run to refresh plan";
19272
+ applyPlanButton.innerText = uiT("aiu_rerunplan", "Re-run to refresh plan");
18602
19273
  }
18603
19274
  else if (!pendingIndexes.length) {
18604
- applyPlanButton.innerText = "No pending steps";
19275
+ applyPlanButton.innerText = uiT("aiu_nopending", "No pending steps");
18605
19276
  }
18606
19277
  else if (actionState.pendingSelectionDependent && !actionState.hasSelection) {
18607
19278
  applyPlanButton.innerText = getSelectionRequiredPlanButtonLabel(pendingOperations);
@@ -18610,7 +19281,7 @@ function RTE_Plugin_AIToolkit() {
18610
19281
  applyPlanButton.innerText = getOperationPlanButtonLabel(resolved);
18611
19282
  }
18612
19283
  else {
18613
- applyPlanButton.innerText = "Apply " + pendingIndexes.length + " pending steps";
19284
+ applyPlanButton.innerText = uiT("aiu_applypending", "Apply {0} pending steps", pendingIndexes.length);
18614
19285
  }
18615
19286
  }
18616
19287
 
@@ -18672,7 +19343,7 @@ function RTE_Plugin_AIToolkit() {
18672
19343
  // setBusyState(false) is called.
18673
19344
  if (isBusy) {
18674
19345
  runButton.classList.add("is-busy");
18675
- setReviewV2ButtonContent(runButton, "spinner", "Thinking...");
19346
+ setReviewV2ButtonContent(runButton, "spinner", (obj.__aiT ? obj.__aiT("aic_thinking", "Thinking...") : "Thinking..."));
18676
19347
  } else {
18677
19348
  runButton.classList.remove("is-busy");
18678
19349
  }
@@ -18792,7 +19463,7 @@ function RTE_Plugin_AIToolkit() {
18792
19463
  // hidden apply options on the kebab's title + aria-label so
18793
19464
  // users discover *what's behind the dots* on hover or with a
18794
19465
  // screen reader, instead of clicking blind. Pre-pass the
18795
- // tooltip read as "More apply options" — accurate but content-
19466
+ // tooltip read as (obj.__aiT ? obj.__aiT("ai2_moreopts", "More apply options") : "More apply options") — accurate but content-
18796
19467
  // free; now it reads "More apply options (3): Replace
18797
19468
  // selection, Insert below, Replace doc" so the count + the
18798
19469
  // names live where the affordance is. Same shape Notion AI's
@@ -18804,7 +19475,7 @@ function RTE_Plugin_AIToolkit() {
18804
19475
  if (showsSelectionAction) moreOptionNames.push("Replace selection");
18805
19476
  if (showsPreviewAction) moreOptionNames.push("Preview");
18806
19477
  if (showsInsertAction) moreOptionNames.push("Insert below");
18807
- if (showsDocumentAction) moreOptionNames.push("Replace document");
19478
+ if (showsDocumentAction) moreOptionNames.push((obj.__aiT ? obj.__aiT("ai2_repldoc", "Replace document") : "Replace document"));
18808
19479
  var moreCount = moreOptionNames.length;
18809
19480
  var anyMoreAvailable = moreCount > 0;
18810
19481
  applyRowMoreToggle.style.display = anyMoreAvailable ? "" : "none";
@@ -18829,7 +19500,7 @@ function RTE_Plugin_AIToolkit() {
18829
19500
  // 2026-05-08 (v20260508s): clarify the run button — when a
18830
19501
  // result already exists the button re-runs the same source, so
18831
19502
  // call it Regenerate. Also flips when the plan goes stale.
18832
- var runLabel = (hasResult || isStale) ? "Regenerate" : "Generate";
19503
+ var runLabel = (hasResult || isStale) ? (obj.__aiT ? obj.__aiT("aid_regenerate", "Regenerate") : "Regenerate") : (obj.__aiT ? obj.__aiT("aid_generate", "Generate") : "Generate");
18833
19504
  setReviewV2ButtonContent(runButton, "sparkles", runLabel);
18834
19505
  // 2026-05-17 (v20260517a): re-attach the keyboard hint chip,
18835
19506
  // since setReviewV2ButtonContent clears textContent and loses
@@ -18991,7 +19662,7 @@ function RTE_Plugin_AIToolkit() {
18991
19662
  }
18992
19663
  syncDialogScopeUi();
18993
19664
  if (!normalizeText(sourceArea.value)) {
18994
- status.innerText = "Load or type source text before running Ask AI.";
19665
+ status.innerText = uiT("aiu_loadsource", "Load or type source text before running Ask AI.");
18995
19666
  updatePreview();
18996
19667
  return;
18997
19668
  }