autumnnote 1.0.7 → 1.0.9

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 (40) hide show
  1. package/README.md +332 -638
  2. package/dist/autumnnote.css +40 -4
  3. package/dist/autumnnote.es.js +3375 -262
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +3374 -261
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/js/Context.js +4 -0
  9. package/src/js/editing/Style.js +195 -44
  10. package/src/js/editing/Typing.js +2 -2
  11. package/src/js/i18n/de.js +320 -0
  12. package/src/js/i18n/en.js +328 -0
  13. package/src/js/i18n/es.js +320 -0
  14. package/src/js/i18n/fr.js +321 -0
  15. package/src/js/i18n/index.js +59 -0
  16. package/src/js/i18n/ja.js +321 -0
  17. package/src/js/i18n/ko.js +320 -0
  18. package/src/js/i18n/vi.js +321 -0
  19. package/src/js/i18n/zh.js +321 -0
  20. package/src/js/index.js +2 -1
  21. package/src/js/module/CodeTooltip.js +12 -9
  22. package/src/js/module/ContextMenu.js +13 -11
  23. package/src/js/module/Editor.js +14 -3
  24. package/src/js/module/EmojiDialog.js +19 -7
  25. package/src/js/module/FindReplace.js +14 -13
  26. package/src/js/module/IconDialog.js +25 -13
  27. package/src/js/module/ImageDialog.js +25 -20
  28. package/src/js/module/ImageTooltip.js +13 -12
  29. package/src/js/module/LinkDialog.js +10 -9
  30. package/src/js/module/LinkTooltip.js +6 -5
  31. package/src/js/module/Placeholder.js +6 -1
  32. package/src/js/module/ShortcutsDialog.js +5 -4
  33. package/src/js/module/Statusbar.js +6 -5
  34. package/src/js/module/TableTooltip.js +412 -102
  35. package/src/js/module/Toolbar.js +21 -15
  36. package/src/js/module/VideoDialog.js +10 -9
  37. package/src/js/module/VideoTooltip.js +12 -11
  38. package/src/js/settings.js +4 -0
  39. package/src/styles/autumnnote.scss +50 -5
  40. package/types/index.d.ts +58 -1
@@ -293,8 +293,24 @@
293
293
  }
294
294
  /**
295
295
  * Strikethrough / removes strikethrough.
296
+ * Falls back to manual DOM manipulation inside nested formats where
297
+ * execCommand's state detection is unreliable (mirrors underline() logic).
296
298
  */
297
- var strikethrough = () => execCommand("strikeThrough");
299
+ function strikethrough() {
300
+ const sel = window.getSelection();
301
+ if (!sel || !sel.rangeCount) return;
302
+ let sc = sel.getRangeAt(0).startContainer;
303
+ if (sc.nodeType === 3) sc = sc.parentElement;
304
+ const sEl = sc && sc.closest && (sc.closest("s") || sc.closest("strike"));
305
+ const nativeState = document.queryCommandState("strikeThrough");
306
+ if (sEl && !nativeState) {
307
+ const parent = sEl.parentNode;
308
+ while (sEl.firstChild) parent.insertBefore(sEl.firstChild, sEl);
309
+ parent.removeChild(sEl);
310
+ return;
311
+ }
312
+ execCommand("strikeThrough");
313
+ }
298
314
  /**
299
315
  * Superscript toggle.
300
316
  */
@@ -327,6 +343,22 @@
327
343
  function fontSize(size, editable = document) {
328
344
  const sel = window.getSelection();
329
345
  const wasCollapsed = !sel || !sel.rangeCount || sel.getRangeAt(0).collapsed;
346
+ if (wasCollapsed && sel && sel.rangeCount > 0) {
347
+ try {
348
+ const range = sel.getRangeAt(0);
349
+ const span = document.createElement("span");
350
+ span.style.fontSize = size;
351
+ const zwsNode = document.createTextNode("​");
352
+ span.appendChild(zwsNode);
353
+ range.insertNode(span);
354
+ const nr = document.createRange();
355
+ nr.setStart(zwsNode, zwsNode.textContent.length);
356
+ nr.collapse(true);
357
+ sel.removeAllRanges();
358
+ sel.addRange(nr);
359
+ } catch (_) {}
360
+ return;
361
+ }
330
362
  execCommand("fontSize", "7");
331
363
  const scope = editable instanceof HTMLElement ? editable : document;
332
364
  const newSpans = [];
@@ -338,27 +370,17 @@
338
370
  el.parentNode.removeChild(el);
339
371
  newSpans.push(span);
340
372
  });
341
- if (sel && newSpans.length > 0) {
373
+ if (!wasCollapsed && sel && newSpans.length > 0) {
342
374
  const first = newSpans[0];
343
375
  const last = newSpans[newSpans.length - 1];
344
376
  try {
345
- if (wasCollapsed) {
346
- if (!first.firstChild) first.appendChild(document.createTextNode("​"));
347
- const nr = document.createRange();
348
- const anchor = first.firstChild;
349
- nr.setStart(anchor, anchor.textContent.length);
350
- nr.collapse(true);
351
- sel.removeAllRanges();
352
- sel.addRange(nr);
353
- } else {
354
- const nr = document.createRange();
355
- const startNode = first.firstChild || first;
356
- const endNode = last.lastChild || last;
357
- nr.setStart(startNode, 0);
358
- nr.setEnd(endNode, endNode.nodeType === Node.TEXT_NODE ? endNode.textContent.length : endNode.childNodes.length);
359
- sel.removeAllRanges();
360
- sel.addRange(nr);
361
- }
377
+ const nr = document.createRange();
378
+ const startNode = first.firstChild || first;
379
+ const endNode = last.lastChild || last;
380
+ nr.setStart(startNode, 0);
381
+ nr.setEnd(endNode, endNode.nodeType === Node.TEXT_NODE ? endNode.textContent.length : endNode.childNodes.length);
382
+ sel.removeAllRanges();
383
+ sel.addRange(nr);
362
384
  } catch (_) {}
363
385
  }
364
386
  }
@@ -389,8 +411,57 @@
389
411
  var indent = () => execCommand("indent");
390
412
  /**
391
413
  * Outdents the list or block.
414
+ * G.5: When cursor is inside a checklist item, "outdent" means converting
415
+ * that item back to a regular <p> element rather than calling execCommand
416
+ * (which would destroy the ul > li checklist structure).
417
+ */
418
+ function outdent() {
419
+ const sel = window.getSelection();
420
+ if (sel && sel.rangeCount) {
421
+ let container = sel.getRangeAt(0).commonAncestorContainer;
422
+ if (container.nodeType === 3) container = container.parentElement;
423
+ const checkLi = container && container.closest && container.closest(".an-checklist li");
424
+ if (checkLi) {
425
+ _checklistItemToP(checkLi);
426
+ return;
427
+ }
428
+ }
429
+ execCommand("outdent");
430
+ }
431
+ /**
432
+ * G.5 helper: splits a checklist at checkLi, converts it to a <p>,
433
+ * and keeps items before/after as separate checklists.
434
+ * @param {HTMLElement} checkLi
392
435
  */
393
- var outdent = () => execCommand("outdent");
436
+ function _checklistItemToP(checkLi) {
437
+ const checkUl = checkLi.closest(".an-checklist");
438
+ if (!checkUl) return;
439
+ const allLis = Array.from(checkUl.children);
440
+ const liIndex = allLis.indexOf(checkLi);
441
+ const afterLis = allLis.slice(liIndex + 1);
442
+ const p = document.createElement("p");
443
+ p.textContent = Array.from(checkLi.childNodes).filter((n) => !(n.nodeType === 1 && n.tagName === "INPUT")).map((n) => n.textContent).join("").replace(/\u200B/g, "").trim() || "\xA0";
444
+ if (afterLis.length > 0) {
445
+ const newUl = document.createElement("ul");
446
+ newUl.className = "an-checklist";
447
+ afterLis.forEach((li) => newUl.appendChild(li));
448
+ checkUl.parentNode.insertBefore(newUl, checkUl.nextSibling);
449
+ }
450
+ checkUl.parentNode.insertBefore(p, checkUl.nextSibling);
451
+ checkUl.removeChild(checkLi);
452
+ if (checkUl.children.length === 0) checkUl.parentNode.removeChild(checkUl);
453
+ try {
454
+ const nr = document.createRange();
455
+ const firstChild = p.firstChild;
456
+ nr.setStart(firstChild && firstChild.nodeType === 3 ? firstChild : p, 0);
457
+ nr.collapse(true);
458
+ const s = window.getSelection();
459
+ if (s) {
460
+ s.removeAllRanges();
461
+ s.addRange(nr);
462
+ }
463
+ } catch {}
464
+ }
394
465
  /**
395
466
  * Inserts an unordered list or converts selection.
396
467
  */
@@ -594,10 +665,60 @@
594
665
  sel.addRange(nr);
595
666
  return;
596
667
  }
597
- const lines = sel.toString().split(/\r?\n/).filter((l) => l.trim().length > 0);
598
- if (lines.length === 0) return;
599
- const items = lines.map((l) => `<li><input type="checkbox" contenteditable="false">${l || "​"}</li>`).join("");
600
- document.execCommand("insertHTML", false, `<ul class="an-checklist">${items}</ul>`);
668
+ if (!sel.toString().replace(/[\u00a0\u200B]/g, " ").trim()) return;
669
+ const BLOCK_TAGS_MULTI = new Set([
670
+ "P",
671
+ "DIV",
672
+ "H1",
673
+ "H2",
674
+ "H3",
675
+ "H4",
676
+ "H5",
677
+ "H6",
678
+ "BLOCKQUOTE",
679
+ "PRE",
680
+ "LI"
681
+ ]);
682
+ const blocks = [];
683
+ const seenBlocks = /* @__PURE__ */ new Set();
684
+ const commonAncestor = range.commonAncestorContainer;
685
+ const iter = document.createNodeIterator(commonAncestor.nodeType === Node.TEXT_NODE ? commonAncestor.parentNode : commonAncestor, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null);
686
+ let node;
687
+ while (node = iter.nextNode()) {
688
+ if (!range.intersectsNode(node)) continue;
689
+ let block = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
690
+ while (block && !BLOCK_TAGS_MULTI.has(block.tagName)) block = block.parentElement;
691
+ if (block && !seenBlocks.has(block)) {
692
+ seenBlocks.add(block);
693
+ blocks.push(block);
694
+ }
695
+ }
696
+ if (blocks.length === 0) return;
697
+ const newUl = document.createElement("ul");
698
+ newUl.className = "an-checklist";
699
+ let lastTextNode = null;
700
+ blocks.forEach((block) => {
701
+ const li = document.createElement("li");
702
+ const cb = document.createElement("input");
703
+ cb.type = "checkbox";
704
+ cb.setAttribute("contenteditable", "false");
705
+ li.appendChild(cb);
706
+ const blockText = Array.from(block.childNodes).map((n) => n.textContent).join("").replace(/[\u00a0\u200B]/g, " ").trim();
707
+ const tn = document.createTextNode(blockText || "​");
708
+ li.appendChild(tn);
709
+ newUl.appendChild(li);
710
+ lastTextNode = tn;
711
+ });
712
+ const firstBlock = blocks[0];
713
+ firstBlock.parentNode.insertBefore(newUl, firstBlock);
714
+ blocks.forEach((block) => block.parentNode && block.parentNode.removeChild(block));
715
+ if (lastTextNode) {
716
+ const nr = document.createRange();
717
+ nr.setStart(lastTextNode, lastTextNode.textContent.length);
718
+ nr.collapse(true);
719
+ sel.removeAllRanges();
720
+ sel.addRange(nr);
721
+ }
601
722
  }
602
723
  /**
603
724
  * Returns true when the cursor is inside a checklist item.
@@ -1040,9 +1161,2699 @@
1040
1161
  onDestroy: null,
1041
1162
  onCharLimitReached: null,
1042
1163
  onWordLimitReached: null,
1043
- focusColor: null
1164
+ focusColor: null,
1165
+ lang: "en"
1044
1166
  };
1045
1167
  //#endregion
1168
+ //#region src/js/i18n/en.js
1169
+ /**
1170
+ * en.js - English locale (canonical reference)
1171
+ * All other locales deep-merge against this to fill missing keys.
1172
+ */
1173
+ /** @type {import('../../../types/index.js').AsnLocale} */
1174
+ var en = {
1175
+ toolbar: {
1176
+ bold: "Bold (Ctrl+B)",
1177
+ italic: "Italic (Ctrl+I)",
1178
+ underline: "Underline (Ctrl+U)",
1179
+ strikethrough: "Strikethrough",
1180
+ superscript: "Superscript",
1181
+ subscript: "Subscript",
1182
+ alignLeft: "Align Left",
1183
+ alignCenter: "Align Center",
1184
+ alignRight: "Align Right",
1185
+ alignJustify: "Justify",
1186
+ ul: "Unordered List",
1187
+ ol: "Ordered List",
1188
+ checklist: "Checklist",
1189
+ indent: "Indent",
1190
+ outdent: "Outdent",
1191
+ undo: "Undo (Ctrl+Z)",
1192
+ redo: "Redo (Ctrl+Y)",
1193
+ hr: "Horizontal Rule",
1194
+ link: "Insert Link",
1195
+ image: "Insert Image",
1196
+ video: "Insert Video",
1197
+ emoji: "Insert Emoji",
1198
+ icon: "Insert FA Icon",
1199
+ table: "Insert Table",
1200
+ fontSize: "Font Size",
1201
+ fontSizePlaceholder: "Size",
1202
+ removeFormat: "Remove Format",
1203
+ direction: "Toggle Text Direction (LTR / RTL)",
1204
+ fontFamily: "Font Family",
1205
+ paragraphStyle: "Paragraph Style",
1206
+ paragraphStylePlaceholder: "Style",
1207
+ lineHeight: "Line Height",
1208
+ lineHeightPlaceholder: "↕ Line",
1209
+ codeview: "HTML Code View",
1210
+ fullscreen: "Fullscreen",
1211
+ shortcuts: "Keyboard Shortcuts (Ctrl+Shift+/)",
1212
+ find: "Find (Ctrl+F)",
1213
+ findReplace: "Find & Replace (Ctrl+H)",
1214
+ inlineCode: "Inline Code (Ctrl+`)",
1215
+ print: "Print",
1216
+ foreColor: "Text Color",
1217
+ backColor: "Highlight Color",
1218
+ chooseTextColor: "Choose text color",
1219
+ chooseHighlightColor: "Choose highlight color",
1220
+ customColor: "Custom color",
1221
+ insertTableLabel: "Insert Table",
1222
+ paragraphItems: {
1223
+ p: "Normal",
1224
+ blockquote: "Quote",
1225
+ pre: "Code"
1226
+ }
1227
+ },
1228
+ linkDialog: {
1229
+ ariaLabel: "Insert link",
1230
+ title: "Insert Link",
1231
+ url: "URL",
1232
+ urlPlaceholder: "https://",
1233
+ displayText: "Display Text",
1234
+ textPlaceholder: "Link text",
1235
+ openInNewTab: "Open in new tab",
1236
+ insertBtn: "Insert",
1237
+ cancelBtn: "Cancel"
1238
+ },
1239
+ imageDialog: {
1240
+ ariaLabel: "Insert image",
1241
+ title: "Insert Image",
1242
+ imageUrl: "Image URL",
1243
+ urlPlaceholder: "https://example.com/image.png",
1244
+ altText: "Alt Text",
1245
+ altPlaceholder: "Describe the image",
1246
+ alignment: "Alignment",
1247
+ alignNone: "None",
1248
+ alignLeft: "Left",
1249
+ alignCenter: "Center",
1250
+ alignRight: "Right",
1251
+ uploadLabel: "Or upload a file",
1252
+ insertBtn: "Insert",
1253
+ cancelBtn: "Cancel"
1254
+ },
1255
+ videoDialog: {
1256
+ ariaLabel: "Insert video",
1257
+ title: "Insert Video",
1258
+ videoUrl: "Video URL",
1259
+ urlPlaceholder: "YouTube, Vimeo, or direct .mp4 URL",
1260
+ widthLabel: "Width (px)",
1261
+ widthPlaceholder: "560",
1262
+ insertBtn: "Insert",
1263
+ cancelBtn: "Cancel",
1264
+ detected: (type) => `Detected: ${type}`,
1265
+ unknownFormat: "Unknown format — will try direct video embed",
1266
+ invalidUrl: "Invalid URL — please enter a valid video link."
1267
+ },
1268
+ emojiDialog: {
1269
+ ariaLabel: "Insert emoji",
1270
+ title: "Insert Emoji",
1271
+ searchPlaceholder: "Search emojis…",
1272
+ all: "All",
1273
+ cancelBtn: "Cancel",
1274
+ close: "Close",
1275
+ categories: {
1276
+ smileys: "Smileys",
1277
+ people: "People",
1278
+ animals: "Animals",
1279
+ food: "Food",
1280
+ travel: "Travel",
1281
+ objects: "Objects",
1282
+ symbols: "Symbols"
1283
+ }
1284
+ },
1285
+ iconDialog: {
1286
+ ariaLabel: "Insert FA icon",
1287
+ title: "Insert FA Icon",
1288
+ searchPlaceholder: "Search icons…",
1289
+ all: "All",
1290
+ style: "Style",
1291
+ size: "Size",
1292
+ color: "Color",
1293
+ useColor: " Use color",
1294
+ selectHint: "Select an icon",
1295
+ insertBtn: "Insert FA Icon",
1296
+ cancelBtn: "Cancel",
1297
+ close: "Close",
1298
+ categories: {
1299
+ popular: "Popular",
1300
+ interface: "Interface",
1301
+ navigation: "Navigation",
1302
+ media: "Media",
1303
+ communication: "Communication",
1304
+ files: "Files",
1305
+ people: "People",
1306
+ objects: "Objects"
1307
+ }
1308
+ },
1309
+ findReplace: {
1310
+ findTitle: "Find",
1311
+ findReplaceTitle: "Find & Replace",
1312
+ findPlaceholder: "Find…",
1313
+ searchAriaLabel: "Search text",
1314
+ caseSensitive: "\xA0Case sensitive",
1315
+ prevBtn: "← Prev",
1316
+ nextBtn: "Next →",
1317
+ replacePlaceholder: "Replace with…",
1318
+ replaceAriaLabel: "Replace with",
1319
+ replaceBtn: "Replace",
1320
+ replaceAllBtn: "Replace All",
1321
+ close: "×"
1322
+ },
1323
+ shortcutsDialog: {
1324
+ title: "Keyboard Shortcuts",
1325
+ ariaLabel: "Keyboard Shortcuts",
1326
+ close: "Close",
1327
+ shortcuts: [
1328
+ {
1329
+ category: "Text Formatting",
1330
+ items: [
1331
+ {
1332
+ keys: "Ctrl + B",
1333
+ action: "Bold"
1334
+ },
1335
+ {
1336
+ keys: "Ctrl + I",
1337
+ action: "Italic"
1338
+ },
1339
+ {
1340
+ keys: "Ctrl + U",
1341
+ action: "Underline"
1342
+ },
1343
+ {
1344
+ keys: "Ctrl + K",
1345
+ action: "Insert / edit link"
1346
+ }
1347
+ ]
1348
+ },
1349
+ {
1350
+ category: "History",
1351
+ items: [{
1352
+ keys: "Ctrl + Z",
1353
+ action: "Undo"
1354
+ }, {
1355
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
1356
+ action: "Redo"
1357
+ }]
1358
+ },
1359
+ {
1360
+ category: "Selection & Navigation",
1361
+ items: [
1362
+ {
1363
+ keys: "Ctrl + A",
1364
+ action: "Select all content"
1365
+ },
1366
+ {
1367
+ keys: "Tab",
1368
+ action: "Indent list item / insert spaces"
1369
+ },
1370
+ {
1371
+ keys: "Shift + Tab",
1372
+ action: "Outdent list item"
1373
+ }
1374
+ ]
1375
+ },
1376
+ {
1377
+ category: "Clipboard",
1378
+ items: [{
1379
+ keys: "Ctrl + Shift + V",
1380
+ action: "Paste as plain text"
1381
+ }]
1382
+ },
1383
+ {
1384
+ category: "Find & Replace",
1385
+ items: [{
1386
+ keys: "Ctrl + F",
1387
+ action: "Find in document"
1388
+ }, {
1389
+ keys: "Ctrl + H",
1390
+ action: "Find & Replace"
1391
+ }]
1392
+ },
1393
+ {
1394
+ category: "Editor",
1395
+ items: [{
1396
+ keys: "Ctrl + Shift + /",
1397
+ action: "Show this keyboard shortcuts dialog"
1398
+ }]
1399
+ }
1400
+ ]
1401
+ },
1402
+ contextMenu: {
1403
+ cut: "Cut",
1404
+ copy: "Copy",
1405
+ paste: "Paste",
1406
+ bold: "Bold",
1407
+ italic: "Italic",
1408
+ underline: "Underline",
1409
+ textColor: "Text Color",
1410
+ highlightColor: "Highlight Color",
1411
+ copyFormat: "Copy Format",
1412
+ pasteFormat: "Paste Format",
1413
+ removeFormat: "Remove Format",
1414
+ link: "Insert Link",
1415
+ image: "Insert Image",
1416
+ video: "Insert Video",
1417
+ table: "Insert Table",
1418
+ back: "Back",
1419
+ noHighlight: "No highlight",
1420
+ customColor: "Custom color",
1421
+ customColorLabel: "Custom…"
1422
+ },
1423
+ statusbar: {
1424
+ resizeHandle: "Resize editor",
1425
+ words: (n) => `Words: ${n}`,
1426
+ wordsLimit: (n, max) => `Words: ${n}/${max}`,
1427
+ chars: (n) => `Chars: ${n}`,
1428
+ charsLimit: (n, max) => `Chars: ${n}/${max}`
1429
+ },
1430
+ tooltips: {
1431
+ link: {
1432
+ ariaLabel: "Link actions",
1433
+ openLink: "Open link",
1434
+ copyUrl: "Copy URL",
1435
+ editLink: "Edit link",
1436
+ removeLink: "Remove link"
1437
+ },
1438
+ image: {
1439
+ ariaLabel: "Image actions",
1440
+ label: "Image",
1441
+ floatLeft: "Float Left",
1442
+ noFloat: "No Float",
1443
+ alignCenter: "Align Center",
1444
+ floatRight: "Float Right",
1445
+ originalSize: "Original Size",
1446
+ rotateLeft: "Rotate Left",
1447
+ rotateRight: "Rotate Right",
1448
+ cropImage: "Crop Image",
1449
+ addCaption: "Add / Edit Caption",
1450
+ deleteImage: "Delete Image"
1451
+ },
1452
+ code: {
1453
+ ariaLabel: "Code block actions",
1454
+ label: "Code",
1455
+ syntaxLanguage: "Syntax Language",
1456
+ syntaxAriaLabel: "Syntax language",
1457
+ copyCode: "Copy Code",
1458
+ toggleWordWrap: "Toggle Word Wrap",
1459
+ enableWordWrap: "Enable Word Wrap",
1460
+ disableWordWrap: "Disable Word Wrap",
1461
+ convertToParagraph: "Convert to Paragraph",
1462
+ deleteCodeBlock: "Delete Code Block"
1463
+ },
1464
+ table: {
1465
+ ariaLabel: "Table actions",
1466
+ label: "Table",
1467
+ selectCells: "Select Cells",
1468
+ addRowAbove: "Add Row Above",
1469
+ addRowBelow: "Add Row Below",
1470
+ deleteRow: "Delete Row",
1471
+ addColumnLeft: "Add Column Left",
1472
+ addColumnRight: "Add Column Right",
1473
+ deleteColumn: "Delete Column",
1474
+ mergeCells: "Merge Cells",
1475
+ unmergeCells: "Unmerge Cells",
1476
+ columnWidth: "Column Width",
1477
+ rowHeight: "Row Height",
1478
+ tableBorderWidth: "Table Border Width",
1479
+ deleteTable: "Delete Table",
1480
+ columnWidthPx: "Column Width (px)",
1481
+ rowHeightPx: "Row Height (px)",
1482
+ tableBorderWidthPx: "Table Border Width (px)",
1483
+ cancelBtn: "Cancel",
1484
+ applyBtn: "Apply"
1485
+ },
1486
+ video: {
1487
+ ariaLabel: "Video actions",
1488
+ label: "Video",
1489
+ floatLeft: "Float Left",
1490
+ noFloat: "No Float",
1491
+ alignCenter: "Align Center",
1492
+ floatRight: "Float Right",
1493
+ originalSize: "Original Size",
1494
+ previewVideo: "Preview Video",
1495
+ exitPreview: "Exit Preview",
1496
+ deleteVideo: "Delete Video"
1497
+ }
1498
+ },
1499
+ errors: {
1500
+ imageFormat: (type) => `Format "${type}" is not supported for display in web browsers. Please convert to JPEG, PNG, or WebP first.`,
1501
+ imageSize: (maxSize) => `Image file is too large. Maximum allowed size is ${maxSize} MB.`
1502
+ }
1503
+ };
1504
+ //#endregion
1505
+ //#region src/js/i18n/index.js
1506
+ /**
1507
+ * i18n/index.js — Locale registry and resolver for autumn-note-ce.
1508
+ *
1509
+ * Usage:
1510
+ * lang: 'en' → built-in English (default)
1511
+ * lang: 'vi' → built-in Vietnamese
1512
+ * lang: 'ja' → built-in Japanese
1513
+ * lang: 'zh' → built-in Simplified Chinese
1514
+ * lang: 'fr' → built-in French
1515
+ * lang: 'de' → built-in German
1516
+ * lang: 'es' → built-in Spanish
1517
+ * lang: 'ko' → built-in Korean
1518
+ * lang: { ... } → custom locale object, deep-merged over English
1519
+ */
1520
+ /**
1521
+ * All built-in locales keyed by their language code.
1522
+ * @type {Record<string, Partial<AsnLocale>>}
1523
+ */
1524
+ var locales = {
1525
+ en,
1526
+ vi: {
1527
+ toolbar: {
1528
+ bold: "Đậm (Ctrl+B)",
1529
+ italic: "Nghiêng (Ctrl+I)",
1530
+ underline: "Gạch chân (Ctrl+U)",
1531
+ strikethrough: "Gạch ngang",
1532
+ superscript: "Chỉ số trên",
1533
+ subscript: "Chỉ số dưới",
1534
+ alignLeft: "Căn trái",
1535
+ alignCenter: "Căn giữa",
1536
+ alignRight: "Căn phải",
1537
+ alignJustify: "Căn đều",
1538
+ ul: "Danh sách không thứ tự",
1539
+ ol: "Danh sách có thứ tự",
1540
+ checklist: "Danh sách kiểm tra",
1541
+ indent: "Tăng thụt đầu dòng",
1542
+ outdent: "Giảm thụt đầu dòng",
1543
+ undo: "Hoàn tác (Ctrl+Z)",
1544
+ redo: "Làm lại (Ctrl+Y)",
1545
+ hr: "Đường kẻ ngang",
1546
+ link: "Chèn liên kết",
1547
+ image: "Chèn hình ảnh",
1548
+ video: "Chèn video",
1549
+ emoji: "Chèn biểu tượng cảm xúc",
1550
+ icon: "Chèn biểu tượng FA",
1551
+ table: "Chèn bảng",
1552
+ fontSize: "Cỡ chữ",
1553
+ fontSizePlaceholder: "Cỡ",
1554
+ removeFormat: "Xóa định dạng",
1555
+ direction: "Chuyển hướng văn bản (LTR / RTL)",
1556
+ fontFamily: "Phông chữ",
1557
+ paragraphStyle: "Kiểu đoạn văn",
1558
+ paragraphStylePlaceholder: "Kiểu",
1559
+ lineHeight: "Khoảng cách dòng",
1560
+ lineHeightPlaceholder: "↕ Dòng",
1561
+ codeview: "Xem mã HTML",
1562
+ fullscreen: "Toàn màn hình",
1563
+ shortcuts: "Phím tắt (Ctrl+Shift+/)",
1564
+ find: "Tìm kiếm (Ctrl+F)",
1565
+ findReplace: "Tìm & Thay thế (Ctrl+H)",
1566
+ inlineCode: "Mã nội tuyến (Ctrl+`)",
1567
+ print: "In",
1568
+ foreColor: "Màu chữ",
1569
+ backColor: "Màu nền chữ",
1570
+ chooseTextColor: "Chọn màu chữ",
1571
+ chooseHighlightColor: "Chọn màu nền",
1572
+ customColor: "Màu tùy chỉnh",
1573
+ insertTableLabel: "Chèn bảng",
1574
+ paragraphItems: {
1575
+ p: "Bình thường",
1576
+ blockquote: "Trích dẫn",
1577
+ pre: "Mã"
1578
+ }
1579
+ },
1580
+ linkDialog: {
1581
+ ariaLabel: "Chèn liên kết",
1582
+ title: "Chèn liên kết",
1583
+ url: "Địa chỉ URL",
1584
+ urlPlaceholder: "https://",
1585
+ displayText: "Văn bản hiển thị",
1586
+ textPlaceholder: "Nội dung liên kết",
1587
+ openInNewTab: "Mở trong tab mới",
1588
+ insertBtn: "Chèn",
1589
+ cancelBtn: "Hủy"
1590
+ },
1591
+ imageDialog: {
1592
+ ariaLabel: "Chèn hình ảnh",
1593
+ title: "Chèn hình ảnh",
1594
+ imageUrl: "URL hình ảnh",
1595
+ urlPlaceholder: "https://example.com/anh.png",
1596
+ altText: "Văn bản thay thế",
1597
+ altPlaceholder: "Mô tả hình ảnh",
1598
+ alignment: "Căn chỉnh",
1599
+ alignNone: "Không",
1600
+ alignLeft: "Trái",
1601
+ alignCenter: "Giữa",
1602
+ alignRight: "Phải",
1603
+ uploadLabel: "Hoặc tải lên tệp",
1604
+ insertBtn: "Chèn",
1605
+ cancelBtn: "Hủy"
1606
+ },
1607
+ videoDialog: {
1608
+ ariaLabel: "Chèn video",
1609
+ title: "Chèn video",
1610
+ videoUrl: "URL video",
1611
+ urlPlaceholder: "YouTube, Vimeo, hoặc URL .mp4 trực tiếp",
1612
+ widthLabel: "Chiều rộng (px)",
1613
+ widthPlaceholder: "560",
1614
+ insertBtn: "Chèn",
1615
+ cancelBtn: "Hủy",
1616
+ detected: (type) => `Đã phát hiện: ${type}`,
1617
+ unknownFormat: "Định dạng không xác định — sẽ thử nhúng video trực tiếp",
1618
+ invalidUrl: "URL không hợp lệ — vui lòng nhập đường dẫn video hợp lệ."
1619
+ },
1620
+ emojiDialog: {
1621
+ ariaLabel: "Chèn biểu tượng cảm xúc",
1622
+ title: "Chèn biểu tượng cảm xúc",
1623
+ searchPlaceholder: "Tìm kiếm biểu tượng…",
1624
+ all: "Tất cả",
1625
+ cancelBtn: "Hủy",
1626
+ close: "Đóng",
1627
+ categories: {
1628
+ smileys: "Mặt cười",
1629
+ people: "Con người",
1630
+ animals: "Động vật",
1631
+ food: "Thức ăn",
1632
+ travel: "Du lịch",
1633
+ objects: "Đồ vật",
1634
+ symbols: "Ký hiệu"
1635
+ }
1636
+ },
1637
+ iconDialog: {
1638
+ ariaLabel: "Chèn biểu tượng FA",
1639
+ title: "Chèn biểu tượng FA",
1640
+ searchPlaceholder: "Tìm kiếm biểu tượng…",
1641
+ all: "Tất cả",
1642
+ style: "Kiểu",
1643
+ size: "Kích thước",
1644
+ color: "Màu sắc",
1645
+ useColor: " Dùng màu",
1646
+ selectHint: "Chọn một biểu tượng",
1647
+ insertBtn: "Chèn biểu tượng FA",
1648
+ cancelBtn: "Hủy",
1649
+ close: "Đóng",
1650
+ categories: {
1651
+ popular: "Phổ biến",
1652
+ interface: "Giao diện",
1653
+ navigation: "Điều hướng",
1654
+ media: "Phương tiện",
1655
+ communication: "Liên lạc",
1656
+ files: "Tệp tin",
1657
+ people: "Con người",
1658
+ objects: "Đồ vật"
1659
+ }
1660
+ },
1661
+ findReplace: {
1662
+ findTitle: "Tìm kiếm",
1663
+ findReplaceTitle: "Tìm & Thay thế",
1664
+ findPlaceholder: "Tìm…",
1665
+ searchAriaLabel: "Văn bản tìm kiếm",
1666
+ caseSensitive: "\xA0Phân biệt hoa thường",
1667
+ prevBtn: "← Trước",
1668
+ nextBtn: "Tiếp →",
1669
+ replacePlaceholder: "Thay thế bằng…",
1670
+ replaceAriaLabel: "Thay thế bằng",
1671
+ replaceBtn: "Thay thế",
1672
+ replaceAllBtn: "Thay thế tất cả",
1673
+ close: "×"
1674
+ },
1675
+ shortcutsDialog: {
1676
+ title: "Phím tắt bàn phím",
1677
+ ariaLabel: "Phím tắt bàn phím",
1678
+ close: "Đóng",
1679
+ shortcuts: [
1680
+ {
1681
+ category: "Định dạng văn bản",
1682
+ items: [
1683
+ {
1684
+ keys: "Ctrl + B",
1685
+ action: "Đậm"
1686
+ },
1687
+ {
1688
+ keys: "Ctrl + I",
1689
+ action: "Nghiêng"
1690
+ },
1691
+ {
1692
+ keys: "Ctrl + U",
1693
+ action: "Gạch chân"
1694
+ },
1695
+ {
1696
+ keys: "Ctrl + K",
1697
+ action: "Chèn / sửa liên kết"
1698
+ }
1699
+ ]
1700
+ },
1701
+ {
1702
+ category: "Lịch sử",
1703
+ items: [{
1704
+ keys: "Ctrl + Z",
1705
+ action: "Hoàn tác"
1706
+ }, {
1707
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
1708
+ action: "Làm lại"
1709
+ }]
1710
+ },
1711
+ {
1712
+ category: "Chọn & Điều hướng",
1713
+ items: [
1714
+ {
1715
+ keys: "Ctrl + A",
1716
+ action: "Chọn tất cả"
1717
+ },
1718
+ {
1719
+ keys: "Tab",
1720
+ action: "Tăng thụt đầu dòng / chèn khoảng trắng"
1721
+ },
1722
+ {
1723
+ keys: "Shift + Tab",
1724
+ action: "Giảm thụt đầu dòng"
1725
+ }
1726
+ ]
1727
+ },
1728
+ {
1729
+ category: "Bảng nhớ tạm",
1730
+ items: [{
1731
+ keys: "Ctrl + Shift + V",
1732
+ action: "Dán dưới dạng văn bản thuần"
1733
+ }]
1734
+ },
1735
+ {
1736
+ category: "Tìm & Thay thế",
1737
+ items: [{
1738
+ keys: "Ctrl + F",
1739
+ action: "Tìm trong tài liệu"
1740
+ }, {
1741
+ keys: "Ctrl + H",
1742
+ action: "Tìm & Thay thế"
1743
+ }]
1744
+ },
1745
+ {
1746
+ category: "Trình soạn thảo",
1747
+ items: [{
1748
+ keys: "Ctrl + Shift + /",
1749
+ action: "Hiện hộp thoại phím tắt"
1750
+ }]
1751
+ }
1752
+ ]
1753
+ },
1754
+ contextMenu: {
1755
+ cut: "Cắt",
1756
+ copy: "Sao chép",
1757
+ paste: "Dán",
1758
+ bold: "Đậm",
1759
+ italic: "Nghiêng",
1760
+ underline: "Gạch chân",
1761
+ textColor: "Màu chữ",
1762
+ highlightColor: "Màu nền chữ",
1763
+ copyFormat: "Sao chép định dạng",
1764
+ pasteFormat: "Dán định dạng",
1765
+ removeFormat: "Xóa định dạng",
1766
+ link: "Chèn liên kết",
1767
+ image: "Chèn hình ảnh",
1768
+ video: "Chèn video",
1769
+ table: "Chèn bảng",
1770
+ back: "Quay lại",
1771
+ noHighlight: "Không tô màu",
1772
+ customColor: "Màu tùy chỉnh",
1773
+ customColorLabel: "Tùy chỉnh…"
1774
+ },
1775
+ statusbar: {
1776
+ resizeHandle: "Kéo để thay đổi kích thước",
1777
+ words: (n) => `Từ: ${n}`,
1778
+ wordsLimit: (n, max) => `Từ: ${n}/${max}`,
1779
+ chars: (n) => `Ký tự: ${n}`,
1780
+ charsLimit: (n, max) => `Ký tự: ${n}/${max}`
1781
+ },
1782
+ tooltips: {
1783
+ link: {
1784
+ ariaLabel: "Hành động liên kết",
1785
+ openLink: "Mở liên kết",
1786
+ copyUrl: "Sao chép URL",
1787
+ editLink: "Sửa liên kết",
1788
+ removeLink: "Xóa liên kết"
1789
+ },
1790
+ image: {
1791
+ ariaLabel: "Hành động hình ảnh",
1792
+ label: "Hình ảnh",
1793
+ floatLeft: "Nổi trái",
1794
+ noFloat: "Không nổi",
1795
+ alignCenter: "Căn giữa",
1796
+ floatRight: "Nổi phải",
1797
+ originalSize: "Kích thước gốc",
1798
+ rotateLeft: "Xoay trái",
1799
+ rotateRight: "Xoay phải",
1800
+ cropImage: "Cắt ảnh",
1801
+ addCaption: "Thêm / Sửa chú thích",
1802
+ deleteImage: "Xóa hình ảnh"
1803
+ },
1804
+ code: {
1805
+ ariaLabel: "Hành động khối mã",
1806
+ label: "Mã",
1807
+ syntaxLanguage: "Ngôn ngữ cú pháp",
1808
+ syntaxAriaLabel: "Ngôn ngữ cú pháp",
1809
+ copyCode: "Sao chép mã",
1810
+ toggleWordWrap: "Chuyển đổi xuống dòng",
1811
+ enableWordWrap: "Bật xuống dòng",
1812
+ disableWordWrap: "Tắt xuống dòng",
1813
+ convertToParagraph: "Chuyển thành đoạn văn",
1814
+ deleteCodeBlock: "Xóa khối mã"
1815
+ },
1816
+ table: {
1817
+ ariaLabel: "Hành động bảng",
1818
+ label: "Bảng",
1819
+ selectCells: "Chọn ô",
1820
+ addRowAbove: "Thêm hàng phía trên",
1821
+ addRowBelow: "Thêm hàng phía dưới",
1822
+ deleteRow: "Xóa hàng",
1823
+ addColumnLeft: "Thêm cột bên trái",
1824
+ addColumnRight: "Thêm cột bên phải",
1825
+ deleteColumn: "Xóa cột",
1826
+ mergeCells: "Gộp ô",
1827
+ unmergeCells: "Tách ô",
1828
+ columnWidth: "Chiều rộng cột",
1829
+ rowHeight: "Chiều cao hàng",
1830
+ tableBorderWidth: "Độ rộng viền bảng",
1831
+ deleteTable: "Xóa bảng",
1832
+ columnWidthPx: "Chiều rộng cột (px)",
1833
+ rowHeightPx: "Chiều cao hàng (px)",
1834
+ tableBorderWidthPx: "Độ rộng viền bảng (px)",
1835
+ cancelBtn: "Hủy",
1836
+ applyBtn: "Áp dụng"
1837
+ },
1838
+ video: {
1839
+ ariaLabel: "Hành động video",
1840
+ label: "Video",
1841
+ floatLeft: "Nổi trái",
1842
+ noFloat: "Không nổi",
1843
+ alignCenter: "Căn giữa",
1844
+ floatRight: "Nổi phải",
1845
+ originalSize: "Kích thước gốc",
1846
+ previewVideo: "Xem trước",
1847
+ exitPreview: "Thoát xem trước",
1848
+ deleteVideo: "Xóa video"
1849
+ }
1850
+ },
1851
+ errors: {
1852
+ imageFormat: (type) => `Định dạng "${type}" không được hỗ trợ hiển thị trên trình duyệt. Vui lòng chuyển đổi sang JPEG, PNG hoặc WebP.`,
1853
+ imageSize: (maxSize) => `Tệp hình ảnh quá lớn. Kích thước tối đa cho phép là ${maxSize} MB.`
1854
+ }
1855
+ },
1856
+ ja: {
1857
+ toolbar: {
1858
+ bold: "太字 (Ctrl+B)",
1859
+ italic: "斜体 (Ctrl+I)",
1860
+ underline: "下線 (Ctrl+U)",
1861
+ strikethrough: "取り消し線",
1862
+ superscript: "上付き文字",
1863
+ subscript: "下付き文字",
1864
+ alignLeft: "左揃え",
1865
+ alignCenter: "中央揃え",
1866
+ alignRight: "右揃え",
1867
+ alignJustify: "両端揃え",
1868
+ ul: "箇条書きリスト",
1869
+ ol: "番号付きリスト",
1870
+ checklist: "チェックリスト",
1871
+ indent: "インデントを増やす",
1872
+ outdent: "インデントを減らす",
1873
+ undo: "元に戻す (Ctrl+Z)",
1874
+ redo: "やり直す (Ctrl+Y)",
1875
+ hr: "水平線",
1876
+ link: "リンクを挿入",
1877
+ image: "画像を挿入",
1878
+ video: "動画を挿入",
1879
+ emoji: "絵文字を挿入",
1880
+ icon: "FAアイコンを挿入",
1881
+ table: "テーブルを挿入",
1882
+ fontSize: "フォントサイズ",
1883
+ fontSizePlaceholder: "サイズ",
1884
+ removeFormat: "書式をリセット",
1885
+ direction: "文字方向を切り替え (LTR / RTL)",
1886
+ fontFamily: "フォント",
1887
+ paragraphStyle: "段落スタイル",
1888
+ paragraphStylePlaceholder: "スタイル",
1889
+ lineHeight: "行の高さ",
1890
+ lineHeightPlaceholder: "↕ 行間",
1891
+ codeview: "HTMLソースを表示",
1892
+ fullscreen: "全画面表示",
1893
+ shortcuts: "キーボードショートカット (Ctrl+Shift+/)",
1894
+ find: "検索 (Ctrl+F)",
1895
+ findReplace: "検索と置換 (Ctrl+H)",
1896
+ inlineCode: "インラインコード (Ctrl+`)",
1897
+ print: "印刷",
1898
+ foreColor: "文字色",
1899
+ backColor: "ハイライト色",
1900
+ chooseTextColor: "文字色を選択",
1901
+ chooseHighlightColor: "ハイライト色を選択",
1902
+ customColor: "カスタムカラー",
1903
+ insertTableLabel: "テーブルを挿入",
1904
+ paragraphItems: {
1905
+ p: "標準",
1906
+ blockquote: "引用",
1907
+ pre: "コード"
1908
+ }
1909
+ },
1910
+ linkDialog: {
1911
+ ariaLabel: "リンクを挿入",
1912
+ title: "リンクを挿入",
1913
+ url: "URL",
1914
+ urlPlaceholder: "https://",
1915
+ displayText: "表示テキスト",
1916
+ textPlaceholder: "リンクテキスト",
1917
+ openInNewTab: "新しいタブで開く",
1918
+ insertBtn: "挿入",
1919
+ cancelBtn: "キャンセル"
1920
+ },
1921
+ imageDialog: {
1922
+ ariaLabel: "画像を挿入",
1923
+ title: "画像を挿入",
1924
+ imageUrl: "画像URL",
1925
+ urlPlaceholder: "https://example.com/image.png",
1926
+ altText: "代替テキスト",
1927
+ altPlaceholder: "画像の説明",
1928
+ alignment: "配置",
1929
+ alignNone: "なし",
1930
+ alignLeft: "左",
1931
+ alignCenter: "中央",
1932
+ alignRight: "右",
1933
+ uploadLabel: "ファイルをアップロード",
1934
+ insertBtn: "挿入",
1935
+ cancelBtn: "キャンセル"
1936
+ },
1937
+ videoDialog: {
1938
+ ariaLabel: "動画を挿入",
1939
+ title: "動画を挿入",
1940
+ videoUrl: "動画URL",
1941
+ urlPlaceholder: "YouTube、Vimeo、または直接動画URL",
1942
+ widthLabel: "幅 (px)",
1943
+ widthPlaceholder: "560",
1944
+ insertBtn: "挿入",
1945
+ cancelBtn: "キャンセル",
1946
+ detected: (type) => `検出: ${type}`,
1947
+ unknownFormat: "不明な形式 — 直接動画埋め込みを試みます",
1948
+ invalidUrl: "URLが無効です — 有効な動画URLを入力してください。"
1949
+ },
1950
+ emojiDialog: {
1951
+ ariaLabel: "絵文字を挿入",
1952
+ title: "絵文字を挿入",
1953
+ searchPlaceholder: "絵文字を検索…",
1954
+ all: "すべて",
1955
+ cancelBtn: "キャンセル",
1956
+ close: "閉じる",
1957
+ categories: {
1958
+ smileys: "顔文字",
1959
+ people: "人物",
1960
+ animals: "動物",
1961
+ food: "食べ物",
1962
+ travel: "旅行",
1963
+ objects: "モノ",
1964
+ symbols: "記号"
1965
+ }
1966
+ },
1967
+ iconDialog: {
1968
+ ariaLabel: "FAアイコンを挿入",
1969
+ title: "FAアイコンを挿入",
1970
+ searchPlaceholder: "アイコンを検索…",
1971
+ all: "すべて",
1972
+ style: "スタイル",
1973
+ size: "サイズ",
1974
+ color: "カラー",
1975
+ useColor: " カラーを使用",
1976
+ selectHint: "アイコンを選択してください",
1977
+ insertBtn: "FAアイコンを挿入",
1978
+ cancelBtn: "キャンセル",
1979
+ close: "閉じる",
1980
+ categories: {
1981
+ popular: "人気",
1982
+ interface: "インターフェース",
1983
+ navigation: "ナビゲーション",
1984
+ media: "メディア",
1985
+ communication: "通信",
1986
+ files: "ファイル",
1987
+ people: "人物",
1988
+ objects: "モノ"
1989
+ }
1990
+ },
1991
+ findReplace: {
1992
+ findTitle: "検索",
1993
+ findReplaceTitle: "検索と置換",
1994
+ findPlaceholder: "検索…",
1995
+ searchAriaLabel: "検索テキスト",
1996
+ caseSensitive: "\xA0大文字/小文字を区別",
1997
+ prevBtn: "← 前へ",
1998
+ nextBtn: "次へ →",
1999
+ replacePlaceholder: "置換後…",
2000
+ replaceAriaLabel: "置換後のテキスト",
2001
+ replaceBtn: "置換",
2002
+ replaceAllBtn: "すべて置換",
2003
+ close: "×"
2004
+ },
2005
+ shortcutsDialog: {
2006
+ title: "キーボードショートカット",
2007
+ ariaLabel: "キーボードショートカット",
2008
+ close: "閉じる",
2009
+ shortcuts: [
2010
+ {
2011
+ category: "テキスト書式",
2012
+ items: [
2013
+ {
2014
+ keys: "Ctrl + B",
2015
+ action: "太字"
2016
+ },
2017
+ {
2018
+ keys: "Ctrl + I",
2019
+ action: "斜体"
2020
+ },
2021
+ {
2022
+ keys: "Ctrl + U",
2023
+ action: "下線"
2024
+ },
2025
+ {
2026
+ keys: "Ctrl + K",
2027
+ action: "リンクの挿入 / 編集"
2028
+ }
2029
+ ]
2030
+ },
2031
+ {
2032
+ category: "履歴",
2033
+ items: [{
2034
+ keys: "Ctrl + Z",
2035
+ action: "元に戻す"
2036
+ }, {
2037
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
2038
+ action: "やり直す"
2039
+ }]
2040
+ },
2041
+ {
2042
+ category: "選択と移動",
2043
+ items: [
2044
+ {
2045
+ keys: "Ctrl + A",
2046
+ action: "すべて選択"
2047
+ },
2048
+ {
2049
+ keys: "Tab",
2050
+ action: "インデント増加 / スペース挿入"
2051
+ },
2052
+ {
2053
+ keys: "Shift + Tab",
2054
+ action: "インデント減少"
2055
+ }
2056
+ ]
2057
+ },
2058
+ {
2059
+ category: "クリップボード",
2060
+ items: [{
2061
+ keys: "Ctrl + Shift + V",
2062
+ action: "プレーンテキストとして貼り付け"
2063
+ }]
2064
+ },
2065
+ {
2066
+ category: "検索と置換",
2067
+ items: [{
2068
+ keys: "Ctrl + F",
2069
+ action: "文書内を検索"
2070
+ }, {
2071
+ keys: "Ctrl + H",
2072
+ action: "検索と置換"
2073
+ }]
2074
+ },
2075
+ {
2076
+ category: "エディター",
2077
+ items: [{
2078
+ keys: "Ctrl + Shift + /",
2079
+ action: "ショートカットダイアログを表示"
2080
+ }]
2081
+ }
2082
+ ]
2083
+ },
2084
+ contextMenu: {
2085
+ cut: "切り取り",
2086
+ copy: "コピー",
2087
+ paste: "貼り付け",
2088
+ bold: "太字",
2089
+ italic: "斜体",
2090
+ underline: "下線",
2091
+ textColor: "文字色",
2092
+ highlightColor: "ハイライト色",
2093
+ copyFormat: "書式をコピー",
2094
+ pasteFormat: "書式を貼り付け",
2095
+ removeFormat: "書式をリセット",
2096
+ link: "リンクを挿入",
2097
+ image: "画像を挿入",
2098
+ video: "動画を挿入",
2099
+ table: "テーブルを挿入",
2100
+ back: "戻る",
2101
+ noHighlight: "ハイライトなし",
2102
+ customColor: "カスタムカラー",
2103
+ customColorLabel: "カスタム…"
2104
+ },
2105
+ statusbar: {
2106
+ resizeHandle: "ドラッグしてリサイズ",
2107
+ words: (n) => `単語数: ${n}`,
2108
+ wordsLimit: (n, max) => `単語数: ${n}/${max}`,
2109
+ chars: (n) => `文字数: ${n}`,
2110
+ charsLimit: (n, max) => `文字数: ${n}/${max}`
2111
+ },
2112
+ tooltips: {
2113
+ link: {
2114
+ ariaLabel: "リンク操作",
2115
+ openLink: "リンクを開く",
2116
+ copyUrl: "URLをコピー",
2117
+ editLink: "リンクを編集",
2118
+ removeLink: "リンクを削除"
2119
+ },
2120
+ image: {
2121
+ ariaLabel: "画像操作",
2122
+ label: "画像",
2123
+ floatLeft: "左に回り込み",
2124
+ noFloat: "回り込みなし",
2125
+ alignCenter: "中央揃え",
2126
+ floatRight: "右に回り込み",
2127
+ originalSize: "元のサイズ",
2128
+ rotateLeft: "左に回転",
2129
+ rotateRight: "右に回転",
2130
+ cropImage: "画像をトリミング",
2131
+ addCaption: "キャプションを追加 / 編集",
2132
+ deleteImage: "画像を削除"
2133
+ },
2134
+ code: {
2135
+ ariaLabel: "コードブロック操作",
2136
+ label: "コード",
2137
+ syntaxLanguage: "言語",
2138
+ syntaxAriaLabel: "構文言語",
2139
+ copyCode: "コードをコピー",
2140
+ toggleWordWrap: "折り返しを切り替え",
2141
+ enableWordWrap: "折り返しを有効にする",
2142
+ disableWordWrap: "折り返しを無効にする",
2143
+ convertToParagraph: "段落に変換",
2144
+ deleteCodeBlock: "コードブロックを削除"
2145
+ },
2146
+ table: {
2147
+ ariaLabel: "テーブル操作",
2148
+ label: "テーブル",
2149
+ selectCells: "セルを選択",
2150
+ addRowAbove: "上に行を追加",
2151
+ addRowBelow: "下に行を追加",
2152
+ deleteRow: "行を削除",
2153
+ addColumnLeft: "左に列を追加",
2154
+ addColumnRight: "右に列を追加",
2155
+ deleteColumn: "列を削除",
2156
+ mergeCells: "セルを結合",
2157
+ unmergeCells: "セルの結合を解除",
2158
+ columnWidth: "列幅",
2159
+ rowHeight: "行の高さ",
2160
+ tableBorderWidth: "テーブルの枠幅",
2161
+ deleteTable: "テーブルを削除",
2162
+ columnWidthPx: "列幅 (px)",
2163
+ rowHeightPx: "行の高さ (px)",
2164
+ tableBorderWidthPx: "テーブルの枠幅 (px)",
2165
+ cancelBtn: "キャンセル",
2166
+ applyBtn: "適用"
2167
+ },
2168
+ video: {
2169
+ ariaLabel: "動画操作",
2170
+ label: "動画",
2171
+ floatLeft: "左に回り込み",
2172
+ noFloat: "回り込みなし",
2173
+ alignCenter: "中央揃え",
2174
+ floatRight: "右に回り込み",
2175
+ originalSize: "元のサイズ",
2176
+ previewVideo: "プレビュー",
2177
+ exitPreview: "プレビューを終了",
2178
+ deleteVideo: "動画を削除"
2179
+ }
2180
+ },
2181
+ errors: {
2182
+ imageFormat: (type) => `形式 "${type}" はブラウザでの表示をサポートしていません。JPEG、PNG、または WebP に変換してください。`,
2183
+ imageSize: (maxSize) => `画像ファイルが大きすぎます。最大許容サイズは ${maxSize} MB です。`
2184
+ }
2185
+ },
2186
+ zh: {
2187
+ toolbar: {
2188
+ bold: "粗体 (Ctrl+B)",
2189
+ italic: "斜体 (Ctrl+I)",
2190
+ underline: "下划线 (Ctrl+U)",
2191
+ strikethrough: "删除线",
2192
+ superscript: "上标",
2193
+ subscript: "下标",
2194
+ alignLeft: "左对齐",
2195
+ alignCenter: "居中",
2196
+ alignRight: "右对齐",
2197
+ alignJustify: "两端对齐",
2198
+ ul: "无序列表",
2199
+ ol: "有序列表",
2200
+ checklist: "待办列表",
2201
+ indent: "增加缩进",
2202
+ outdent: "减少缩进",
2203
+ undo: "撤销 (Ctrl+Z)",
2204
+ redo: "重做 (Ctrl+Y)",
2205
+ hr: "水平分割线",
2206
+ link: "插入链接",
2207
+ image: "插入图片",
2208
+ video: "插入视频",
2209
+ emoji: "插入表情",
2210
+ icon: "插入 FA 图标",
2211
+ table: "插入表格",
2212
+ fontSize: "字号",
2213
+ fontSizePlaceholder: "大小",
2214
+ removeFormat: "清除格式",
2215
+ direction: "切换文字方向 (LTR / RTL)",
2216
+ fontFamily: "字体",
2217
+ paragraphStyle: "段落样式",
2218
+ paragraphStylePlaceholder: "样式",
2219
+ lineHeight: "行高",
2220
+ lineHeightPlaceholder: "↕ 行距",
2221
+ codeview: "查看 HTML 源码",
2222
+ fullscreen: "全屏",
2223
+ shortcuts: "键盘快捷键 (Ctrl+Shift+/)",
2224
+ find: "搜索 (Ctrl+F)",
2225
+ findReplace: "查找和替换 (Ctrl+H)",
2226
+ inlineCode: "行内代码 (Ctrl+`)",
2227
+ print: "打印",
2228
+ foreColor: "文字颜色",
2229
+ backColor: "高亮颜色",
2230
+ chooseTextColor: "选择文字颜色",
2231
+ chooseHighlightColor: "选择高亮颜色",
2232
+ customColor: "自定义颜色",
2233
+ insertTableLabel: "插入表格",
2234
+ paragraphItems: {
2235
+ p: "正文",
2236
+ blockquote: "引用",
2237
+ pre: "代码"
2238
+ }
2239
+ },
2240
+ linkDialog: {
2241
+ ariaLabel: "插入链接",
2242
+ title: "插入链接",
2243
+ url: "链接地址",
2244
+ urlPlaceholder: "https://",
2245
+ displayText: "显示文字",
2246
+ textPlaceholder: "链接文字",
2247
+ openInNewTab: "在新标签页中打开",
2248
+ insertBtn: "插入",
2249
+ cancelBtn: "取消"
2250
+ },
2251
+ imageDialog: {
2252
+ ariaLabel: "插入图片",
2253
+ title: "插入图片",
2254
+ imageUrl: "图片地址",
2255
+ urlPlaceholder: "https://example.com/image.png",
2256
+ altText: "替代文字",
2257
+ altPlaceholder: "图片描述",
2258
+ alignment: "对齐方式",
2259
+ alignNone: "无",
2260
+ alignLeft: "左",
2261
+ alignCenter: "居中",
2262
+ alignRight: "右",
2263
+ uploadLabel: "或上传文件",
2264
+ insertBtn: "插入",
2265
+ cancelBtn: "取消"
2266
+ },
2267
+ videoDialog: {
2268
+ ariaLabel: "插入视频",
2269
+ title: "插入视频",
2270
+ videoUrl: "视频地址",
2271
+ urlPlaceholder: "YouTube、Vimeo 或直接 .mp4 链接",
2272
+ widthLabel: "宽度 (px)",
2273
+ widthPlaceholder: "560",
2274
+ insertBtn: "插入",
2275
+ cancelBtn: "取消",
2276
+ detected: (type) => `已识别: ${type}`,
2277
+ unknownFormat: "未知格式 — 将尝试直接嵌入视频",
2278
+ invalidUrl: "URL 无效 — 请输入有效的视频链接。"
2279
+ },
2280
+ emojiDialog: {
2281
+ ariaLabel: "插入表情",
2282
+ title: "插入表情",
2283
+ searchPlaceholder: "搜索表情…",
2284
+ all: "全部",
2285
+ cancelBtn: "取消",
2286
+ close: "关闭",
2287
+ categories: {
2288
+ smileys: "笑脸",
2289
+ people: "人物",
2290
+ animals: "动物",
2291
+ food: "食物",
2292
+ travel: "旅行",
2293
+ objects: "物品",
2294
+ symbols: "符号"
2295
+ }
2296
+ },
2297
+ iconDialog: {
2298
+ ariaLabel: "插入 FA 图标",
2299
+ title: "插入 FA 图标",
2300
+ searchPlaceholder: "搜索图标…",
2301
+ all: "全部",
2302
+ style: "样式",
2303
+ size: "大小",
2304
+ color: "颜色",
2305
+ useColor: " 使用颜色",
2306
+ selectHint: "请选择一个图标",
2307
+ insertBtn: "插入 FA 图标",
2308
+ cancelBtn: "取消",
2309
+ close: "关闭",
2310
+ categories: {
2311
+ popular: "热门",
2312
+ interface: "界面",
2313
+ navigation: "导航",
2314
+ media: "媒体",
2315
+ communication: "通讯",
2316
+ files: "文件",
2317
+ people: "人物",
2318
+ objects: "物品"
2319
+ }
2320
+ },
2321
+ findReplace: {
2322
+ findTitle: "搜索",
2323
+ findReplaceTitle: "查找和替换",
2324
+ findPlaceholder: "查找…",
2325
+ searchAriaLabel: "搜索文字",
2326
+ caseSensitive: "\xA0区分大小写",
2327
+ prevBtn: "← 上一个",
2328
+ nextBtn: "下一个 →",
2329
+ replacePlaceholder: "替换为…",
2330
+ replaceAriaLabel: "替换为",
2331
+ replaceBtn: "替换",
2332
+ replaceAllBtn: "全部替换",
2333
+ close: "×"
2334
+ },
2335
+ shortcutsDialog: {
2336
+ title: "键盘快捷键",
2337
+ ariaLabel: "键盘快捷键",
2338
+ close: "关闭",
2339
+ shortcuts: [
2340
+ {
2341
+ category: "文字格式",
2342
+ items: [
2343
+ {
2344
+ keys: "Ctrl + B",
2345
+ action: "粗体"
2346
+ },
2347
+ {
2348
+ keys: "Ctrl + I",
2349
+ action: "斜体"
2350
+ },
2351
+ {
2352
+ keys: "Ctrl + U",
2353
+ action: "下划线"
2354
+ },
2355
+ {
2356
+ keys: "Ctrl + K",
2357
+ action: "插入 / 编辑链接"
2358
+ }
2359
+ ]
2360
+ },
2361
+ {
2362
+ category: "历史记录",
2363
+ items: [{
2364
+ keys: "Ctrl + Z",
2365
+ action: "撤销"
2366
+ }, {
2367
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
2368
+ action: "重做"
2369
+ }]
2370
+ },
2371
+ {
2372
+ category: "选择与导航",
2373
+ items: [
2374
+ {
2375
+ keys: "Ctrl + A",
2376
+ action: "全选"
2377
+ },
2378
+ {
2379
+ keys: "Tab",
2380
+ action: "增加缩进 / 插入空格"
2381
+ },
2382
+ {
2383
+ keys: "Shift + Tab",
2384
+ action: "减少缩进"
2385
+ }
2386
+ ]
2387
+ },
2388
+ {
2389
+ category: "剪贴板",
2390
+ items: [{
2391
+ keys: "Ctrl + Shift + V",
2392
+ action: "粘贴为纯文本"
2393
+ }]
2394
+ },
2395
+ {
2396
+ category: "查找和替换",
2397
+ items: [{
2398
+ keys: "Ctrl + F",
2399
+ action: "在文档中搜索"
2400
+ }, {
2401
+ keys: "Ctrl + H",
2402
+ action: "查找和替换"
2403
+ }]
2404
+ },
2405
+ {
2406
+ category: "编辑器",
2407
+ items: [{
2408
+ keys: "Ctrl + Shift + /",
2409
+ action: "显示快捷键对话框"
2410
+ }]
2411
+ }
2412
+ ]
2413
+ },
2414
+ contextMenu: {
2415
+ cut: "剪切",
2416
+ copy: "复制",
2417
+ paste: "粘贴",
2418
+ bold: "粗体",
2419
+ italic: "斜体",
2420
+ underline: "下划线",
2421
+ textColor: "文字颜色",
2422
+ highlightColor: "高亮颜色",
2423
+ copyFormat: "复制格式",
2424
+ pasteFormat: "粘贴格式",
2425
+ removeFormat: "清除格式",
2426
+ link: "插入链接",
2427
+ image: "插入图片",
2428
+ video: "插入视频",
2429
+ table: "插入表格",
2430
+ back: "返回",
2431
+ noHighlight: "无高亮",
2432
+ customColor: "自定义颜色",
2433
+ customColorLabel: "自定义…"
2434
+ },
2435
+ statusbar: {
2436
+ resizeHandle: "拖动以调整大小",
2437
+ words: (n) => `字数: ${n}`,
2438
+ wordsLimit: (n, max) => `字数: ${n}/${max}`,
2439
+ chars: (n) => `字符数: ${n}`,
2440
+ charsLimit: (n, max) => `字符数: ${n}/${max}`
2441
+ },
2442
+ tooltips: {
2443
+ link: {
2444
+ ariaLabel: "链接操作",
2445
+ openLink: "打开链接",
2446
+ copyUrl: "复制 URL",
2447
+ editLink: "编辑链接",
2448
+ removeLink: "删除链接"
2449
+ },
2450
+ image: {
2451
+ ariaLabel: "图片操作",
2452
+ label: "图片",
2453
+ floatLeft: "左浮动",
2454
+ noFloat: "不浮动",
2455
+ alignCenter: "居中对齐",
2456
+ floatRight: "右浮动",
2457
+ originalSize: "原始尺寸",
2458
+ rotateLeft: "向左旋转",
2459
+ rotateRight: "向右旋转",
2460
+ cropImage: "裁剪图片",
2461
+ addCaption: "添加 / 编辑说明",
2462
+ deleteImage: "删除图片"
2463
+ },
2464
+ code: {
2465
+ ariaLabel: "代码块操作",
2466
+ label: "代码",
2467
+ syntaxLanguage: "语法语言",
2468
+ syntaxAriaLabel: "语法语言",
2469
+ copyCode: "复制代码",
2470
+ toggleWordWrap: "切换自动换行",
2471
+ enableWordWrap: "启用自动换行",
2472
+ disableWordWrap: "禁用自动换行",
2473
+ convertToParagraph: "转换为段落",
2474
+ deleteCodeBlock: "删除代码块"
2475
+ },
2476
+ table: {
2477
+ ariaLabel: "表格操作",
2478
+ label: "表格",
2479
+ selectCells: "选择单元格",
2480
+ addRowAbove: "在上方插入行",
2481
+ addRowBelow: "在下方插入行",
2482
+ deleteRow: "删除行",
2483
+ addColumnLeft: "在左侧插入列",
2484
+ addColumnRight: "在右侧插入列",
2485
+ deleteColumn: "删除列",
2486
+ mergeCells: "合并单元格",
2487
+ unmergeCells: "拆分单元格",
2488
+ columnWidth: "列宽",
2489
+ rowHeight: "行高",
2490
+ tableBorderWidth: "表格边框宽度",
2491
+ deleteTable: "删除表格",
2492
+ columnWidthPx: "列宽 (px)",
2493
+ rowHeightPx: "行高 (px)",
2494
+ tableBorderWidthPx: "表格边框宽度 (px)",
2495
+ cancelBtn: "取消",
2496
+ applyBtn: "应用"
2497
+ },
2498
+ video: {
2499
+ ariaLabel: "视频操作",
2500
+ label: "视频",
2501
+ floatLeft: "左浮动",
2502
+ noFloat: "不浮动",
2503
+ alignCenter: "居中对齐",
2504
+ floatRight: "右浮动",
2505
+ originalSize: "原始尺寸",
2506
+ previewVideo: "预览视频",
2507
+ exitPreview: "退出预览",
2508
+ deleteVideo: "删除视频"
2509
+ }
2510
+ },
2511
+ errors: {
2512
+ imageFormat: (type) => `格式 "${type}" 不支持在浏览器中显示。请转换为 JPEG、PNG 或 WebP。`,
2513
+ imageSize: (maxSize) => `图片文件过大。最大允许大小为 ${maxSize} MB。`
2514
+ }
2515
+ },
2516
+ fr: {
2517
+ toolbar: {
2518
+ bold: "Gras (Ctrl+B)",
2519
+ italic: "Italique (Ctrl+I)",
2520
+ underline: "Souligné (Ctrl+U)",
2521
+ strikethrough: "Barré",
2522
+ superscript: "Exposant",
2523
+ subscript: "Indice",
2524
+ alignLeft: "Aligner à gauche",
2525
+ alignCenter: "Centrer",
2526
+ alignRight: "Aligner à droite",
2527
+ alignJustify: "Justifier",
2528
+ ul: "Liste à puces",
2529
+ ol: "Liste numérotée",
2530
+ checklist: "Liste de tâches",
2531
+ indent: "Augmenter le retrait",
2532
+ outdent: "Diminuer le retrait",
2533
+ undo: "Annuler (Ctrl+Z)",
2534
+ redo: "Rétablir (Ctrl+Y)",
2535
+ hr: "Ligne horizontale",
2536
+ link: "Insérer un lien",
2537
+ image: "Insérer une image",
2538
+ video: "Insérer une vidéo",
2539
+ emoji: "Insérer un emoji",
2540
+ icon: "Insérer une icône FA",
2541
+ table: "Insérer un tableau",
2542
+ fontSize: "Taille de police",
2543
+ fontSizePlaceholder: "Taille",
2544
+ removeFormat: "Effacer la mise en forme",
2545
+ direction: "Basculer la direction du texte (LTR / RTL)",
2546
+ fontFamily: "Police",
2547
+ paragraphStyle: "Style de paragraphe",
2548
+ paragraphStylePlaceholder: "Style",
2549
+ lineHeight: "Interligne",
2550
+ lineHeightPlaceholder: "↕ Ligne",
2551
+ codeview: "Afficher le code HTML",
2552
+ fullscreen: "Plein écran",
2553
+ shortcuts: "Raccourcis clavier (Ctrl+Shift+/)",
2554
+ find: "Rechercher (Ctrl+F)",
2555
+ findReplace: "Rechercher et remplacer (Ctrl+H)",
2556
+ inlineCode: "Code inline (Ctrl+`)",
2557
+ print: "Imprimer",
2558
+ foreColor: "Couleur du texte",
2559
+ backColor: "Couleur de surbrillance",
2560
+ chooseTextColor: "Choisir la couleur du texte",
2561
+ chooseHighlightColor: "Choisir la couleur de surbrillance",
2562
+ customColor: "Couleur personnalisée",
2563
+ insertTableLabel: "Insérer un tableau",
2564
+ paragraphItems: {
2565
+ p: "Normal",
2566
+ blockquote: "Citation",
2567
+ pre: "Code"
2568
+ }
2569
+ },
2570
+ linkDialog: {
2571
+ ariaLabel: "Insérer un lien",
2572
+ title: "Insérer un lien",
2573
+ url: "URL",
2574
+ urlPlaceholder: "https://",
2575
+ displayText: "Texte affiché",
2576
+ textPlaceholder: "Texte du lien",
2577
+ openInNewTab: "Ouvrir dans un nouvel onglet",
2578
+ insertBtn: "Insérer",
2579
+ cancelBtn: "Annuler"
2580
+ },
2581
+ imageDialog: {
2582
+ ariaLabel: "Insérer une image",
2583
+ title: "Insérer une image",
2584
+ imageUrl: "URL de l'image",
2585
+ urlPlaceholder: "https://example.com/image.png",
2586
+ altText: "Texte alternatif",
2587
+ altPlaceholder: "Description de l'image",
2588
+ alignment: "Alignement",
2589
+ alignNone: "Aucun",
2590
+ alignLeft: "Gauche",
2591
+ alignCenter: "Centre",
2592
+ alignRight: "Droite",
2593
+ uploadLabel: "Ou téléverser un fichier",
2594
+ insertBtn: "Insérer",
2595
+ cancelBtn: "Annuler"
2596
+ },
2597
+ videoDialog: {
2598
+ ariaLabel: "Insérer une vidéo",
2599
+ title: "Insérer une vidéo",
2600
+ videoUrl: "URL de la vidéo",
2601
+ urlPlaceholder: "YouTube, Vimeo ou URL .mp4 directe",
2602
+ widthLabel: "Largeur (px)",
2603
+ widthPlaceholder: "560",
2604
+ insertBtn: "Insérer",
2605
+ cancelBtn: "Annuler",
2606
+ detected: (type) => `Détecté\u00a0: ${type}`,
2607
+ unknownFormat: "Format inconnu — tentative d'intégration directe",
2608
+ invalidUrl: "URL invalide — veuillez saisir un lien vidéo valide."
2609
+ },
2610
+ emojiDialog: {
2611
+ ariaLabel: "Insérer un emoji",
2612
+ title: "Insérer un emoji",
2613
+ searchPlaceholder: "Rechercher des emojis…",
2614
+ all: "Tout",
2615
+ cancelBtn: "Annuler",
2616
+ close: "Fermer",
2617
+ categories: {
2618
+ smileys: "Smileys",
2619
+ people: "Personnes",
2620
+ animals: "Animaux",
2621
+ food: "Nourriture",
2622
+ travel: "Voyage",
2623
+ objects: "Objets",
2624
+ symbols: "Symboles"
2625
+ }
2626
+ },
2627
+ iconDialog: {
2628
+ ariaLabel: "Insérer une icône FA",
2629
+ title: "Insérer une icône FA",
2630
+ searchPlaceholder: "Rechercher des icônes…",
2631
+ all: "Tout",
2632
+ style: "Style",
2633
+ size: "Taille",
2634
+ color: "Couleur",
2635
+ useColor: " Utiliser la couleur",
2636
+ selectHint: "Sélectionner une icône",
2637
+ insertBtn: "Insérer une icône FA",
2638
+ cancelBtn: "Annuler",
2639
+ close: "Fermer",
2640
+ categories: {
2641
+ popular: "Populaire",
2642
+ interface: "Interface",
2643
+ navigation: "Navigation",
2644
+ media: "Média",
2645
+ communication: "Communication",
2646
+ files: "Fichiers",
2647
+ people: "Personnes",
2648
+ objects: "Objets"
2649
+ }
2650
+ },
2651
+ findReplace: {
2652
+ findTitle: "Rechercher",
2653
+ findReplaceTitle: "Rechercher et remplacer",
2654
+ findPlaceholder: "Rechercher…",
2655
+ searchAriaLabel: "Texte à rechercher",
2656
+ caseSensitive: "\xA0Respecter la casse",
2657
+ prevBtn: "← Préc.",
2658
+ nextBtn: "Suiv. →",
2659
+ replacePlaceholder: "Remplacer par…",
2660
+ replaceAriaLabel: "Remplacer par",
2661
+ replaceBtn: "Remplacer",
2662
+ replaceAllBtn: "Tout remplacer",
2663
+ close: "×"
2664
+ },
2665
+ shortcutsDialog: {
2666
+ title: "Raccourcis clavier",
2667
+ ariaLabel: "Raccourcis clavier",
2668
+ close: "Fermer",
2669
+ shortcuts: [
2670
+ {
2671
+ category: "Mise en forme du texte",
2672
+ items: [
2673
+ {
2674
+ keys: "Ctrl + B",
2675
+ action: "Gras"
2676
+ },
2677
+ {
2678
+ keys: "Ctrl + I",
2679
+ action: "Italique"
2680
+ },
2681
+ {
2682
+ keys: "Ctrl + U",
2683
+ action: "Souligné"
2684
+ },
2685
+ {
2686
+ keys: "Ctrl + K",
2687
+ action: "Insérer / modifier un lien"
2688
+ }
2689
+ ]
2690
+ },
2691
+ {
2692
+ category: "Historique",
2693
+ items: [{
2694
+ keys: "Ctrl + Z",
2695
+ action: "Annuler"
2696
+ }, {
2697
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
2698
+ action: "Rétablir"
2699
+ }]
2700
+ },
2701
+ {
2702
+ category: "Sélection et navigation",
2703
+ items: [
2704
+ {
2705
+ keys: "Ctrl + A",
2706
+ action: "Tout sélectionner"
2707
+ },
2708
+ {
2709
+ keys: "Tab",
2710
+ action: "Augmenter le retrait / insérer des espaces"
2711
+ },
2712
+ {
2713
+ keys: "Shift + Tab",
2714
+ action: "Diminuer le retrait"
2715
+ }
2716
+ ]
2717
+ },
2718
+ {
2719
+ category: "Presse-papiers",
2720
+ items: [{
2721
+ keys: "Ctrl + Shift + V",
2722
+ action: "Coller en texte brut"
2723
+ }]
2724
+ },
2725
+ {
2726
+ category: "Rechercher et remplacer",
2727
+ items: [{
2728
+ keys: "Ctrl + F",
2729
+ action: "Rechercher dans le document"
2730
+ }, {
2731
+ keys: "Ctrl + H",
2732
+ action: "Rechercher et remplacer"
2733
+ }]
2734
+ },
2735
+ {
2736
+ category: "Éditeur",
2737
+ items: [{
2738
+ keys: "Ctrl + Shift + /",
2739
+ action: "Afficher les raccourcis clavier"
2740
+ }]
2741
+ }
2742
+ ]
2743
+ },
2744
+ contextMenu: {
2745
+ cut: "Couper",
2746
+ copy: "Copier",
2747
+ paste: "Coller",
2748
+ bold: "Gras",
2749
+ italic: "Italique",
2750
+ underline: "Souligné",
2751
+ textColor: "Couleur du texte",
2752
+ highlightColor: "Couleur de surbrillance",
2753
+ copyFormat: "Copier la mise en forme",
2754
+ pasteFormat: "Coller la mise en forme",
2755
+ removeFormat: "Effacer la mise en forme",
2756
+ link: "Insérer un lien",
2757
+ image: "Insérer une image",
2758
+ video: "Insérer une vidéo",
2759
+ table: "Insérer un tableau",
2760
+ back: "Retour",
2761
+ noHighlight: "Sans surbrillance",
2762
+ customColor: "Couleur personnalisée",
2763
+ customColorLabel: "Personnaliser…"
2764
+ },
2765
+ statusbar: {
2766
+ resizeHandle: "Faire glisser pour redimensionner",
2767
+ words: (n) => `Mots\u00a0: ${n}`,
2768
+ wordsLimit: (n, max) => `Mots\u00a0: ${n}/${max}`,
2769
+ chars: (n) => `Caractères\u00a0: ${n}`,
2770
+ charsLimit: (n, max) => `Caractères\u00a0: ${n}/${max}`
2771
+ },
2772
+ tooltips: {
2773
+ link: {
2774
+ ariaLabel: "Actions du lien",
2775
+ openLink: "Ouvrir le lien",
2776
+ copyUrl: "Copier l'URL",
2777
+ editLink: "Modifier le lien",
2778
+ removeLink: "Supprimer le lien"
2779
+ },
2780
+ image: {
2781
+ ariaLabel: "Actions de l'image",
2782
+ label: "Image",
2783
+ floatLeft: "Flottant à gauche",
2784
+ noFloat: "Sans flottant",
2785
+ alignCenter: "Centré",
2786
+ floatRight: "Flottant à droite",
2787
+ originalSize: "Taille originale",
2788
+ rotateLeft: "Rotation à gauche",
2789
+ rotateRight: "Rotation à droite",
2790
+ cropImage: "Recadrer l'image",
2791
+ addCaption: "Ajouter / modifier la légende",
2792
+ deleteImage: "Supprimer l'image"
2793
+ },
2794
+ code: {
2795
+ ariaLabel: "Actions du bloc de code",
2796
+ label: "Code",
2797
+ syntaxLanguage: "Langage de syntaxe",
2798
+ syntaxAriaLabel: "Langage de syntaxe",
2799
+ copyCode: "Copier le code",
2800
+ toggleWordWrap: "Activer/désactiver le retour à la ligne",
2801
+ enableWordWrap: "Activer le retour à la ligne",
2802
+ disableWordWrap: "Désactiver le retour à la ligne",
2803
+ convertToParagraph: "Convertir en paragraphe",
2804
+ deleteCodeBlock: "Supprimer le bloc de code"
2805
+ },
2806
+ table: {
2807
+ ariaLabel: "Actions du tableau",
2808
+ label: "Tableau",
2809
+ selectCells: "Sélectionner des cellules",
2810
+ addRowAbove: "Ajouter une ligne au-dessus",
2811
+ addRowBelow: "Ajouter une ligne en-dessous",
2812
+ deleteRow: "Supprimer la ligne",
2813
+ addColumnLeft: "Ajouter une colonne à gauche",
2814
+ addColumnRight: "Ajouter une colonne à droite",
2815
+ deleteColumn: "Supprimer la colonne",
2816
+ mergeCells: "Fusionner les cellules",
2817
+ unmergeCells: "Scinder les cellules",
2818
+ columnWidth: "Largeur de colonne",
2819
+ rowHeight: "Hauteur de ligne",
2820
+ tableBorderWidth: "Épaisseur des bordures",
2821
+ deleteTable: "Supprimer le tableau",
2822
+ columnWidthPx: "Largeur de colonne (px)",
2823
+ rowHeightPx: "Hauteur de ligne (px)",
2824
+ tableBorderWidthPx: "Épaisseur des bordures (px)",
2825
+ cancelBtn: "Annuler",
2826
+ applyBtn: "Appliquer"
2827
+ },
2828
+ video: {
2829
+ ariaLabel: "Actions de la vidéo",
2830
+ label: "Vidéo",
2831
+ floatLeft: "Flottant à gauche",
2832
+ noFloat: "Sans flottant",
2833
+ alignCenter: "Centré",
2834
+ floatRight: "Flottant à droite",
2835
+ originalSize: "Taille originale",
2836
+ previewVideo: "Aperçu",
2837
+ exitPreview: "Quitter l'aperçu",
2838
+ deleteVideo: "Supprimer la vidéo"
2839
+ }
2840
+ },
2841
+ errors: {
2842
+ imageFormat: (type) => `Le format "${type}" n'est pas pris en charge par le navigateur. Veuillez le convertir en JPEG, PNG ou WebP.`,
2843
+ imageSize: (maxSize) => `Le fichier image est trop volumineux. La taille maximale autorisée est de ${maxSize}\u00a0Mo.`
2844
+ }
2845
+ },
2846
+ de: {
2847
+ toolbar: {
2848
+ bold: "Fett (Ctrl+B)",
2849
+ italic: "Kursiv (Ctrl+I)",
2850
+ underline: "Unterstrichen (Ctrl+U)",
2851
+ strikethrough: "Durchgestrichen",
2852
+ superscript: "Hochgestellt",
2853
+ subscript: "Tiefgestellt",
2854
+ alignLeft: "Linksbündig",
2855
+ alignCenter: "Zentriert",
2856
+ alignRight: "Rechtsbündig",
2857
+ alignJustify: "Blocksatz",
2858
+ ul: "Ungeordnete Liste",
2859
+ ol: "Geordnete Liste",
2860
+ checklist: "Checkliste",
2861
+ indent: "Einzug vergrößern",
2862
+ outdent: "Einzug verkleinern",
2863
+ undo: "Rückgängig (Ctrl+Z)",
2864
+ redo: "Wiederholen (Ctrl+Y)",
2865
+ hr: "Horizontale Linie",
2866
+ link: "Link einfügen",
2867
+ image: "Bild einfügen",
2868
+ video: "Video einfügen",
2869
+ emoji: "Emoji einfügen",
2870
+ icon: "FA-Symbol einfügen",
2871
+ table: "Tabelle einfügen",
2872
+ fontSize: "Schriftgröße",
2873
+ fontSizePlaceholder: "Größe",
2874
+ removeFormat: "Formatierung entfernen",
2875
+ direction: "Textrichtung umschalten (LTR / RTL)",
2876
+ fontFamily: "Schriftart",
2877
+ paragraphStyle: "Absatzstil",
2878
+ paragraphStylePlaceholder: "Stil",
2879
+ lineHeight: "Zeilenhöhe",
2880
+ lineHeightPlaceholder: "↕ Zeile",
2881
+ codeview: "HTML-Code-Ansicht",
2882
+ fullscreen: "Vollbild",
2883
+ shortcuts: "Tastenkürzel (Ctrl+Shift+/)",
2884
+ find: "Suchen (Ctrl+F)",
2885
+ findReplace: "Suchen & Ersetzen (Ctrl+H)",
2886
+ inlineCode: "Inline-Code (Ctrl+`)",
2887
+ print: "Drucken",
2888
+ foreColor: "Textfarbe",
2889
+ backColor: "Hervorhebungsfarbe",
2890
+ chooseTextColor: "Textfarbe auswählen",
2891
+ chooseHighlightColor: "Hervorhebungsfarbe auswählen",
2892
+ customColor: "Benutzerdefinierte Farbe",
2893
+ insertTableLabel: "Tabelle einfügen",
2894
+ paragraphItems: {
2895
+ p: "Normal",
2896
+ blockquote: "Zitat",
2897
+ pre: "Code"
2898
+ }
2899
+ },
2900
+ linkDialog: {
2901
+ ariaLabel: "Link einfügen",
2902
+ title: "Link einfügen",
2903
+ url: "URL",
2904
+ urlPlaceholder: "https://",
2905
+ displayText: "Anzeigetext",
2906
+ textPlaceholder: "Linktext",
2907
+ openInNewTab: "In neuem Tab öffnen",
2908
+ insertBtn: "Einfügen",
2909
+ cancelBtn: "Abbrechen"
2910
+ },
2911
+ imageDialog: {
2912
+ ariaLabel: "Bild einfügen",
2913
+ title: "Bild einfügen",
2914
+ imageUrl: "Bild-URL",
2915
+ urlPlaceholder: "https://example.com/image.png",
2916
+ altText: "Alt-Text",
2917
+ altPlaceholder: "Bild beschreiben",
2918
+ alignment: "Ausrichtung",
2919
+ alignNone: "Keine",
2920
+ alignLeft: "Links",
2921
+ alignCenter: "Mitte",
2922
+ alignRight: "Rechts",
2923
+ uploadLabel: "Oder Datei hochladen",
2924
+ insertBtn: "Einfügen",
2925
+ cancelBtn: "Abbrechen"
2926
+ },
2927
+ videoDialog: {
2928
+ ariaLabel: "Video einfügen",
2929
+ title: "Video einfügen",
2930
+ videoUrl: "Video-URL",
2931
+ urlPlaceholder: "YouTube, Vimeo oder direkte .mp4-URL",
2932
+ widthLabel: "Breite (px)",
2933
+ widthPlaceholder: "560",
2934
+ insertBtn: "Einfügen",
2935
+ cancelBtn: "Abbrechen",
2936
+ detected: (type) => `Erkannt: ${type}`,
2937
+ unknownFormat: "Unbekanntes Format — direktes Video-Einbetten wird versucht",
2938
+ invalidUrl: "Ungültige URL — bitte geben Sie einen gültigen Videolink ein."
2939
+ },
2940
+ emojiDialog: {
2941
+ ariaLabel: "Emoji einfügen",
2942
+ title: "Emoji einfügen",
2943
+ searchPlaceholder: "Emojis suchen…",
2944
+ all: "Alle",
2945
+ cancelBtn: "Abbrechen",
2946
+ close: "Schließen",
2947
+ categories: {
2948
+ smileys: "Smileys",
2949
+ people: "Menschen",
2950
+ animals: "Tiere",
2951
+ food: "Essen",
2952
+ travel: "Reisen",
2953
+ objects: "Objekte",
2954
+ symbols: "Symbole"
2955
+ }
2956
+ },
2957
+ iconDialog: {
2958
+ ariaLabel: "FA-Symbol einfügen",
2959
+ title: "FA-Symbol einfügen",
2960
+ searchPlaceholder: "Symbole suchen…",
2961
+ all: "Alle",
2962
+ style: "Stil",
2963
+ size: "Größe",
2964
+ color: "Farbe",
2965
+ useColor: " Farbe verwenden",
2966
+ selectHint: "Symbol auswählen",
2967
+ insertBtn: "FA-Symbol einfügen",
2968
+ cancelBtn: "Abbrechen",
2969
+ close: "Schließen",
2970
+ categories: {
2971
+ popular: "Beliebt",
2972
+ interface: "Benutzeroberfläche",
2973
+ navigation: "Navigation",
2974
+ media: "Medien",
2975
+ communication: "Kommunikation",
2976
+ files: "Dateien",
2977
+ people: "Menschen",
2978
+ objects: "Objekte"
2979
+ }
2980
+ },
2981
+ findReplace: {
2982
+ findTitle: "Suchen",
2983
+ findReplaceTitle: "Suchen & Ersetzen",
2984
+ findPlaceholder: "Suchen…",
2985
+ searchAriaLabel: "Suchtext",
2986
+ caseSensitive: "\xA0Groß-/Kleinschreibung",
2987
+ prevBtn: "← Zurück",
2988
+ nextBtn: "Weiter →",
2989
+ replacePlaceholder: "Ersetzen durch…",
2990
+ replaceAriaLabel: "Ersetzen durch",
2991
+ replaceBtn: "Ersetzen",
2992
+ replaceAllBtn: "Alle ersetzen",
2993
+ close: "×"
2994
+ },
2995
+ shortcutsDialog: {
2996
+ title: "Tastenkürzel",
2997
+ ariaLabel: "Tastenkürzel",
2998
+ close: "Schließen",
2999
+ shortcuts: [
3000
+ {
3001
+ category: "Textformatierung",
3002
+ items: [
3003
+ {
3004
+ keys: "Ctrl + B",
3005
+ action: "Fett"
3006
+ },
3007
+ {
3008
+ keys: "Ctrl + I",
3009
+ action: "Kursiv"
3010
+ },
3011
+ {
3012
+ keys: "Ctrl + U",
3013
+ action: "Unterstrichen"
3014
+ },
3015
+ {
3016
+ keys: "Ctrl + K",
3017
+ action: "Link einfügen / bearbeiten"
3018
+ }
3019
+ ]
3020
+ },
3021
+ {
3022
+ category: "Verlauf",
3023
+ items: [{
3024
+ keys: "Ctrl + Z",
3025
+ action: "Rückgängig"
3026
+ }, {
3027
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
3028
+ action: "Wiederholen"
3029
+ }]
3030
+ },
3031
+ {
3032
+ category: "Auswahl & Navigation",
3033
+ items: [
3034
+ {
3035
+ keys: "Ctrl + A",
3036
+ action: "Alles auswählen"
3037
+ },
3038
+ {
3039
+ keys: "Tab",
3040
+ action: "Listenebene erhöhen / Leerzeichen einfügen"
3041
+ },
3042
+ {
3043
+ keys: "Shift + Tab",
3044
+ action: "Listenebene verringern"
3045
+ }
3046
+ ]
3047
+ },
3048
+ {
3049
+ category: "Zwischenablage",
3050
+ items: [{
3051
+ keys: "Ctrl + Shift + V",
3052
+ action: "Als einfachen Text einfügen"
3053
+ }]
3054
+ },
3055
+ {
3056
+ category: "Suchen & Ersetzen",
3057
+ items: [{
3058
+ keys: "Ctrl + F",
3059
+ action: "Im Dokument suchen"
3060
+ }, {
3061
+ keys: "Ctrl + H",
3062
+ action: "Suchen & Ersetzen"
3063
+ }]
3064
+ },
3065
+ {
3066
+ category: "Editor",
3067
+ items: [{
3068
+ keys: "Ctrl + Shift + /",
3069
+ action: "Diesen Tastenkürzel-Dialog anzeigen"
3070
+ }]
3071
+ }
3072
+ ]
3073
+ },
3074
+ contextMenu: {
3075
+ cut: "Ausschneiden",
3076
+ copy: "Kopieren",
3077
+ paste: "Einfügen",
3078
+ bold: "Fett",
3079
+ italic: "Kursiv",
3080
+ underline: "Unterstrichen",
3081
+ textColor: "Textfarbe",
3082
+ highlightColor: "Hervorhebungsfarbe",
3083
+ copyFormat: "Format kopieren",
3084
+ pasteFormat: "Format einfügen",
3085
+ removeFormat: "Formatierung entfernen",
3086
+ link: "Link einfügen",
3087
+ image: "Bild einfügen",
3088
+ video: "Video einfügen",
3089
+ table: "Tabelle einfügen",
3090
+ back: "Zurück",
3091
+ noHighlight: "Keine Hervorhebung",
3092
+ customColor: "Benutzerdefinierte Farbe",
3093
+ customColorLabel: "Benutzerdefiniert…"
3094
+ },
3095
+ statusbar: {
3096
+ resizeHandle: "Editor-Größe ändern",
3097
+ words: (n) => `Wörter: ${n}`,
3098
+ wordsLimit: (n, max) => `Wörter: ${n}/${max}`,
3099
+ chars: (n) => `Zeichen: ${n}`,
3100
+ charsLimit: (n, max) => `Zeichen: ${n}/${max}`
3101
+ },
3102
+ tooltips: {
3103
+ link: {
3104
+ ariaLabel: "Link-Aktionen",
3105
+ openLink: "Link öffnen",
3106
+ copyUrl: "URL kopieren",
3107
+ editLink: "Link bearbeiten",
3108
+ removeLink: "Link entfernen"
3109
+ },
3110
+ image: {
3111
+ ariaLabel: "Bild-Aktionen",
3112
+ label: "Bild",
3113
+ floatLeft: "Links umfließen",
3114
+ noFloat: "Kein Umfluss",
3115
+ alignCenter: "Zentriert",
3116
+ floatRight: "Rechts umfließen",
3117
+ originalSize: "Originalgröße",
3118
+ rotateLeft: "Links drehen",
3119
+ rotateRight: "Rechts drehen",
3120
+ cropImage: "Bild zuschneiden",
3121
+ addCaption: "Beschriftung hinzufügen / bearbeiten",
3122
+ deleteImage: "Bild löschen"
3123
+ },
3124
+ code: {
3125
+ ariaLabel: "Codeblock-Aktionen",
3126
+ label: "Code",
3127
+ syntaxLanguage: "Syntaxsprache",
3128
+ syntaxAriaLabel: "Syntaxsprache",
3129
+ copyCode: "Code kopieren",
3130
+ toggleWordWrap: "Zeilenumbruch umschalten",
3131
+ enableWordWrap: "Zeilenumbruch aktivieren",
3132
+ disableWordWrap: "Zeilenumbruch deaktivieren",
3133
+ convertToParagraph: "In Absatz umwandeln",
3134
+ deleteCodeBlock: "Codeblock löschen"
3135
+ },
3136
+ table: {
3137
+ ariaLabel: "Tabellen-Aktionen",
3138
+ label: "Tabelle",
3139
+ selectCells: "Zellen auswählen",
3140
+ addRowAbove: "Zeile oben hinzufügen",
3141
+ addRowBelow: "Zeile unten hinzufügen",
3142
+ deleteRow: "Zeile löschen",
3143
+ addColumnLeft: "Spalte links hinzufügen",
3144
+ addColumnRight: "Spalte rechts hinzufügen",
3145
+ deleteColumn: "Spalte löschen",
3146
+ mergeCells: "Zellen zusammenführen",
3147
+ unmergeCells: "Zellen trennen",
3148
+ columnWidth: "Spaltenbreite",
3149
+ rowHeight: "Zeilenhöhe",
3150
+ tableBorderWidth: "Tabellenrahmenbreite",
3151
+ deleteTable: "Tabelle löschen",
3152
+ columnWidthPx: "Spaltenbreite (px)",
3153
+ rowHeightPx: "Zeilenhöhe (px)",
3154
+ tableBorderWidthPx: "Tabellenrahmenbreite (px)",
3155
+ cancelBtn: "Abbrechen",
3156
+ applyBtn: "Anwenden"
3157
+ },
3158
+ video: {
3159
+ ariaLabel: "Video-Aktionen",
3160
+ label: "Video",
3161
+ floatLeft: "Links umfließen",
3162
+ noFloat: "Kein Umfluss",
3163
+ alignCenter: "Zentriert",
3164
+ floatRight: "Rechts umfließen",
3165
+ originalSize: "Originalgröße",
3166
+ previewVideo: "Video-Vorschau",
3167
+ exitPreview: "Vorschau beenden",
3168
+ deleteVideo: "Video löschen"
3169
+ }
3170
+ },
3171
+ errors: {
3172
+ imageFormat: (type) => `Das Format „${type}" wird in Webbrowsern nicht unterstützt. Bitte konvertieren Sie es zuerst in JPEG, PNG oder WebP.`,
3173
+ imageSize: (maxSize) => `Die Bilddatei ist zu groß. Die maximal zulässige Größe beträgt ${maxSize} MB.`
3174
+ }
3175
+ },
3176
+ es: {
3177
+ toolbar: {
3178
+ bold: "Negrita (Ctrl+B)",
3179
+ italic: "Cursiva (Ctrl+I)",
3180
+ underline: "Subrayado (Ctrl+U)",
3181
+ strikethrough: "Tachado",
3182
+ superscript: "Superíndice",
3183
+ subscript: "Subíndice",
3184
+ alignLeft: "Alinear a la izquierda",
3185
+ alignCenter: "Centrar",
3186
+ alignRight: "Alinear a la derecha",
3187
+ alignJustify: "Justificar",
3188
+ ul: "Lista sin orden",
3189
+ ol: "Lista ordenada",
3190
+ checklist: "Lista de verificación",
3191
+ indent: "Aumentar sangría",
3192
+ outdent: "Reducir sangría",
3193
+ undo: "Deshacer (Ctrl+Z)",
3194
+ redo: "Rehacer (Ctrl+Y)",
3195
+ hr: "Línea horizontal",
3196
+ link: "Insertar enlace",
3197
+ image: "Insertar imagen",
3198
+ video: "Insertar vídeo",
3199
+ emoji: "Insertar emoji",
3200
+ icon: "Insertar icono FA",
3201
+ table: "Insertar tabla",
3202
+ fontSize: "Tamaño de fuente",
3203
+ fontSizePlaceholder: "Tamaño",
3204
+ removeFormat: "Eliminar formato",
3205
+ direction: "Cambiar dirección del texto (LTR / RTL)",
3206
+ fontFamily: "Fuente",
3207
+ paragraphStyle: "Estilo de párrafo",
3208
+ paragraphStylePlaceholder: "Estilo",
3209
+ lineHeight: "Interlineado",
3210
+ lineHeightPlaceholder: "↕ Línea",
3211
+ codeview: "Vista de código HTML",
3212
+ fullscreen: "Pantalla completa",
3213
+ shortcuts: "Atajos de teclado (Ctrl+Shift+/)",
3214
+ find: "Buscar (Ctrl+F)",
3215
+ findReplace: "Buscar y reemplazar (Ctrl+H)",
3216
+ inlineCode: "Código en línea (Ctrl+`)",
3217
+ print: "Imprimir",
3218
+ foreColor: "Color del texto",
3219
+ backColor: "Color de resaltado",
3220
+ chooseTextColor: "Elegir color del texto",
3221
+ chooseHighlightColor: "Elegir color de resaltado",
3222
+ customColor: "Color personalizado",
3223
+ insertTableLabel: "Insertar tabla",
3224
+ paragraphItems: {
3225
+ p: "Normal",
3226
+ blockquote: "Cita",
3227
+ pre: "Código"
3228
+ }
3229
+ },
3230
+ linkDialog: {
3231
+ ariaLabel: "Insertar enlace",
3232
+ title: "Insertar enlace",
3233
+ url: "URL",
3234
+ urlPlaceholder: "https://",
3235
+ displayText: "Texto a mostrar",
3236
+ textPlaceholder: "Texto del enlace",
3237
+ openInNewTab: "Abrir en nueva pestaña",
3238
+ insertBtn: "Insertar",
3239
+ cancelBtn: "Cancelar"
3240
+ },
3241
+ imageDialog: {
3242
+ ariaLabel: "Insertar imagen",
3243
+ title: "Insertar imagen",
3244
+ imageUrl: "URL de imagen",
3245
+ urlPlaceholder: "https://example.com/image.png",
3246
+ altText: "Texto alternativo",
3247
+ altPlaceholder: "Describir la imagen",
3248
+ alignment: "Alineación",
3249
+ alignNone: "Ninguna",
3250
+ alignLeft: "Izquierda",
3251
+ alignCenter: "Centro",
3252
+ alignRight: "Derecha",
3253
+ uploadLabel: "O subir un archivo",
3254
+ insertBtn: "Insertar",
3255
+ cancelBtn: "Cancelar"
3256
+ },
3257
+ videoDialog: {
3258
+ ariaLabel: "Insertar vídeo",
3259
+ title: "Insertar vídeo",
3260
+ videoUrl: "URL del vídeo",
3261
+ urlPlaceholder: "YouTube, Vimeo o URL directa .mp4",
3262
+ widthLabel: "Ancho (px)",
3263
+ widthPlaceholder: "560",
3264
+ insertBtn: "Insertar",
3265
+ cancelBtn: "Cancelar",
3266
+ detected: (type) => `Detectado: ${type}`,
3267
+ unknownFormat: "Formato desconocido — se intentará incrustar el vídeo directamente",
3268
+ invalidUrl: "URL no válida — introduzca un enlace de vídeo válido."
3269
+ },
3270
+ emojiDialog: {
3271
+ ariaLabel: "Insertar emoji",
3272
+ title: "Insertar emoji",
3273
+ searchPlaceholder: "Buscar emojis…",
3274
+ all: "Todos",
3275
+ cancelBtn: "Cancelar",
3276
+ close: "Cerrar",
3277
+ categories: {
3278
+ smileys: "Caritas",
3279
+ people: "Personas",
3280
+ animals: "Animales",
3281
+ food: "Comida",
3282
+ travel: "Viajes",
3283
+ objects: "Objetos",
3284
+ symbols: "Símbolos"
3285
+ }
3286
+ },
3287
+ iconDialog: {
3288
+ ariaLabel: "Insertar icono FA",
3289
+ title: "Insertar icono FA",
3290
+ searchPlaceholder: "Buscar iconos…",
3291
+ all: "Todos",
3292
+ style: "Estilo",
3293
+ size: "Tamaño",
3294
+ color: "Color",
3295
+ useColor: " Usar color",
3296
+ selectHint: "Seleccionar un icono",
3297
+ insertBtn: "Insertar icono FA",
3298
+ cancelBtn: "Cancelar",
3299
+ close: "Cerrar",
3300
+ categories: {
3301
+ popular: "Popular",
3302
+ interface: "Interfaz",
3303
+ navigation: "Navegación",
3304
+ media: "Medios",
3305
+ communication: "Comunicación",
3306
+ files: "Archivos",
3307
+ people: "Personas",
3308
+ objects: "Objetos"
3309
+ }
3310
+ },
3311
+ findReplace: {
3312
+ findTitle: "Buscar",
3313
+ findReplaceTitle: "Buscar y reemplazar",
3314
+ findPlaceholder: "Buscar…",
3315
+ searchAriaLabel: "Texto de búsqueda",
3316
+ caseSensitive: "\xA0Distinguir mayúsculas",
3317
+ prevBtn: "← Anterior",
3318
+ nextBtn: "Siguiente →",
3319
+ replacePlaceholder: "Reemplazar con…",
3320
+ replaceAriaLabel: "Reemplazar con",
3321
+ replaceBtn: "Reemplazar",
3322
+ replaceAllBtn: "Reemplazar todo",
3323
+ close: "×"
3324
+ },
3325
+ shortcutsDialog: {
3326
+ title: "Atajos de teclado",
3327
+ ariaLabel: "Atajos de teclado",
3328
+ close: "Cerrar",
3329
+ shortcuts: [
3330
+ {
3331
+ category: "Formato de texto",
3332
+ items: [
3333
+ {
3334
+ keys: "Ctrl + B",
3335
+ action: "Negrita"
3336
+ },
3337
+ {
3338
+ keys: "Ctrl + I",
3339
+ action: "Cursiva"
3340
+ },
3341
+ {
3342
+ keys: "Ctrl + U",
3343
+ action: "Subrayado"
3344
+ },
3345
+ {
3346
+ keys: "Ctrl + K",
3347
+ action: "Insertar / editar enlace"
3348
+ }
3349
+ ]
3350
+ },
3351
+ {
3352
+ category: "Historial",
3353
+ items: [{
3354
+ keys: "Ctrl + Z",
3355
+ action: "Deshacer"
3356
+ }, {
3357
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
3358
+ action: "Rehacer"
3359
+ }]
3360
+ },
3361
+ {
3362
+ category: "Selección y navegación",
3363
+ items: [
3364
+ {
3365
+ keys: "Ctrl + A",
3366
+ action: "Seleccionar todo"
3367
+ },
3368
+ {
3369
+ keys: "Tab",
3370
+ action: "Aumentar sangría / insertar espacios"
3371
+ },
3372
+ {
3373
+ keys: "Shift + Tab",
3374
+ action: "Reducir sangría"
3375
+ }
3376
+ ]
3377
+ },
3378
+ {
3379
+ category: "Portapapeles",
3380
+ items: [{
3381
+ keys: "Ctrl + Shift + V",
3382
+ action: "Pegar como texto plano"
3383
+ }]
3384
+ },
3385
+ {
3386
+ category: "Buscar y reemplazar",
3387
+ items: [{
3388
+ keys: "Ctrl + F",
3389
+ action: "Buscar en el documento"
3390
+ }, {
3391
+ keys: "Ctrl + H",
3392
+ action: "Buscar y reemplazar"
3393
+ }]
3394
+ },
3395
+ {
3396
+ category: "Editor",
3397
+ items: [{
3398
+ keys: "Ctrl + Shift + /",
3399
+ action: "Mostrar este diálogo de atajos"
3400
+ }]
3401
+ }
3402
+ ]
3403
+ },
3404
+ contextMenu: {
3405
+ cut: "Cortar",
3406
+ copy: "Copiar",
3407
+ paste: "Pegar",
3408
+ bold: "Negrita",
3409
+ italic: "Cursiva",
3410
+ underline: "Subrayado",
3411
+ textColor: "Color del texto",
3412
+ highlightColor: "Color de resaltado",
3413
+ copyFormat: "Copiar formato",
3414
+ pasteFormat: "Pegar formato",
3415
+ removeFormat: "Eliminar formato",
3416
+ link: "Insertar enlace",
3417
+ image: "Insertar imagen",
3418
+ video: "Insertar vídeo",
3419
+ table: "Insertar tabla",
3420
+ back: "Atrás",
3421
+ noHighlight: "Sin resaltado",
3422
+ customColor: "Color personalizado",
3423
+ customColorLabel: "Personalizado…"
3424
+ },
3425
+ statusbar: {
3426
+ resizeHandle: "Redimensionar editor",
3427
+ words: (n) => `Palabras: ${n}`,
3428
+ wordsLimit: (n, max) => `Palabras: ${n}/${max}`,
3429
+ chars: (n) => `Caracteres: ${n}`,
3430
+ charsLimit: (n, max) => `Caracteres: ${n}/${max}`
3431
+ },
3432
+ tooltips: {
3433
+ link: {
3434
+ ariaLabel: "Acciones de enlace",
3435
+ openLink: "Abrir enlace",
3436
+ copyUrl: "Copiar URL",
3437
+ editLink: "Editar enlace",
3438
+ removeLink: "Eliminar enlace"
3439
+ },
3440
+ image: {
3441
+ ariaLabel: "Acciones de imagen",
3442
+ label: "Imagen",
3443
+ floatLeft: "Flotar a la izquierda",
3444
+ noFloat: "Sin flotado",
3445
+ alignCenter: "Centrar",
3446
+ floatRight: "Flotar a la derecha",
3447
+ originalSize: "Tamaño original",
3448
+ rotateLeft: "Rotar a la izquierda",
3449
+ rotateRight: "Rotar a la derecha",
3450
+ cropImage: "Recortar imagen",
3451
+ addCaption: "Añadir / editar pie de foto",
3452
+ deleteImage: "Eliminar imagen"
3453
+ },
3454
+ code: {
3455
+ ariaLabel: "Acciones de bloque de código",
3456
+ label: "Código",
3457
+ syntaxLanguage: "Lenguaje de sintaxis",
3458
+ syntaxAriaLabel: "Lenguaje de sintaxis",
3459
+ copyCode: "Copiar código",
3460
+ toggleWordWrap: "Alternar ajuste de línea",
3461
+ enableWordWrap: "Activar ajuste de línea",
3462
+ disableWordWrap: "Desactivar ajuste de línea",
3463
+ convertToParagraph: "Convertir en párrafo",
3464
+ deleteCodeBlock: "Eliminar bloque de código"
3465
+ },
3466
+ table: {
3467
+ ariaLabel: "Acciones de tabla",
3468
+ label: "Tabla",
3469
+ selectCells: "Seleccionar celdas",
3470
+ addRowAbove: "Añadir fila encima",
3471
+ addRowBelow: "Añadir fila debajo",
3472
+ deleteRow: "Eliminar fila",
3473
+ addColumnLeft: "Añadir columna a la izquierda",
3474
+ addColumnRight: "Añadir columna a la derecha",
3475
+ deleteColumn: "Eliminar columna",
3476
+ mergeCells: "Combinar celdas",
3477
+ unmergeCells: "Separar celdas",
3478
+ columnWidth: "Ancho de columna",
3479
+ rowHeight: "Alto de fila",
3480
+ tableBorderWidth: "Grosor del borde de la tabla",
3481
+ deleteTable: "Eliminar tabla",
3482
+ columnWidthPx: "Ancho de columna (px)",
3483
+ rowHeightPx: "Alto de fila (px)",
3484
+ tableBorderWidthPx: "Grosor del borde (px)",
3485
+ cancelBtn: "Cancelar",
3486
+ applyBtn: "Aplicar"
3487
+ },
3488
+ video: {
3489
+ ariaLabel: "Acciones de vídeo",
3490
+ label: "Vídeo",
3491
+ floatLeft: "Flotar a la izquierda",
3492
+ noFloat: "Sin flotado",
3493
+ alignCenter: "Centrar",
3494
+ floatRight: "Flotar a la derecha",
3495
+ originalSize: "Tamaño original",
3496
+ previewVideo: "Vista previa de vídeo",
3497
+ exitPreview: "Salir de la vista previa",
3498
+ deleteVideo: "Eliminar vídeo"
3499
+ }
3500
+ },
3501
+ errors: {
3502
+ imageFormat: (type) => `El formato "${type}" no es compatible con los navegadores web. Por favor, conviértalo primero a JPEG, PNG o WebP.`,
3503
+ imageSize: (maxSize) => `El archivo de imagen es demasiado grande. El tamaño máximo permitido es ${maxSize} MB.`
3504
+ }
3505
+ },
3506
+ ko: {
3507
+ toolbar: {
3508
+ bold: "굵게 (Ctrl+B)",
3509
+ italic: "기울임꼴 (Ctrl+I)",
3510
+ underline: "밑줄 (Ctrl+U)",
3511
+ strikethrough: "취소선",
3512
+ superscript: "위 첨자",
3513
+ subscript: "아래 첨자",
3514
+ alignLeft: "왼쪽 정렬",
3515
+ alignCenter: "가운데 정렬",
3516
+ alignRight: "오른쪽 정렬",
3517
+ alignJustify: "양쪽 정렬",
3518
+ ul: "순서 없는 목록",
3519
+ ol: "순서 있는 목록",
3520
+ checklist: "체크리스트",
3521
+ indent: "들여쓰기",
3522
+ outdent: "내어쓰기",
3523
+ undo: "실행 취소 (Ctrl+Z)",
3524
+ redo: "다시 실행 (Ctrl+Y)",
3525
+ hr: "수평선",
3526
+ link: "링크 삽입",
3527
+ image: "이미지 삽입",
3528
+ video: "동영상 삽입",
3529
+ emoji: "이모지 삽입",
3530
+ icon: "FA 아이콘 삽입",
3531
+ table: "표 삽입",
3532
+ fontSize: "글꼴 크기",
3533
+ fontSizePlaceholder: "크기",
3534
+ removeFormat: "서식 제거",
3535
+ direction: "텍스트 방향 전환 (LTR / RTL)",
3536
+ fontFamily: "글꼴",
3537
+ paragraphStyle: "단락 스타일",
3538
+ paragraphStylePlaceholder: "스타일",
3539
+ lineHeight: "줄 간격",
3540
+ lineHeightPlaceholder: "↕ 줄",
3541
+ codeview: "HTML 코드 보기",
3542
+ fullscreen: "전체 화면",
3543
+ shortcuts: "키보드 단축키 (Ctrl+Shift+/)",
3544
+ find: "찾기 (Ctrl+F)",
3545
+ findReplace: "찾기 및 바꾸기 (Ctrl+H)",
3546
+ inlineCode: "인라인 코드 (Ctrl+`)",
3547
+ print: "인쇄",
3548
+ foreColor: "글자 색",
3549
+ backColor: "강조 색",
3550
+ chooseTextColor: "글자 색 선택",
3551
+ chooseHighlightColor: "강조 색 선택",
3552
+ customColor: "사용자 지정 색",
3553
+ insertTableLabel: "표 삽입",
3554
+ paragraphItems: {
3555
+ p: "기본",
3556
+ blockquote: "인용",
3557
+ pre: "코드"
3558
+ }
3559
+ },
3560
+ linkDialog: {
3561
+ ariaLabel: "링크 삽입",
3562
+ title: "링크 삽입",
3563
+ url: "URL",
3564
+ urlPlaceholder: "https://",
3565
+ displayText: "표시 텍스트",
3566
+ textPlaceholder: "링크 텍스트",
3567
+ openInNewTab: "새 탭에서 열기",
3568
+ insertBtn: "삽입",
3569
+ cancelBtn: "취소"
3570
+ },
3571
+ imageDialog: {
3572
+ ariaLabel: "이미지 삽입",
3573
+ title: "이미지 삽입",
3574
+ imageUrl: "이미지 URL",
3575
+ urlPlaceholder: "https://example.com/image.png",
3576
+ altText: "대체 텍스트",
3577
+ altPlaceholder: "이미지 설명",
3578
+ alignment: "정렬",
3579
+ alignNone: "없음",
3580
+ alignLeft: "왼쪽",
3581
+ alignCenter: "가운데",
3582
+ alignRight: "오른쪽",
3583
+ uploadLabel: "또는 파일 업로드",
3584
+ insertBtn: "삽입",
3585
+ cancelBtn: "취소"
3586
+ },
3587
+ videoDialog: {
3588
+ ariaLabel: "동영상 삽입",
3589
+ title: "동영상 삽입",
3590
+ videoUrl: "동영상 URL",
3591
+ urlPlaceholder: "YouTube, Vimeo 또는 직접 .mp4 URL",
3592
+ widthLabel: "너비 (px)",
3593
+ widthPlaceholder: "560",
3594
+ insertBtn: "삽입",
3595
+ cancelBtn: "취소",
3596
+ detected: (type) => `감지됨: ${type}`,
3597
+ unknownFormat: "알 수 없는 형식 — 직접 동영상 임베드를 시도합니다",
3598
+ invalidUrl: "유효하지 않은 URL — 올바른 동영상 링크를 입력하세요."
3599
+ },
3600
+ emojiDialog: {
3601
+ ariaLabel: "이모지 삽입",
3602
+ title: "이모지 삽입",
3603
+ searchPlaceholder: "이모지 검색…",
3604
+ all: "전체",
3605
+ cancelBtn: "취소",
3606
+ close: "닫기",
3607
+ categories: {
3608
+ smileys: "스마일",
3609
+ people: "사람",
3610
+ animals: "동물",
3611
+ food: "음식",
3612
+ travel: "여행",
3613
+ objects: "사물",
3614
+ symbols: "기호"
3615
+ }
3616
+ },
3617
+ iconDialog: {
3618
+ ariaLabel: "FA 아이콘 삽입",
3619
+ title: "FA 아이콘 삽입",
3620
+ searchPlaceholder: "아이콘 검색…",
3621
+ all: "전체",
3622
+ style: "스타일",
3623
+ size: "크기",
3624
+ color: "색상",
3625
+ useColor: " 색상 사용",
3626
+ selectHint: "아이콘을 선택하세요",
3627
+ insertBtn: "FA 아이콘 삽입",
3628
+ cancelBtn: "취소",
3629
+ close: "닫기",
3630
+ categories: {
3631
+ popular: "인기",
3632
+ interface: "인터페이스",
3633
+ navigation: "탐색",
3634
+ media: "미디어",
3635
+ communication: "커뮤니케이션",
3636
+ files: "파일",
3637
+ people: "사람",
3638
+ objects: "사물"
3639
+ }
3640
+ },
3641
+ findReplace: {
3642
+ findTitle: "찾기",
3643
+ findReplaceTitle: "찾기 및 바꾸기",
3644
+ findPlaceholder: "찾기…",
3645
+ searchAriaLabel: "검색 텍스트",
3646
+ caseSensitive: "\xA0대소문자 구분",
3647
+ prevBtn: "← 이전",
3648
+ nextBtn: "다음 →",
3649
+ replacePlaceholder: "바꿀 내용…",
3650
+ replaceAriaLabel: "바꿀 내용",
3651
+ replaceBtn: "바꾸기",
3652
+ replaceAllBtn: "모두 바꾸기",
3653
+ close: "×"
3654
+ },
3655
+ shortcutsDialog: {
3656
+ title: "키보드 단축키",
3657
+ ariaLabel: "키보드 단축키",
3658
+ close: "닫기",
3659
+ shortcuts: [
3660
+ {
3661
+ category: "텍스트 서식",
3662
+ items: [
3663
+ {
3664
+ keys: "Ctrl + B",
3665
+ action: "굵게"
3666
+ },
3667
+ {
3668
+ keys: "Ctrl + I",
3669
+ action: "기울임꼴"
3670
+ },
3671
+ {
3672
+ keys: "Ctrl + U",
3673
+ action: "밑줄"
3674
+ },
3675
+ {
3676
+ keys: "Ctrl + K",
3677
+ action: "링크 삽입 / 편집"
3678
+ }
3679
+ ]
3680
+ },
3681
+ {
3682
+ category: "실행 기록",
3683
+ items: [{
3684
+ keys: "Ctrl + Z",
3685
+ action: "실행 취소"
3686
+ }, {
3687
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
3688
+ action: "다시 실행"
3689
+ }]
3690
+ },
3691
+ {
3692
+ category: "선택 및 탐색",
3693
+ items: [
3694
+ {
3695
+ keys: "Ctrl + A",
3696
+ action: "전체 선택"
3697
+ },
3698
+ {
3699
+ keys: "Tab",
3700
+ action: "목록 들여쓰기 / 공백 삽입"
3701
+ },
3702
+ {
3703
+ keys: "Shift + Tab",
3704
+ action: "목록 내어쓰기"
3705
+ }
3706
+ ]
3707
+ },
3708
+ {
3709
+ category: "클립보드",
3710
+ items: [{
3711
+ keys: "Ctrl + Shift + V",
3712
+ action: "일반 텍스트로 붙여넣기"
3713
+ }]
3714
+ },
3715
+ {
3716
+ category: "찾기 및 바꾸기",
3717
+ items: [{
3718
+ keys: "Ctrl + F",
3719
+ action: "문서에서 찾기"
3720
+ }, {
3721
+ keys: "Ctrl + H",
3722
+ action: "찾기 및 바꾸기"
3723
+ }]
3724
+ },
3725
+ {
3726
+ category: "편집기",
3727
+ items: [{
3728
+ keys: "Ctrl + Shift + /",
3729
+ action: "이 단축키 대화상자 표시"
3730
+ }]
3731
+ }
3732
+ ]
3733
+ },
3734
+ contextMenu: {
3735
+ cut: "잘라내기",
3736
+ copy: "복사",
3737
+ paste: "붙여넣기",
3738
+ bold: "굵게",
3739
+ italic: "기울임꼴",
3740
+ underline: "밑줄",
3741
+ textColor: "글자 색",
3742
+ highlightColor: "강조 색",
3743
+ copyFormat: "서식 복사",
3744
+ pasteFormat: "서식 붙여넣기",
3745
+ removeFormat: "서식 제거",
3746
+ link: "링크 삽입",
3747
+ image: "이미지 삽입",
3748
+ video: "동영상 삽입",
3749
+ table: "표 삽입",
3750
+ back: "뒤로",
3751
+ noHighlight: "강조 없음",
3752
+ customColor: "사용자 지정 색",
3753
+ customColorLabel: "사용자 지정…"
3754
+ },
3755
+ statusbar: {
3756
+ resizeHandle: "편집기 크기 조정",
3757
+ words: (n) => `단어: ${n}`,
3758
+ wordsLimit: (n, max) => `단어: ${n}/${max}`,
3759
+ chars: (n) => `글자: ${n}`,
3760
+ charsLimit: (n, max) => `글자: ${n}/${max}`
3761
+ },
3762
+ tooltips: {
3763
+ link: {
3764
+ ariaLabel: "링크 작업",
3765
+ openLink: "링크 열기",
3766
+ copyUrl: "URL 복사",
3767
+ editLink: "링크 편집",
3768
+ removeLink: "링크 제거"
3769
+ },
3770
+ image: {
3771
+ ariaLabel: "이미지 작업",
3772
+ label: "이미지",
3773
+ floatLeft: "왼쪽 배치",
3774
+ noFloat: "배치 없음",
3775
+ alignCenter: "가운데 정렬",
3776
+ floatRight: "오른쪽 배치",
3777
+ originalSize: "원본 크기",
3778
+ rotateLeft: "왼쪽 회전",
3779
+ rotateRight: "오른쪽 회전",
3780
+ cropImage: "이미지 자르기",
3781
+ addCaption: "캡션 추가 / 편집",
3782
+ deleteImage: "이미지 삭제"
3783
+ },
3784
+ code: {
3785
+ ariaLabel: "코드 블록 작업",
3786
+ label: "코드",
3787
+ syntaxLanguage: "구문 언어",
3788
+ syntaxAriaLabel: "구문 언어",
3789
+ copyCode: "코드 복사",
3790
+ toggleWordWrap: "줄 바꿈 전환",
3791
+ enableWordWrap: "줄 바꿈 사용",
3792
+ disableWordWrap: "줄 바꿈 해제",
3793
+ convertToParagraph: "단락으로 변환",
3794
+ deleteCodeBlock: "코드 블록 삭제"
3795
+ },
3796
+ table: {
3797
+ ariaLabel: "표 작업",
3798
+ label: "표",
3799
+ selectCells: "셀 선택",
3800
+ addRowAbove: "위에 행 추가",
3801
+ addRowBelow: "아래에 행 추가",
3802
+ deleteRow: "행 삭제",
3803
+ addColumnLeft: "왼쪽에 열 추가",
3804
+ addColumnRight: "오른쪽에 열 추가",
3805
+ deleteColumn: "열 삭제",
3806
+ mergeCells: "셀 병합",
3807
+ unmergeCells: "셀 분할",
3808
+ columnWidth: "열 너비",
3809
+ rowHeight: "행 높이",
3810
+ tableBorderWidth: "표 테두리 너비",
3811
+ deleteTable: "표 삭제",
3812
+ columnWidthPx: "열 너비 (px)",
3813
+ rowHeightPx: "행 높이 (px)",
3814
+ tableBorderWidthPx: "표 테두리 너비 (px)",
3815
+ cancelBtn: "취소",
3816
+ applyBtn: "적용"
3817
+ },
3818
+ video: {
3819
+ ariaLabel: "동영상 작업",
3820
+ label: "동영상",
3821
+ floatLeft: "왼쪽 배치",
3822
+ noFloat: "배치 없음",
3823
+ alignCenter: "가운데 정렬",
3824
+ floatRight: "오른쪽 배치",
3825
+ originalSize: "원본 크기",
3826
+ previewVideo: "동영상 미리보기",
3827
+ exitPreview: "미리보기 종료",
3828
+ deleteVideo: "동영상 삭제"
3829
+ }
3830
+ },
3831
+ errors: {
3832
+ imageFormat: (type) => `"${type}" 형식은 웹 브라우저에서 지원되지 않습니다. JPEG, PNG 또는 WebP로 변환해 주세요.`,
3833
+ imageSize: (maxSize) => `이미지 파일이 너무 큽니다. 최대 허용 크기는 ${maxSize} MB입니다.`
3834
+ }
3835
+ }
3836
+ };
3837
+ /**
3838
+ * Resolve a locale object from a lang option value.
3839
+ *
3840
+ * @param {string | Partial<AsnLocale> | null | undefined} lang
3841
+ * @returns {AsnLocale} A fully-populated locale (always contains every key from en.js).
3842
+ */
3843
+ function resolveLocale(lang) {
3844
+ if (!lang || lang === "en") return en;
3845
+ if (typeof lang === "string") {
3846
+ const partial = locales[lang];
3847
+ if (!partial) return en;
3848
+ return mergeDeep(mergeDeep({}, en), partial);
3849
+ }
3850
+ if (typeof lang === "object") return mergeDeep(mergeDeep({}, en), lang);
3851
+ return en;
3852
+ }
3853
+ /**
3854
+ * @typedef {Object} AsnLocale (see types/index.d.ts for the full definition)
3855
+ */
3856
+ //#endregion
1046
3857
  //#region src/js/core/sanitise.js
1047
3858
  /**
1048
3859
  * sanitise.js - Shared HTML and URL sanitisation utilities
@@ -1676,7 +4487,7 @@
1676
4487
  const para = closestPara(range.sc, editable);
1677
4488
  if (para && isLi(para)) {
1678
4489
  event.preventDefault();
1679
- if (event.shiftKey) execCommand("outdent");
4490
+ if (event.shiftKey) outdent();
1680
4491
  else execCommand("indent");
1681
4492
  return true;
1682
4493
  }
@@ -2097,7 +4908,12 @@
2097
4908
  };
2098
4909
  const isReadOnly = () => this.context.layoutInfo.container.classList.contains("an-disabled");
2099
4910
  this._disposers.push(on(editable, "keydown", onKeydown), on(editable, "beforeinput", onBeforeInput), on(editable, "input", onInput), on(document, "selectionchange", onSelChange), on(editable, "click", onCheckboxClick), on(editable, "mouseup", fixChecklistCursor), on(editable, "keyup", fixChecklistCursor), on(editable, "dragstart", (e) => {
2100
- if (isReadOnly()) e.preventDefault();
4911
+ if (isReadOnly()) {
4912
+ e.preventDefault();
4913
+ return;
4914
+ }
4915
+ const target = e.target;
4916
+ if (target && (target.nodeName === "IFRAME" || target.closest && target.closest(".an-video-wrapper"))) e.preventDefault();
2101
4917
  }), on(editable, "drop", (e) => {
2102
4918
  if (isReadOnly()) e.preventDefault();
2103
4919
  }));
@@ -2254,7 +5070,7 @@
2254
5070
  * @param {string} html - HTML string (will be sanitised)
2255
5071
  */
2256
5072
  setHTML(html) {
2257
- this.context.layoutInfo.editable.innerHTML = sanitiseHTML(html);
5073
+ this.context.layoutInfo.editable.innerHTML = sanitiseHTML(html, { allowIframes: true });
2258
5074
  if (this._history) this._history.reset();
2259
5075
  this.afterCommand();
2260
5076
  }
@@ -2688,9 +5504,9 @@
2688
5504
  const btn = createElement("button", {
2689
5505
  type: "button",
2690
5506
  class: !!this.options.useBootstrap ? this.options.toolbarButtonClass || "btn btn-sm btn-light" : "an-btn",
2691
- title: def.tooltip || "",
5507
+ title: this.context.locale.toolbar[def.name] || def.tooltip || "",
2692
5508
  "data-btn": def.name,
2693
- "aria-label": def.tooltip || def.name,
5509
+ "aria-label": this.context.locale.toolbar[def.name] || def.tooltip || def.name,
2694
5510
  "aria-haspopup": "true",
2695
5511
  "aria-expanded": "false"
2696
5512
  });
@@ -2703,7 +5519,7 @@
2703
5519
  });
2704
5520
  const grid = createElement("div", { class: "an-table-grid" });
2705
5521
  const label = createElement("div", { class: "an-table-label" });
2706
- label.textContent = "Insert Table";
5522
+ label.textContent = this.context.locale.toolbar.insertTableLabel || "Insert Table";
2707
5523
  const cells = [];
2708
5524
  for (let r = 1; r <= ROWS; r++) for (let c = 1; c <= COLS; c++) {
2709
5525
  const cell = createElement("div", {
@@ -2723,7 +5539,7 @@
2723
5539
  const c = +cell.getAttribute("data-col");
2724
5540
  cell.classList.toggle("active", r <= rows && c <= cols);
2725
5541
  });
2726
- label.textContent = rows && cols ? `${rows} × ${cols}` : "Insert Table";
5542
+ label.textContent = rows && cols ? `${rows} × ${cols}` : this.context.locale.toolbar.insertTableLabel || "Insert Table";
2727
5543
  };
2728
5544
  const openPopup = () => {
2729
5545
  isOpen = true;
@@ -2803,9 +5619,9 @@
2803
5619
  const applyBtn = createElement("button", {
2804
5620
  type: "button",
2805
5621
  class: `${baseClass} an-color-btn`,
2806
- title: def.tooltip || "",
5622
+ title: this.context.locale.toolbar[def.name] || def.tooltip || "",
2807
5623
  "data-btn": def.name,
2808
- "aria-label": def.tooltip || def.name
5624
+ "aria-label": this.context.locale.toolbar[def.name] || def.tooltip || def.name
2809
5625
  });
2810
5626
  const S = "stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
2811
5627
  applyBtn.innerHTML = def.name === "foreColor" ? `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" ${S} style="display:block"><path d="M4 20L12 4L20 20"/><line x1="7.5" y1="14" x2="16.5" y2="14"/></svg>` : `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" ${S} style="display:block"><path d="M3 21v-4l9-9 4 4-9 9z"/><path d="M12 8l4 4"/></svg>`;
@@ -2815,7 +5631,7 @@
2815
5631
  const arrowBtn = createElement("button", {
2816
5632
  type: "button",
2817
5633
  class: `${baseClass} an-color-arrow`,
2818
- title: `Choose ${def.name === "foreColor" ? "text" : "highlight"} color`,
5634
+ title: def.name === "foreColor" ? this.context.locale.toolbar.chooseTextColor || "Choose text color" : this.context.locale.toolbar.chooseHighlightColor || "Choose highlight color",
2819
5635
  "aria-haspopup": "true",
2820
5636
  "aria-expanded": "false"
2821
5637
  });
@@ -2837,9 +5653,9 @@
2837
5653
  const colorInput = createElement("input", {
2838
5654
  type: "color",
2839
5655
  value: currentColor,
2840
- title: "Custom color"
5656
+ title: this.context.locale.toolbar.customColor || "Custom color"
2841
5657
  });
2842
- const customLabel = createElement("span", {}, ["Custom color"]);
5658
+ const customLabel = createElement("span", {}, [this.context.locale.toolbar.customColor || "Custom color"]);
2843
5659
  customRow.appendChild(colorInput);
2844
5660
  customRow.appendChild(customLabel);
2845
5661
  popup.appendChild(swatches);
@@ -2949,19 +5765,19 @@
2949
5765
  const items = def.name === "fontFamily" ? this.options.fontFamilies || [] : def.items || [];
2950
5766
  const select = createElement("select", {
2951
5767
  class: def.selectClass ? `an-select ${def.selectClass}` : "an-select",
2952
- title: def.tooltip || "",
5768
+ title: this.context.locale.toolbar[def.name] || def.tooltip || "",
2953
5769
  "data-btn": def.name,
2954
- "aria-label": def.tooltip || def.name
5770
+ "aria-label": this.context.locale.toolbar[def.name] || def.tooltip || def.name
2955
5771
  });
2956
5772
  const placeholder = createElement("option", {
2957
5773
  value: "",
2958
5774
  disabled: "",
2959
5775
  hidden: ""
2960
- }, [def.placeholder || "Font"]);
5776
+ }, [this.context.locale.toolbar[def.name + "Placeholder"] || def.placeholder || "Font"]);
2961
5777
  select.appendChild(placeholder);
2962
5778
  items.forEach((item) => {
2963
5779
  const value = typeof item === "object" ? item.value : item;
2964
- const label = typeof item === "object" ? item.label : item;
5780
+ const label = typeof item === "object" ? def.name === "paragraphStyle" ? (this.context.locale.toolbar.paragraphItems || {})[item.value] || item.label : item.label : item;
2965
5781
  const isHeader = typeof item === "object" && !!item.disabled;
2966
5782
  const attrs = { value };
2967
5783
  if (isHeader) attrs.disabled = "";
@@ -3001,9 +5817,9 @@
3001
5817
  const btn = createElement("button", {
3002
5818
  type: "button",
3003
5819
  class: `${!!this.options.useBootstrap ? this.options.toolbarButtonClass || "btn btn-sm btn-light" : `an-btn`}${btnDef.className ? ` ${btnDef.className}` : ""}`,
3004
- title: btnDef.tooltip || "",
5820
+ title: this.context.locale.toolbar[btnDef.name] || btnDef.tooltip || "",
3005
5821
  "data-btn": btnDef.name,
3006
- "aria-label": btnDef.tooltip || btnDef.name
5822
+ "aria-label": this.context.locale.toolbar[btnDef.name] || btnDef.tooltip || btnDef.name
3007
5823
  });
3008
5824
  const faPrefix = this.options.fontAwesomeClass || "fas";
3009
5825
  if (this._faReady) {
@@ -3128,7 +5944,7 @@
3128
5944
  if (this.options.resizable !== false) {
3129
5945
  const handle = createElement("div", {
3130
5946
  class: "an-resize-handle",
3131
- title: "Resize editor",
5947
+ title: this.context.locale.statusbar.resizeHandle,
3132
5948
  "aria-hidden": "true"
3133
5949
  });
3134
5950
  this._bindResize(handle);
@@ -3218,8 +6034,9 @@
3218
6034
  const chars = text.replace(/\n/g, "").length;
3219
6035
  const maxWords = this.options.maxWords || 0;
3220
6036
  const maxChars = this.options.maxChars || 0;
3221
- this._wordCountEl.textContent = maxWords ? `Words: ${words}/${maxWords}` : `Words: ${words}`;
3222
- this._charCountEl.textContent = maxChars ? `Chars: ${chars}/${maxChars}` : `Chars: ${chars}`;
6037
+ const LS = this.context.locale.statusbar;
6038
+ this._wordCountEl.textContent = maxWords ? LS.wordsLimit(words, maxWords) : LS.words(words);
6039
+ this._charCountEl.textContent = maxChars ? LS.charsLimit(chars, maxChars) : LS.chars(chars);
3223
6040
  _applyLimitClass(this._wordCountEl, words, maxWords);
3224
6041
  _applyLimitClass(this._charCountEl, chars, maxChars);
3225
6042
  }
@@ -3629,7 +6446,7 @@
3629
6446
  _update() {
3630
6447
  const editable = this.context.layoutInfo.editable;
3631
6448
  const isFocused = document.activeElement === editable;
3632
- const isEmpty = !editable.textContent.trim() && !editable.querySelector("img, table, hr, .an-video-wrapper");
6449
+ const isEmpty = !(editable.textContent.replace(/\u200B/g, "").trim().length > 0) && !editable.querySelector("img, table, hr, .an-video-wrapper");
3633
6450
  editable.classList.toggle("an-placeholder", isEmpty && !isFocused);
3634
6451
  }
3635
6452
  };
@@ -3816,32 +6633,33 @@
3816
6633
  this._open();
3817
6634
  }
3818
6635
  _buildDialog() {
6636
+ const L = this.context.locale.linkDialog;
3819
6637
  const overlay = createElement("div", {
3820
6638
  class: "an-dialog-overlay",
3821
6639
  role: "dialog",
3822
6640
  "aria-modal": "true",
3823
- "aria-label": "Insert link"
6641
+ "aria-label": L.ariaLabel
3824
6642
  });
3825
6643
  const box = createElement("div", { class: "an-dialog-box" });
3826
6644
  const title = createElement("h3", { class: "an-dialog-title" });
3827
- title.textContent = "Insert Link";
6645
+ title.textContent = L.title;
3828
6646
  const urlLabel = createElement("label", { class: "an-label" });
3829
- urlLabel.textContent = "URL";
6647
+ urlLabel.textContent = L.url;
3830
6648
  const urlInput = createElement("input", {
3831
6649
  type: "url",
3832
6650
  class: "an-input",
3833
- placeholder: "https://",
6651
+ placeholder: L.urlPlaceholder,
3834
6652
  id: "an-link-url",
3835
6653
  name: "url",
3836
6654
  autocomplete: "off"
3837
6655
  });
3838
6656
  this._urlInput = urlInput;
3839
6657
  const textLabel = createElement("label", { class: "an-label" });
3840
- textLabel.textContent = "Display Text";
6658
+ textLabel.textContent = L.displayText;
3841
6659
  const textInput = createElement("input", {
3842
6660
  type: "text",
3843
6661
  class: "an-input",
3844
- placeholder: "Link text",
6662
+ placeholder: L.textPlaceholder,
3845
6663
  id: "an-link-text",
3846
6664
  name: "linkText",
3847
6665
  autocomplete: "off"
@@ -3855,18 +6673,18 @@
3855
6673
  });
3856
6674
  this._tabCheckbox = tabCheckbox;
3857
6675
  tabLabel.appendChild(tabCheckbox);
3858
- tabLabel.appendChild(document.createTextNode(" Open in new tab"));
6676
+ tabLabel.appendChild(document.createTextNode(" " + L.openInNewTab));
3859
6677
  const btnRow = createElement("div", { class: "an-dialog-actions" });
3860
6678
  const insertBtn = createElement("button", {
3861
6679
  type: "button",
3862
6680
  class: "an-btn an-btn-primary"
3863
6681
  });
3864
- insertBtn.textContent = "Insert";
6682
+ insertBtn.textContent = L.insertBtn;
3865
6683
  const cancelBtn = createElement("button", {
3866
6684
  type: "button",
3867
6685
  class: "an-btn"
3868
6686
  });
3869
- cancelBtn.textContent = "Cancel";
6687
+ cancelBtn.textContent = L.cancelBtn;
3870
6688
  btnRow.appendChild(insertBtn);
3871
6689
  btnRow.appendChild(cancelBtn);
3872
6690
  box.append(title, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
@@ -3992,53 +6810,54 @@
3992
6810
  this._open();
3993
6811
  }
3994
6812
  _buildDialog() {
6813
+ const L = this.context.locale.imageDialog;
3995
6814
  const overlay = createElement("div", {
3996
6815
  class: "an-dialog-overlay",
3997
6816
  role: "dialog",
3998
6817
  "aria-modal": "true",
3999
- "aria-label": "Insert image"
6818
+ "aria-label": L.ariaLabel
4000
6819
  });
4001
6820
  const box = createElement("div", { class: "an-dialog-box" });
4002
6821
  const title = createElement("h3", { class: "an-dialog-title" });
4003
- title.textContent = "Insert Image";
6822
+ title.textContent = L.title;
4004
6823
  const urlLabel = createElement("label", { class: "an-label" });
4005
- urlLabel.textContent = "Image URL";
6824
+ urlLabel.textContent = L.imageUrl;
4006
6825
  const urlInput = createElement("input", {
4007
6826
  type: "url",
4008
6827
  class: "an-input",
4009
- placeholder: "https://example.com/image.png",
6828
+ placeholder: L.urlPlaceholder,
4010
6829
  autocomplete: "off"
4011
6830
  });
4012
6831
  this._urlInput = urlInput;
4013
6832
  const altLabel = createElement("label", { class: "an-label" });
4014
- altLabel.textContent = "Alt Text";
6833
+ altLabel.textContent = L.altText;
4015
6834
  const altInput = createElement("input", {
4016
6835
  type: "text",
4017
6836
  class: "an-input",
4018
- placeholder: "Describe the image",
6837
+ placeholder: L.altPlaceholder,
4019
6838
  autocomplete: "off"
4020
6839
  });
4021
6840
  this._altInput = altInput;
4022
6841
  box.append(title, urlLabel, urlInput, altLabel, altInput);
4023
6842
  const alignLabel = createElement("label", { class: "an-label" });
4024
- alignLabel.textContent = "Alignment";
6843
+ alignLabel.textContent = L.alignment;
4025
6844
  const alignRow = createElement("div", { class: "an-align-row" });
4026
6845
  [
4027
6846
  {
4028
6847
  value: "",
4029
- label: "None"
6848
+ label: L.alignNone
4030
6849
  },
4031
6850
  {
4032
6851
  value: "left",
4033
- label: "Left"
6852
+ label: L.alignLeft
4034
6853
  },
4035
6854
  {
4036
6855
  value: "center",
4037
- label: "Center"
6856
+ label: L.alignCenter
4038
6857
  },
4039
6858
  {
4040
6859
  value: "right",
4041
- label: "Right"
6860
+ label: L.alignRight
4042
6861
  }
4043
6862
  ].forEach(({ value, label }) => {
4044
6863
  const radioId = `an-align-${value || "none"}`;
@@ -4060,11 +6879,11 @@
4060
6879
  box.append(alignLabel, alignRow);
4061
6880
  if (this.options.allowImageUpload !== false) {
4062
6881
  const fileLabel = createElement("label", { class: "an-label" });
4063
- fileLabel.textContent = "Or upload a file";
6882
+ fileLabel.textContent = L.uploadLabel;
4064
6883
  const fileInput = createElement("input", {
4065
6884
  type: "file",
4066
6885
  class: "an-input",
4067
- accept: "image/*"
6886
+ accept: "image/jpeg,image/png,image/gif,image/webp,image/svg+xml,image/avif"
4068
6887
  });
4069
6888
  this._fileInput = fileInput;
4070
6889
  const fileHint = createElement("p", { class: "an-dialog-hint" });
@@ -4078,12 +6897,12 @@
4078
6897
  type: "button",
4079
6898
  class: "an-btn an-btn-primary"
4080
6899
  });
4081
- insertBtn.textContent = "Insert";
6900
+ insertBtn.textContent = L.insertBtn;
4082
6901
  const cancelBtn = createElement("button", {
4083
6902
  type: "button",
4084
6903
  class: "an-btn"
4085
6904
  });
4086
- cancelBtn.textContent = "Cancel";
6905
+ cancelBtn.textContent = L.cancelBtn;
4087
6906
  btnRow.appendChild(insertBtn);
4088
6907
  btnRow.appendChild(cancelBtn);
4089
6908
  box.append(btnRow);
@@ -4111,14 +6930,15 @@
4111
6930
  _onFileChange() {
4112
6931
  const file = this._fileInput && this._fileInput.files && this._fileInput.files[0];
4113
6932
  if (!file || !file.type.startsWith("image/")) return;
4114
- if ([
4115
- "image/tiff",
4116
- "image/x-tiff",
4117
- "image/bmp",
4118
- "image/x-bmp",
4119
- "image/x-ms-bmp"
4120
- ].includes(file.type)) {
4121
- const message = `Format "${file.type}" is not supported for display in web browsers. Please convert to PNG, JPEG, or WebP first.`;
6933
+ if (!new Set([
6934
+ "image/jpeg",
6935
+ "image/png",
6936
+ "image/gif",
6937
+ "image/webp",
6938
+ "image/svg+xml",
6939
+ "image/avif"
6940
+ ]).has(file.type)) {
6941
+ const message = this.context.locale.errors.imageFormat(file.type);
4122
6942
  if (this._fileHint) this._fileHint.textContent = message;
4123
6943
  this.context.triggerEvent("imageError", {
4124
6944
  file,
@@ -4130,7 +6950,7 @@
4130
6950
  if (this._fileHint) this._fileHint.textContent = "";
4131
6951
  const maxSize = (this.options.maxImageSize || 5) * 1024 * 1024;
4132
6952
  if (file.size > maxSize) {
4133
- const message = `Image file is too large. Maximum allowed size is ${this.options.maxImageSize || 5} MB.`;
6953
+ const message = this.context.locale.errors.imageSize(this.options.maxImageSize || 5);
4134
6954
  if (this._fileHint) this._fileHint.textContent = message;
4135
6955
  console.warn("[AutumnNote] ImageDialog:", message);
4136
6956
  this.context.triggerEvent("imageError", {
@@ -4216,28 +7036,29 @@
4216
7036
  this._open();
4217
7037
  }
4218
7038
  _buildDialog() {
7039
+ const L = this.context.locale.videoDialog;
4219
7040
  const overlay = createElement("div", {
4220
7041
  class: "an-dialog-overlay",
4221
7042
  role: "dialog",
4222
7043
  "aria-modal": "true",
4223
- "aria-label": "Insert video"
7044
+ "aria-label": L.ariaLabel
4224
7045
  });
4225
7046
  const box = createElement("div", { class: "an-dialog-box" });
4226
7047
  const title = createElement("h3", { class: "an-dialog-title" });
4227
- title.textContent = "Insert Video";
7048
+ title.textContent = L.title;
4228
7049
  const urlLabel = createElement("label", { class: "an-label" });
4229
- urlLabel.textContent = "Video URL";
7050
+ urlLabel.textContent = L.videoUrl;
4230
7051
  const urlInput = createElement("input", {
4231
7052
  type: "url",
4232
7053
  class: "an-input",
4233
- placeholder: "YouTube, Vimeo, or direct .mp4 URL",
7054
+ placeholder: L.urlPlaceholder,
4234
7055
  autocomplete: "off"
4235
7056
  });
4236
7057
  this._urlInput = urlInput;
4237
7058
  const hintEl = createElement("p", { class: "an-dialog-hint" });
4238
7059
  this._hintEl = hintEl;
4239
7060
  const widthLabel = createElement("label", { class: "an-label" });
4240
- widthLabel.textContent = "Width (px)";
7061
+ widthLabel.textContent = L.widthLabel;
4241
7062
  const widthInput = createElement("input", {
4242
7063
  type: "number",
4243
7064
  class: "an-input",
@@ -4252,19 +7073,19 @@
4252
7073
  type: "button",
4253
7074
  class: "an-btn an-btn-primary"
4254
7075
  });
4255
- insertBtn.textContent = "Insert";
7076
+ insertBtn.textContent = L.insertBtn;
4256
7077
  const cancelBtn = createElement("button", {
4257
7078
  type: "button",
4258
7079
  class: "an-btn"
4259
7080
  });
4260
- cancelBtn.textContent = "Cancel";
7081
+ cancelBtn.textContent = L.cancelBtn;
4261
7082
  btnRow.appendChild(insertBtn);
4262
7083
  btnRow.appendChild(cancelBtn);
4263
7084
  box.append(title, urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
4264
7085
  overlay.appendChild(box);
4265
7086
  const d0 = on(urlInput, "input", () => {
4266
7087
  const info = this._parseVideoUrl(urlInput.value.trim());
4267
- hintEl.textContent = info ? `Detected: ${info.type}` : urlInput.value ? "Unknown format — will try direct video embed" : "";
7088
+ hintEl.textContent = info ? this.context.locale.videoDialog.detected(info.type) : urlInput.value ? this.context.locale.videoDialog.unknownFormat : "";
4268
7089
  });
4269
7090
  const d1 = on(insertBtn, "click", () => this._onInsert());
4270
7091
  const d2 = on(cancelBtn, "click", () => this._close());
@@ -4289,7 +7110,7 @@
4289
7110
  }
4290
7111
  const html = this._buildEmbedHtml(rawUrl, width);
4291
7112
  if (!html) {
4292
- this._hintEl.textContent = "Invalid URL — please enter a valid video link.";
7113
+ this._hintEl.textContent = this.context.locale.videoDialog.invalidUrl;
4293
7114
  this._urlInput.focus();
4294
7115
  return;
4295
7116
  }
@@ -4861,19 +7682,20 @@
4861
7682
  this._el = null;
4862
7683
  }
4863
7684
  _buildTooltip() {
7685
+ const L = this.context.locale.tooltips.link;
4864
7686
  const el = createElement("div", {
4865
7687
  class: "an-link-tooltip",
4866
7688
  role: "toolbar",
4867
- "aria-label": "Link actions"
7689
+ "aria-label": L.ariaLabel
4868
7690
  });
4869
7691
  el.style.display = "none";
4870
7692
  this._urlLabel = createElement("span", { class: "an-link-tooltip-url" });
4871
7693
  el.appendChild(this._urlLabel);
4872
7694
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
4873
- this._openBtn = this._makeBtn(ICONS$5.open, "Open link", () => this._openLink());
4874
- this._copyBtn = this._makeBtn(ICONS$5.copy, "Copy URL", () => this._copyLink());
4875
- this._editBtn = this._makeBtn(ICONS$5.edit, "Edit link", () => this._editLink());
4876
- this._unlinkBtn = this._makeBtn(ICONS$5.unlink, "Remove link", () => this._unlink());
7695
+ this._openBtn = this._makeBtn(ICONS$5.open, L.openLink, () => this._openLink());
7696
+ this._copyBtn = this._makeBtn(ICONS$5.copy, L.copyUrl, () => this._copyLink());
7697
+ this._editBtn = this._makeBtn(ICONS$5.edit, L.editLink, () => this._editLink());
7698
+ this._unlinkBtn = this._makeBtn(ICONS$5.unlink, L.removeLink, () => this._unlink());
4877
7699
  el.appendChild(this._openBtn);
4878
7700
  el.appendChild(this._copyBtn);
4879
7701
  el.appendChild(this._editBtn);
@@ -5052,38 +7874,39 @@
5052
7874
  this._el = null;
5053
7875
  }
5054
7876
  _buildTooltip() {
7877
+ const L = this.context.locale.tooltips.image;
5055
7878
  const el = createElement("div", {
5056
7879
  class: "an-link-tooltip an-image-tooltip",
5057
7880
  role: "toolbar",
5058
- "aria-label": "Image actions"
7881
+ "aria-label": L.ariaLabel
5059
7882
  });
5060
7883
  el.style.display = "none";
5061
7884
  this._label = createElement("span", { class: "an-link-tooltip-url" });
5062
- this._label.textContent = "Image";
7885
+ this._label.textContent = L.label;
5063
7886
  el.appendChild(this._label);
5064
7887
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5065
- this._floatLeftBtn = this._makeBtn(ICONS$4.floatLeft, "Float Left", () => this._setFloat("left"));
5066
- this._floatNoneBtn = this._makeBtn(ICONS$4.floatNone, "No Float", () => this._setFloat(""));
5067
- this._alignCenterBtn = this._makeBtn(ICONS$4.alignCenter, "Align Center", () => this._setCenter());
5068
- this._floatRightBtn = this._makeBtn(ICONS$4.floatRight, "Float Right", () => this._setFloat("right"));
7888
+ this._floatLeftBtn = this._makeBtn(ICONS$4.floatLeft, L.floatLeft, () => this._setFloat("left"));
7889
+ this._floatNoneBtn = this._makeBtn(ICONS$4.floatNone, L.noFloat, () => this._setFloat(""));
7890
+ this._alignCenterBtn = this._makeBtn(ICONS$4.alignCenter, L.alignCenter, () => this._setCenter());
7891
+ this._floatRightBtn = this._makeBtn(ICONS$4.floatRight, L.floatRight, () => this._setFloat("right"));
5069
7892
  el.appendChild(this._floatLeftBtn);
5070
7893
  el.appendChild(this._floatNoneBtn);
5071
7894
  el.appendChild(this._alignCenterBtn);
5072
7895
  el.appendChild(this._floatRightBtn);
5073
7896
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5074
- this._originalBtn = this._makeBtn(ICONS$4.originalSize, "Original Size", () => this._resetSize());
7897
+ this._originalBtn = this._makeBtn(ICONS$4.originalSize, L.originalSize, () => this._resetSize());
5075
7898
  el.appendChild(this._originalBtn);
5076
7899
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5077
- el.appendChild(this._makeBtn(ICONS$4.rotateLeft, "Rotate Left", () => this._rotate(-90)));
5078
- el.appendChild(this._makeBtn(ICONS$4.rotateRight, "Rotate Right", () => this._rotate(90)));
7900
+ el.appendChild(this._makeBtn(ICONS$4.rotateLeft, L.rotateLeft, () => this._rotate(-90)));
7901
+ el.appendChild(this._makeBtn(ICONS$4.rotateRight, L.rotateRight, () => this._rotate(90)));
5079
7902
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5080
- this._cropBtn = this._makeBtn(ICONS$4.crop, "Crop Image", () => this._crop());
7903
+ this._cropBtn = this._makeBtn(ICONS$4.crop, L.cropImage, () => this._crop());
5081
7904
  el.appendChild(this._cropBtn);
5082
7905
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5083
- this._captionBtn = this._makeBtn(ICONS$4.caption, "Add / Edit Caption", () => this._toggleCaption());
7906
+ this._captionBtn = this._makeBtn(ICONS$4.caption, L.addCaption, () => this._toggleCaption());
5084
7907
  el.appendChild(this._captionBtn);
5085
7908
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5086
- this._deleteBtn = this._makeBtn(ICONS$4.deleteImg, "Delete Image", () => this._delete(), true);
7909
+ this._deleteBtn = this._makeBtn(ICONS$4.deleteImg, L.deleteImage, () => this._delete(), true);
5087
7910
  el.appendChild(this._deleteBtn);
5088
7911
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => this._scheduleHide()));
5089
7912
  return el;
@@ -5335,32 +8158,33 @@
5335
8158
  this._el = null;
5336
8159
  }
5337
8160
  _buildTooltip() {
8161
+ const L = this.context.locale.tooltips.video;
5338
8162
  const el = createElement("div", {
5339
8163
  class: "an-link-tooltip an-video-tooltip",
5340
8164
  role: "toolbar",
5341
- "aria-label": "Video actions"
8165
+ "aria-label": L.ariaLabel
5342
8166
  });
5343
8167
  el.style.display = "none";
5344
8168
  this._label = createElement("span", { class: "an-link-tooltip-url" });
5345
- this._label.textContent = "Video";
8169
+ this._label.textContent = L.label;
5346
8170
  el.appendChild(this._label);
5347
8171
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5348
- this._floatLeftBtn = this._makeBtn(ICONS$3.floatLeft, "Float Left", () => this._setFloat("left"));
5349
- this._floatNoneBtn = this._makeBtn(ICONS$3.floatNone, "No Float", () => this._setFloat(""));
5350
- this._alignCenterBtn = this._makeBtn(ICONS$3.alignCenter, "Align Center", () => this._setCenter());
5351
- this._floatRightBtn = this._makeBtn(ICONS$3.floatRight, "Float Right", () => this._setFloat("right"));
8172
+ this._floatLeftBtn = this._makeBtn(ICONS$3.floatLeft, L.floatLeft, () => this._setFloat("left"));
8173
+ this._floatNoneBtn = this._makeBtn(ICONS$3.floatNone, L.noFloat, () => this._setFloat(""));
8174
+ this._alignCenterBtn = this._makeBtn(ICONS$3.alignCenter, L.alignCenter, () => this._setCenter());
8175
+ this._floatRightBtn = this._makeBtn(ICONS$3.floatRight, L.floatRight, () => this._setFloat("right"));
5352
8176
  el.appendChild(this._floatLeftBtn);
5353
8177
  el.appendChild(this._floatNoneBtn);
5354
8178
  el.appendChild(this._alignCenterBtn);
5355
8179
  el.appendChild(this._floatRightBtn);
5356
8180
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5357
- this._originalBtn = this._makeBtn(ICONS$3.originalSize, "Original Size", () => this._resetSize());
8181
+ this._originalBtn = this._makeBtn(ICONS$3.originalSize, L.originalSize, () => this._resetSize());
5358
8182
  el.appendChild(this._originalBtn);
5359
8183
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5360
- this._previewBtn = this._makeBtn(ICONS$3.preview, "Preview Video", () => this._togglePreview());
8184
+ this._previewBtn = this._makeBtn(ICONS$3.preview, L.previewVideo, () => this._togglePreview());
5361
8185
  el.appendChild(this._previewBtn);
5362
8186
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5363
- this._deleteBtn = this._makeBtn(ICONS$3.deleteVideo, "Delete Video", () => this._delete(), true);
8187
+ this._deleteBtn = this._makeBtn(ICONS$3.deleteVideo, L.deleteVideo, () => this._delete(), true);
5364
8188
  el.appendChild(this._deleteBtn);
5365
8189
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => this._scheduleHide()));
5366
8190
  return el;
@@ -5497,7 +8321,7 @@
5497
8321
  if (shield) shield.style.display = "none";
5498
8322
  this.context.invoke("videoResizer.deselect");
5499
8323
  this._previewBtn.classList.add("an-link-tooltip-btn--copied");
5500
- this._previewBtn.title = "Exit Preview";
8324
+ this._previewBtn.title = this.context.locale.tooltips.video.exitPreview;
5501
8325
  this._previewClickOff = (e) => {
5502
8326
  if (!wrapper.contains(e.target) && !this._el.contains(e.target)) this._exitPreview();
5503
8327
  };
@@ -5511,7 +8335,7 @@
5511
8335
  if (shield) shield.style.display = "";
5512
8336
  }
5513
8337
  this._previewBtn.classList.remove("an-link-tooltip-btn--copied");
5514
- this._previewBtn.title = "Preview Video";
8338
+ this._previewBtn.title = this.context.locale.tooltips.video.previewVideo;
5515
8339
  if (this._previewClickOff) {
5516
8340
  document.removeEventListener("mousedown", this._previewClickOff, true);
5517
8341
  this._previewClickOff = null;
@@ -5572,6 +8396,47 @@
5572
8396
  }
5573
8397
  return null;
5574
8398
  }
8399
+ /**
8400
+ * Build a 2D grid map of the table, accounting for both rowspan and colspan.
8401
+ *
8402
+ * gridMap[r][c] = the DOM cell occupying visual grid position (r, c).
8403
+ * cellPos = WeakMap: cell → { r, c, rs, cs } (top-left grid origin + span).
8404
+ *
8405
+ * Uses HTMLTableElement.rows which is scoped to the table itself and never
8406
+ * includes rows from nested tables.
8407
+ *
8408
+ * @param {HTMLTableElement} table
8409
+ * @returns {{ gridMap: Object, cellPos: WeakMap }}
8410
+ */
8411
+ function buildGridMap(table) {
8412
+ const rows = Array.from(table.rows);
8413
+ const gridMap = {};
8414
+ const cellPos = /* @__PURE__ */ new WeakMap();
8415
+ rows.forEach((row, r) => {
8416
+ if (!gridMap[r]) gridMap[r] = {};
8417
+ let c = 0;
8418
+ for (const cell of row.cells) {
8419
+ while (gridMap[r][c]) c++;
8420
+ const rs = cell.rowSpan || 1;
8421
+ const cs = cell.colSpan || 1;
8422
+ cellPos.set(cell, {
8423
+ r,
8424
+ c,
8425
+ rs,
8426
+ cs
8427
+ });
8428
+ for (let dr = 0; dr < rs; dr++) {
8429
+ if (!gridMap[r + dr]) gridMap[r + dr] = {};
8430
+ for (let dc = 0; dc < cs; dc++) gridMap[r + dr][c + dc] = cell;
8431
+ }
8432
+ c += cs;
8433
+ }
8434
+ });
8435
+ return {
8436
+ gridMap,
8437
+ cellPos
8438
+ };
8439
+ }
5575
8440
  var ICONS$2 = {
5576
8441
  rowAbove: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="12" x2="21" y2="12"/><path d="M12 3v7"/><path d="M9 7l3-4 3 4"/></svg>`,
5577
8442
  rowBelow: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="12" x2="21" y2="12"/><path d="M12 12v7"/><path d="M9 17l3 4 3-4"/></svg>`,
@@ -5580,10 +8445,12 @@
5580
8445
  colRight: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="12" y1="3" x2="12" y2="21"/><path d="M12 12h9"/><path d="M17 8l4 4-4 4"/></svg>`,
5581
8446
  deleteCol: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="12" y1="3" x2="12" y2="21"/><line x1="15" y1="6" x2="21" y2="12"/><line x1="21" y1="6" x2="15" y2="12"/></svg>`,
5582
8447
  mergeCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="8" height="10" rx="1"/><rect x="14" y="7" width="8" height="10" rx="1"/><path d="M10 12h4"/><path d="M12 10l2 2-2 2"/></svg>`,
8448
+ unmergeCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="1"/><line x1="12" y1="5" x2="12" y2="19" stroke-dasharray="2.5 2"/><line x1="2" y1="12" x2="22" y2="12" stroke-dasharray="2.5 2"/><path d="M9 9 L6 12 L9 15"/><path d="M15 9 L18 12 L15 15"/></svg>`,
5583
8449
  colWidth: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="7" y1="4" x2="7" y2="20"/><line x1="17" y1="4" x2="17" y2="20"/><line x1="7" y1="12" x2="17" y2="12"/><path d="M10 9l-3 3 3 3"/><path d="M14 9l3 3-3 3"/></svg>`,
5584
8450
  rowHeight: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="17" x2="20" y2="17"/><line x1="12" y1="7" x2="12" y2="17"/><path d="M9 10l3-3 3 3"/><path d="M9 14l3 3 3-3"/></svg>`,
5585
8451
  tableBorder: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6" stroke-width="1"/><line x1="3" y1="13" x2="21" y2="13" stroke-width="2"/><line x1="3" y1="20" x2="21" y2="20" stroke-width="3"/></svg>`,
5586
- deleteTable: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><line x1="16" y1="16" x2="22" y2="22" stroke="#ef4444"/><line x1="22" y1="16" x2="16" y2="22" stroke="#ef4444"/></svg>`
8452
+ deleteTable: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><line x1="16" y1="16" x2="22" y2="22" stroke="#ef4444"/><line x1="22" y1="16" x2="16" y2="22" stroke="#ef4444"/></svg>`,
8453
+ selectCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z" fill="currentColor" opacity="0.15"/><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z"/></svg>`
5587
8454
  };
5588
8455
  var TableTooltip = class {
5589
8456
  /** @param {import('../Context.js').Context} context */
@@ -5599,6 +8466,12 @@
5599
8466
  this._sizeApply = null;
5600
8467
  this._sizeTitleEl = null;
5601
8468
  this._sizeInputEl = null;
8469
+ this._selectMode = false;
8470
+ this._selectedCells = [];
8471
+ this._selectStart = null;
8472
+ this._selectDragging = false;
8473
+ this._selectBtn = null;
8474
+ this._editable = null;
5602
8475
  }
5603
8476
  initialize() {
5604
8477
  this._el = this._buildTooltip();
@@ -5606,6 +8479,29 @@
5606
8479
  this._sizePopover = this._buildSizePopover();
5607
8480
  document.body.appendChild(this._sizePopover);
5608
8481
  const editable = this.context.layoutInfo.editable;
8482
+ this._editable = editable;
8483
+ const onSelMousedown = (e) => {
8484
+ if (!this._selectMode) return;
8485
+ const cell = e.target.closest("td, th");
8486
+ if (!cell || !editable.contains(cell)) return;
8487
+ if (cell.style.cursor === "col-resize" || cell.style.cursor === "row-resize") return;
8488
+ e.preventDefault();
8489
+ this._activeTable = cell.closest("table");
8490
+ this._selectStart = cell;
8491
+ this._selectDragging = true;
8492
+ this._setSelection([cell]);
8493
+ };
8494
+ const onSelMousemove = (e) => {
8495
+ if (!this._selectMode || !this._selectDragging || !this._selectStart) return;
8496
+ const cell = e.target.closest("td, th");
8497
+ if (!cell || !editable.contains(cell)) return;
8498
+ if (cell.closest("table") !== this._activeTable) return;
8499
+ this._setSelection(this._getRectCells(this._selectStart, cell));
8500
+ };
8501
+ const onSelMouseup = () => {
8502
+ this._selectDragging = false;
8503
+ };
8504
+ this._disposers.push(on(editable, "mousedown", onSelMousedown), on(editable, "mousemove", onSelMousemove), on(document, "mouseup", onSelMouseup));
5609
8505
  this._disposers.push(on(editable, "mouseover", (e) => {
5610
8506
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
5611
8507
  const table = e.target.closest("table");
@@ -5615,9 +8511,11 @@
5615
8511
  this._scheduleShow(table);
5616
8512
  }
5617
8513
  }, { passive: true }), on(editable, "mouseout", (e) => {
8514
+ if (this._selectMode) return;
5618
8515
  const to = e.relatedTarget;
5619
8516
  if (!to || !editable.contains(to) && !this._el.contains(to) && !(this._sizePopover && this._sizePopover.contains(to))) this._scheduleHide();
5620
8517
  }, { passive: true }), on(document, "click", (e) => {
8518
+ if (this._selectMode && this._activeTable && this._activeTable.contains(e.target)) return;
5621
8519
  if (this._activeTable && !this._activeTable.contains(e.target) && !this._el.contains(e.target) && !(this._sizePopover && this._sizePopover.contains(e.target))) this._hide();
5622
8520
  }));
5623
8521
  this._initResize();
@@ -5690,7 +8588,7 @@
5690
8588
  if (_edge === "col") {
5691
8589
  _startW = _nearCell.offsetWidth;
5692
8590
  _colIdx = getVisualColIndex(_nearCell);
5693
- _colCells = _colIdx >= 0 ? Array.from(_table.querySelectorAll("tr")).map((r) => getCellAtVisualCol(r, _colIdx)).filter(Boolean) : [];
8591
+ _colCells = _colIdx >= 0 ? Array.from(_table.querySelectorAll("tr")).map((r) => getCellAtVisualCol(r, _colIdx)).filter(Boolean).filter((c) => (c.colSpan || 1) === 1) : [];
5694
8592
  document.body.style.cursor = "col-resize";
5695
8593
  } else {
5696
8594
  _row = _nearCell.closest("tr");
@@ -5751,32 +8649,38 @@
5751
8649
  this._sizePopover = null;
5752
8650
  }
5753
8651
  _buildTooltip() {
8652
+ const L = this.context.locale.tooltips.table;
5754
8653
  const el = createElement("div", {
5755
8654
  class: "an-link-tooltip an-table-tooltip",
5756
8655
  role: "toolbar",
5757
- "aria-label": "Table actions"
8656
+ "aria-label": L.ariaLabel
5758
8657
  });
5759
8658
  el.style.display = "none";
5760
8659
  this._label = createElement("span", { class: "an-link-tooltip-url" });
5761
- this._label.textContent = "Table";
8660
+ this._label.textContent = L.label;
5762
8661
  el.appendChild(this._label);
5763
8662
  el.appendChild(this._sep());
5764
- el.appendChild(this._makeBtn(ICONS$2.rowAbove, "Add Row Above", () => this._addRow("above")));
5765
- el.appendChild(this._makeBtn(ICONS$2.rowBelow, "Add Row Below", () => this._addRow("below")));
5766
- el.appendChild(this._makeBtn(ICONS$2.deleteRow, "Delete Row", () => this._deleteRow()));
8663
+ this._selectBtn = this._makeBtn(ICONS$2.selectCells, L.selectCells, () => this._toggleSelectMode());
8664
+ el.appendChild(this._selectBtn);
8665
+ el.appendChild(this._sep());
8666
+ el.appendChild(this._makeBtn(ICONS$2.rowAbove, L.addRowAbove, () => this._addRow("above")));
8667
+ el.appendChild(this._makeBtn(ICONS$2.rowBelow, L.addRowBelow, () => this._addRow("below")));
8668
+ el.appendChild(this._makeBtn(ICONS$2.deleteRow, L.deleteRow, () => this._deleteRow()));
5767
8669
  el.appendChild(this._sep());
5768
- el.appendChild(this._makeBtn(ICONS$2.colLeft, "Add Column Left", () => this._addColumn("left")));
5769
- el.appendChild(this._makeBtn(ICONS$2.colRight, "Add Column Right", () => this._addColumn("right")));
5770
- el.appendChild(this._makeBtn(ICONS$2.deleteCol, "Delete Column", () => this._deleteColumn()));
8670
+ el.appendChild(this._makeBtn(ICONS$2.colLeft, L.addColumnLeft, () => this._addColumn("left")));
8671
+ el.appendChild(this._makeBtn(ICONS$2.colRight, L.addColumnRight, () => this._addColumn("right")));
8672
+ el.appendChild(this._makeBtn(ICONS$2.deleteCol, L.deleteColumn, () => this._deleteColumn()));
5771
8673
  el.appendChild(this._sep());
5772
- el.appendChild(this._makeBtn(ICONS$2.mergeCells, "Merge Cells", () => this._mergeCells()));
8674
+ el.appendChild(this._makeBtn(ICONS$2.mergeCells, L.mergeCells, () => this._mergeCells()));
8675
+ el.appendChild(this._makeBtn(ICONS$2.unmergeCells, L.unmergeCells, () => this._unmergeCells()));
5773
8676
  el.appendChild(this._sep());
5774
- el.appendChild(this._makeBtn(ICONS$2.colWidth, "Column Width", () => this._openSizePopover("col")));
5775
- el.appendChild(this._makeBtn(ICONS$2.rowHeight, "Row Height", () => this._openSizePopover("row")));
5776
- el.appendChild(this._makeBtn(ICONS$2.tableBorder, "Table Border Width", () => this._openSizePopover("border")));
8677
+ el.appendChild(this._makeBtn(ICONS$2.colWidth, L.columnWidth, () => this._openSizePopover("col")));
8678
+ el.appendChild(this._makeBtn(ICONS$2.rowHeight, L.rowHeight, () => this._openSizePopover("row")));
8679
+ el.appendChild(this._makeBtn(ICONS$2.tableBorder, L.tableBorderWidth, () => this._openSizePopover("border")));
5777
8680
  el.appendChild(this._sep());
5778
- el.appendChild(this._makeBtn(ICONS$2.deleteTable, "Delete Table", () => this._deleteTable(), true));
8681
+ el.appendChild(this._makeBtn(ICONS$2.deleteTable, L.deleteTable, () => this._deleteTable(), true));
5779
8682
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => {
8683
+ if (this._selectMode) return;
5780
8684
  if (this._sizePopover && this._sizePopover.style.display !== "none") return;
5781
8685
  this._scheduleHide();
5782
8686
  }));
@@ -5832,6 +8736,12 @@
5832
8736
  this._el.style.display = "none";
5833
8737
  this._activeTable = null;
5834
8738
  this._activeCell = null;
8739
+ if (this._selectMode) {
8740
+ this._selectMode = false;
8741
+ if (this._selectBtn) this._selectBtn.classList.remove("an-link-tooltip-btn--active");
8742
+ if (this._editable) this._editable.classList.remove("an-table-select-mode");
8743
+ }
8744
+ this._clearSelection();
5835
8745
  this._clearTimers();
5836
8746
  this._hideSizePopover();
5837
8747
  }
@@ -5865,14 +8775,111 @@
5865
8775
  }
5866
8776
  return this._activeCell || this._activeTable && this._activeTable.querySelector("td, th");
5867
8777
  }
8778
+ _toggleSelectMode() {
8779
+ this._selectMode = !this._selectMode;
8780
+ if (this._selectBtn) this._selectBtn.classList.toggle("an-link-tooltip-btn--active", this._selectMode);
8781
+ if (this._editable) this._editable.classList.toggle("an-table-select-mode", this._selectMode);
8782
+ if (!this._selectMode) this._clearSelection();
8783
+ }
8784
+ _clearSelection() {
8785
+ this._selectedCells.forEach((c) => c.classList.remove("an-cell-selected"));
8786
+ this._selectedCells = [];
8787
+ this._selectStart = null;
8788
+ }
8789
+ _setSelection(cells) {
8790
+ this._selectedCells.forEach((c) => {
8791
+ if (!cells.includes(c)) c.classList.remove("an-cell-selected");
8792
+ });
8793
+ this._selectedCells = cells;
8794
+ cells.forEach((c) => c.classList.add("an-cell-selected"));
8795
+ }
8796
+ /**
8797
+ * Returns all cells in the rectangular area between startCell and endCell,
8798
+ * correctly handling rowspan/colspan by using the grid map.
8799
+ * The rect is expanded iteratively until it is stable — this ensures any
8800
+ * merged cell that starts outside the initial rect but spans into it is
8801
+ * fully included.
8802
+ */
8803
+ _getRectCells(startCell, endCell) {
8804
+ if (!startCell) return [];
8805
+ if (!endCell || startCell === endCell) return [startCell];
8806
+ const table = startCell.closest("table");
8807
+ if (!table || !table.contains(endCell)) return [startCell];
8808
+ const { gridMap, cellPos } = buildGridMap(table);
8809
+ const sp = cellPos.get(startCell);
8810
+ const ep = cellPos.get(endCell);
8811
+ if (!sp || !ep) return [startCell];
8812
+ let minR = Math.min(sp.r, ep.r);
8813
+ let maxR = Math.max(sp.r + sp.rs - 1, ep.r + ep.rs - 1);
8814
+ let minC = Math.min(sp.c, ep.c);
8815
+ let maxC = Math.max(sp.c + sp.cs - 1, ep.c + ep.cs - 1);
8816
+ let changed = true;
8817
+ while (changed) {
8818
+ changed = false;
8819
+ for (let r = minR; r <= maxR; r++) {
8820
+ const rowMap = gridMap[r];
8821
+ if (!rowMap) continue;
8822
+ for (let c = minC; c <= maxC; c++) {
8823
+ const cell = rowMap[c];
8824
+ if (!cell) continue;
8825
+ const pos = cellPos.get(cell);
8826
+ if (!pos) continue;
8827
+ if (pos.r < minR) {
8828
+ minR = pos.r;
8829
+ changed = true;
8830
+ }
8831
+ if (pos.r + pos.rs - 1 > maxR) {
8832
+ maxR = pos.r + pos.rs - 1;
8833
+ changed = true;
8834
+ }
8835
+ if (pos.c < minC) {
8836
+ minC = pos.c;
8837
+ changed = true;
8838
+ }
8839
+ if (pos.c + pos.cs - 1 > maxC) {
8840
+ maxC = pos.c + pos.cs - 1;
8841
+ changed = true;
8842
+ }
8843
+ }
8844
+ }
8845
+ }
8846
+ const seen = /* @__PURE__ */ new Set();
8847
+ const result = [];
8848
+ for (let r = minR; r <= maxR; r++) {
8849
+ const rowMap = gridMap[r];
8850
+ if (!rowMap) continue;
8851
+ for (let c = minC; c <= maxC; c++) {
8852
+ const cell = rowMap[c];
8853
+ if (cell && !seen.has(cell)) {
8854
+ seen.add(cell);
8855
+ result.push(cell);
8856
+ }
8857
+ }
8858
+ }
8859
+ return result.length > 0 ? result : [startCell];
8860
+ }
8861
+ /**
8862
+ * Returns the active cell set: user-selected cells when available,
8863
+ * otherwise the single active/cursor cell.
8864
+ * @returns {HTMLTableCellElement[]}
8865
+ */
8866
+ _getSelectedCells() {
8867
+ return this._selectedCells.length > 0 ? this._selectedCells : [this._getCell()].filter(Boolean);
8868
+ }
5868
8869
  _addRow(position) {
5869
- const cell = this._getCell();
5870
- if (!cell) return;
5871
- const row = cell.closest("tr");
5872
- if (!row) return;
5873
- const colCount = Array.from(row.cells).reduce((sum, c) => sum + (c.colSpan || 1), 0);
8870
+ const cells = this._getSelectedCells();
8871
+ if (!cells.length) return;
8872
+ const table = cells[0].closest("table");
8873
+ if (!table) return;
8874
+ const allRows = Array.from(table.querySelectorAll("tr"));
8875
+ const refRow = [...new Set(cells.map((c) => c.closest("tr")).filter(Boolean))].reduce((best, r) => {
8876
+ const bi = allRows.indexOf(best);
8877
+ const ri = allRows.indexOf(r);
8878
+ return position === "above" ? ri < bi ? r : best : ri > bi ? r : best;
8879
+ });
8880
+ const colCount = Array.from(refRow.cells).reduce((sum, c) => sum + (c.colSpan || 1), 0);
5874
8881
  const newRow = document.createElement("tr");
5875
- const refCells = Array.from(row.cells);
8882
+ const refCells = Array.from(refRow.cells);
5876
8883
  for (let i = 0; i < colCount; i++) {
5877
8884
  const td = createElement("td", {}, ["\xA0"]);
5878
8885
  const ref = refCells[i];
@@ -5880,19 +8887,20 @@
5880
8887
  if (ref && ref.style.minWidth) td.style.minWidth = ref.style.minWidth;
5881
8888
  newRow.appendChild(td);
5882
8889
  }
5883
- if (position === "above") row.parentElement?.insertBefore(newRow, row);
5884
- else row.insertAdjacentElement("afterend", newRow);
8890
+ if (position === "above") refRow.parentElement?.insertBefore(newRow, refRow);
8891
+ else refRow.insertAdjacentElement("afterend", newRow);
5885
8892
  requestAnimationFrame(() => this._positionNear(this._activeTable));
5886
8893
  this.context.invoke("editor.afterCommand");
5887
8894
  }
5888
8895
  _addColumn(position) {
5889
- const cell = this._getCell();
5890
- if (!cell) return;
5891
- const table = cell.closest("table");
8896
+ const cells = this._getSelectedCells();
8897
+ if (!cells.length) return;
8898
+ const table = cells[0].closest("table");
5892
8899
  if (!table) return;
5893
- const visualColIdx = getVisualColIndex(cell);
8900
+ const colIndices = cells.map((c) => getVisualColIndex(c));
8901
+ const targetColIdx = position === "left" ? Math.min(...colIndices) : Math.max(...colIndices);
5894
8902
  const rows = Array.from(table.querySelectorAll("tr"));
5895
- const refs = rows.map((r) => position === "left" ? getCellAtVisualCol(r, visualColIdx) : getCellAfterVisualCol(r, visualColIdx));
8903
+ const refs = rows.map((r) => position === "left" ? getCellAtVisualCol(r, targetColIdx) : getCellAfterVisualCol(r, targetColIdx));
5896
8904
  const isHeaders = rows.map((r) => r.closest("thead") !== null);
5897
8905
  rows.forEach((r, i) => {
5898
8906
  r.insertBefore(createElement(isHeaders[i] ? "th" : "td", {}, ["\xA0"]), refs[i]);
@@ -5901,68 +8909,93 @@
5901
8909
  this.context.invoke("editor.afterCommand");
5902
8910
  }
5903
8911
  _deleteRow() {
5904
- const cell = this._getCell();
5905
- if (!cell) return;
5906
- const row = cell.closest("tr");
5907
- const table = cell.closest("table");
5908
- if (!row || !table) return;
8912
+ const cells = this._getSelectedCells();
8913
+ if (!cells.length) return;
8914
+ const table = cells[0].closest("table");
8915
+ if (!table) return;
5909
8916
  const tbody = table.querySelector("tbody");
5910
- if ((tbody ? tbody.querySelectorAll("tr").length : table.querySelectorAll("tr").length) <= 1 && row.closest("tbody")) return;
8917
+ const totalBodyRows = tbody ? tbody.querySelectorAll("tr").length : table.querySelectorAll("tr").length;
8918
+ const selectedRows = [...new Set(cells.map((c) => c.closest("tr")).filter(Boolean))];
8919
+ if (selectedRows.filter((r) => r.closest("tbody")).length >= totalBodyRows) return;
5911
8920
  this._activeCell = null;
5912
- row.parentElement?.removeChild(row);
8921
+ this._clearSelection();
8922
+ selectedRows.forEach((r) => r.parentElement?.removeChild(r));
5913
8923
  requestAnimationFrame(() => this._positionNear(this._activeTable));
5914
8924
  this.context.invoke("editor.afterCommand");
5915
8925
  }
5916
8926
  _deleteColumn() {
5917
- const cell = this._getCell();
5918
- if (!cell) return;
5919
- const table = cell.closest("table");
8927
+ const cells = this._getSelectedCells();
8928
+ if (!cells.length) return;
8929
+ const table = cells[0].closest("table");
5920
8930
  if (!table) return;
5921
- const row = cell.closest("tr");
5922
- if (row && row.cells.length <= 1) return;
5923
- const visualColIdx = getVisualColIndex(cell);
5924
- this._activeCell = null;
5925
- const rows = Array.from(table.querySelectorAll("tr"));
5926
- rows.map((r) => getCellAtVisualCol(r, visualColIdx)).forEach((c, i) => {
5927
- if (c) rows[i].removeChild(c);
8931
+ const tableRows = Array.from(table.querySelectorAll("tr"));
8932
+ if (tableRows[0] && tableRows[0].cells.length <= 1) return;
8933
+ const colIndices = [...new Set(cells.map((c) => getVisualColIndex(c)))];
8934
+ if (colIndices.length >= (tableRows[0]?.cells.length ?? 1)) return;
8935
+ const cellsToDelete = [];
8936
+ colIndices.forEach((colIdx) => {
8937
+ tableRows.forEach((r) => {
8938
+ const c = getCellAtVisualCol(r, colIdx);
8939
+ if (c) cellsToDelete.push(c);
8940
+ });
5928
8941
  });
8942
+ this._activeCell = null;
8943
+ this._clearSelection();
8944
+ cellsToDelete.forEach((c) => c.parentElement?.removeChild(c));
5929
8945
  requestAnimationFrame(() => this._positionNear(this._activeTable));
5930
8946
  this.context.invoke("editor.afterCommand");
5931
8947
  }
5932
8948
  _mergeCells() {
5933
8949
  const cell = this._getCell();
5934
8950
  if (!cell) return;
5935
- const sel = window.getSelection();
5936
- if (!sel || sel.rangeCount === 0) return;
5937
- const range = sel.getRangeAt(0);
5938
8951
  const table = cell.closest("table");
5939
8952
  if (!table) return;
5940
- const selected = Array.from(table.querySelectorAll("td, th")).filter((c) => {
5941
- try {
5942
- return range.intersectsNode(c);
5943
- } catch {
5944
- return false;
5945
- }
5946
- });
5947
- if (selected.length < 2) return;
5948
- const rows = [...new Set(selected.map((c) => c.closest("tr")))];
5949
- if (rows.length === 1) {
5950
- const row = rows[0];
5951
- const rowSelected = Array.from(row.cells).filter((c) => selected.includes(c));
5952
- if (rowSelected.length < 2) return;
5953
- const first = rowSelected[0];
5954
- first.colSpan = rowSelected.reduce((sum, c) => sum + (c.colSpan || 1), 0);
5955
- first.innerHTML = rowSelected.map((c) => c.innerHTML).join("");
5956
- rowSelected.slice(1).forEach((c) => row.removeChild(c));
5957
- } else {
5958
- if ([...new Set(selected.map((c) => getVisualColIndex(c)))].length !== 1) return;
5959
- const first = selected[0];
5960
- first.rowSpan = selected.reduce((sum, c) => sum + (c.rowSpan || 1), 0);
5961
- first.innerHTML = selected.map((c) => c.innerHTML).join("");
5962
- selected.slice(1).forEach((c) => {
5963
- if (c.closest("tr")) c.closest("tr").removeChild(c);
8953
+ let selected = this._getSelectedCells().filter((c) => table.contains(c));
8954
+ if (selected.length < 2) {
8955
+ const sel = window.getSelection();
8956
+ if (!sel || sel.rangeCount === 0) return;
8957
+ const range = sel.getRangeAt(0);
8958
+ selected = Array.from(table.querySelectorAll("td, th")).filter((c) => {
8959
+ try {
8960
+ return range.intersectsNode(c);
8961
+ } catch {
8962
+ return false;
8963
+ }
5964
8964
  });
8965
+ if (selected.length < 2) return;
8966
+ }
8967
+ const { gridMap, cellPos } = buildGridMap(table);
8968
+ let minR = Infinity, maxR = -Infinity, minC = Infinity, maxC = -Infinity;
8969
+ selected.forEach((c) => {
8970
+ const pos = cellPos.get(c);
8971
+ if (!pos) return;
8972
+ if (pos.r < minR) minR = pos.r;
8973
+ if (pos.r + pos.rs - 1 > maxR) maxR = pos.r + pos.rs - 1;
8974
+ if (pos.c < minC) minC = pos.c;
8975
+ if (pos.c + pos.cs - 1 > maxC) maxC = pos.c + pos.cs - 1;
8976
+ });
8977
+ if (minR === Infinity) return;
8978
+ const seen = /* @__PURE__ */ new Set();
8979
+ const rectCells = [];
8980
+ for (let r = minR; r <= maxR; r++) {
8981
+ const rowMap = gridMap[r];
8982
+ if (!rowMap) continue;
8983
+ for (let c = minC; c <= maxC; c++) {
8984
+ const tc = rowMap[c];
8985
+ if (tc && !seen.has(tc)) {
8986
+ seen.add(tc);
8987
+ rectCells.push(tc);
8988
+ }
8989
+ }
5965
8990
  }
8991
+ if (rectCells.length < 2) return;
8992
+ const first = rectCells[0];
8993
+ first.colSpan = maxC - minC + 1;
8994
+ first.rowSpan = maxR - minR + 1;
8995
+ first.style.verticalAlign = "middle";
8996
+ first.innerHTML = rectCells.map((c) => c.innerHTML).join("");
8997
+ rectCells.slice(1).forEach((c) => c.parentElement?.removeChild(c));
8998
+ this._clearSelection();
5966
8999
  this.context.invoke("editor.afterCommand");
5967
9000
  }
5968
9001
  _deleteTable() {
@@ -5972,6 +9005,57 @@
5972
9005
  if (table.parentNode) table.parentNode.removeChild(table);
5973
9006
  this.context.invoke("editor.afterCommand");
5974
9007
  }
9008
+ _unmergeCells() {
9009
+ const cells = this._getSelectedCells();
9010
+ if (!cells.length) return;
9011
+ const table = cells[0].closest("table");
9012
+ if (!table) return;
9013
+ const mergedCells = cells.filter((c) => table.contains(c) && ((c.colSpan || 1) > 1 || (c.rowSpan || 1) > 1));
9014
+ if (!mergedCells.length) return;
9015
+ mergedCells.forEach((cell) => {
9016
+ if (table.contains(cell)) this._unmergeOne(cell, table);
9017
+ });
9018
+ this._clearSelection();
9019
+ requestAnimationFrame(() => this._positionNear(this._activeTable));
9020
+ this.context.invoke("editor.afterCommand");
9021
+ }
9022
+ /**
9023
+ * Split a single merged cell (colspan/rowspan > 1) back into individual cells.
9024
+ * New cells are empty (&nbsp;); the original cell retains its content.
9025
+ * @param {HTMLTableCellElement} cell
9026
+ * @param {HTMLTableElement} table
9027
+ */
9028
+ _unmergeOne(cell, table) {
9029
+ const cs = cell.colSpan || 1;
9030
+ const rs = cell.rowSpan || 1;
9031
+ if (cs === 1 && rs === 1) return;
9032
+ const { cellPos } = buildGridMap(table);
9033
+ const pos = cellPos.get(cell);
9034
+ if (!pos) return;
9035
+ const { r, c } = pos;
9036
+ const tableRows = Array.from(table.rows);
9037
+ const tag = cell.tagName.toLowerCase();
9038
+ cell.rowSpan = 1;
9039
+ cell.colSpan = 1;
9040
+ cell.style.verticalAlign = "";
9041
+ if (cs > 1) {
9042
+ const insertRef = cell.nextElementSibling;
9043
+ for (let dc = 1; dc < cs; dc++) tableRows[r].insertBefore(createElement(tag, {}, ["\xA0"]), insertRef);
9044
+ }
9045
+ for (let dr = 1; dr < rs; dr++) {
9046
+ const targetRow = tableRows[r + dr];
9047
+ if (!targetRow) continue;
9048
+ let ref = null;
9049
+ for (const tc of targetRow.cells) {
9050
+ const tp = cellPos.get(tc);
9051
+ if (tp && tp.c > c) {
9052
+ ref = tc;
9053
+ break;
9054
+ }
9055
+ }
9056
+ for (let dc = 0; dc < cs; dc++) targetRow.insertBefore(createElement(tag, {}, ["\xA0"]), ref);
9057
+ }
9058
+ }
5975
9059
  _buildSizePopover() {
5976
9060
  const popover = createElement("div", { class: "an-size-popover" });
5977
9061
  popover.style.display = "none";
@@ -5992,12 +9076,12 @@
5992
9076
  type: "button",
5993
9077
  class: "an-btn"
5994
9078
  });
5995
- cancelBtn.textContent = "Cancel";
9079
+ cancelBtn.textContent = this.context.locale.tooltips.table.cancelBtn;
5996
9080
  const applyBtn = createElement("button", {
5997
9081
  type: "button",
5998
9082
  class: "an-btn an-btn-primary"
5999
9083
  });
6000
- applyBtn.textContent = "Apply";
9084
+ applyBtn.textContent = this.context.locale.tooltips.table.applyBtn;
6001
9085
  actionsEl.appendChild(cancelBtn);
6002
9086
  actionsEl.appendChild(applyBtn);
6003
9087
  popover.appendChild(titleEl);
@@ -6035,7 +9119,7 @@
6035
9119
  if (!table) return;
6036
9120
  const firstCell = table.querySelector("td, th");
6037
9121
  const currentPx = firstCell ? parseInt(firstCell.style.borderWidth, 10) || parseInt(window.getComputedStyle(firstCell).borderWidth, 10) || 1 : 1;
6038
- this._sizeTitleEl.textContent = "Table Border Width (px)";
9122
+ this._sizeTitleEl.textContent = this.context.locale.tooltips.table.tableBorderWidthPx;
6039
9123
  this._sizeInputEl.min = "0";
6040
9124
  this._sizeInputEl.max = "10";
6041
9125
  this._sizeInputEl.value = currentPx;
@@ -6053,27 +9137,35 @@
6053
9137
  };
6054
9138
  } else {
6055
9139
  const isCol = type === "col";
6056
- this._sizeTitleEl.textContent = isCol ? "Column Width (px)" : "Row Height (px)";
9140
+ const activeCells = this._getSelectedCells().filter((c) => {
9141
+ const t = c.closest("table");
9142
+ return t && t === cell.closest("table");
9143
+ });
9144
+ this._sizeTitleEl.textContent = isCol ? this.context.locale.tooltips.table.columnWidthPx : this.context.locale.tooltips.table.rowHeightPx;
6057
9145
  this._sizeInputEl.min = "1";
6058
9146
  this._sizeInputEl.max = "2000";
6059
9147
  this._sizeInputEl.value = isCol ? cell.offsetWidth || 120 : cell.closest("tr") ? cell.closest("tr").offsetHeight || 40 : 40;
6060
9148
  this._sizeApply = (val) => {
9149
+ const table = cell.closest("table");
9150
+ if (!table) return;
6061
9151
  if (isCol) {
6062
- const table = cell.closest("table");
6063
- const visualColIdx = getVisualColIndex(cell);
6064
- Array.from(table.querySelectorAll("tr")).map((r) => getCellAtVisualCol(r, visualColIdx)).forEach((c) => {
6065
- if (c) {
6066
- c.style.width = `${val}px`;
6067
- c.style.minWidth = `${val}px`;
6068
- }
9152
+ const colIndices = [...new Set(activeCells.map((c) => getVisualColIndex(c)))];
9153
+ const tableRows = Array.from(table.querySelectorAll("tr"));
9154
+ colIndices.forEach((colIdx) => {
9155
+ tableRows.forEach((r) => {
9156
+ const c = getCellAtVisualCol(r, colIdx);
9157
+ if (c && (c.colSpan || 1) === 1) {
9158
+ c.style.width = `${val}px`;
9159
+ c.style.minWidth = `${val}px`;
9160
+ }
9161
+ });
6069
9162
  });
6070
- } else {
6071
- const row = cell.closest("tr");
6072
- if (row) for (const c of row.cells) {
9163
+ } else [...new Set(activeCells.map((c) => c.closest("tr")).filter(Boolean))].forEach((row) => {
9164
+ for (const c of row.cells) {
6073
9165
  c.style.height = `${val}px`;
6074
9166
  c.style.minHeight = `${val}px`;
6075
9167
  }
6076
- }
9168
+ });
6077
9169
  this.context.invoke("editor.afterCommand");
6078
9170
  };
6079
9171
  }
@@ -6149,20 +9241,21 @@
6149
9241
  this._el = null;
6150
9242
  }
6151
9243
  _buildTooltip() {
9244
+ const L = this.context.locale.tooltips.code;
6152
9245
  const el = createElement("div", {
6153
9246
  class: "an-link-tooltip an-code-tooltip",
6154
9247
  role: "toolbar",
6155
- "aria-label": "Code block actions"
9248
+ "aria-label": L.ariaLabel
6156
9249
  });
6157
9250
  el.style.display = "none";
6158
9251
  this._label = createElement("span", { class: "an-link-tooltip-url" });
6159
- this._label.textContent = "Code";
9252
+ this._label.textContent = L.label;
6160
9253
  el.appendChild(this._label);
6161
9254
  el.appendChild(this._sep());
6162
9255
  this._langSelect = createElement("select", {
6163
9256
  class: "an-code-lang-select",
6164
- title: "Syntax Language",
6165
- "aria-label": "Syntax language"
9257
+ title: L.syntaxLanguage,
9258
+ "aria-label": L.syntaxAriaLabel
6166
9259
  });
6167
9260
  [
6168
9261
  ["", "Plain text"],
@@ -6193,15 +9286,15 @@
6193
9286
  this._disposers.push(on(this._langSelect, "change", () => this._onLangChange()));
6194
9287
  el.appendChild(this._langSelect);
6195
9288
  el.appendChild(this._sep());
6196
- this._copyBtn = this._makeBtn(ICONS$1.copy, "Copy Code", () => this._copyCode());
9289
+ this._copyBtn = this._makeBtn(ICONS$1.copy, L.copyCode, () => this._copyCode());
6197
9290
  el.appendChild(this._copyBtn);
6198
9291
  el.appendChild(this._sep());
6199
- this._wrapBtn = this._makeBtn(ICONS$1.wrapOn, "Toggle Word Wrap", () => this._toggleWrap());
9292
+ this._wrapBtn = this._makeBtn(ICONS$1.wrapOn, L.toggleWordWrap, () => this._toggleWrap());
6200
9293
  el.appendChild(this._wrapBtn);
6201
9294
  el.appendChild(this._sep());
6202
- el.appendChild(this._makeBtn(ICONS$1.toParagraph, "Convert to Paragraph", () => this._toParagraph()));
9295
+ el.appendChild(this._makeBtn(ICONS$1.toParagraph, L.convertToParagraph, () => this._toParagraph()));
6203
9296
  el.appendChild(this._sep());
6204
- el.appendChild(this._makeBtn(ICONS$1.deleteCode, "Delete Code Block", () => this._delete(), true));
9297
+ el.appendChild(this._makeBtn(ICONS$1.deleteCode, L.deleteCodeBlock, () => this._delete(), true));
6205
9298
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => this._scheduleHide()));
6206
9299
  return el;
6207
9300
  }
@@ -6278,7 +9371,7 @@
6278
9371
  if (!this._activePre || !this._wrapBtn) return;
6279
9372
  const wrapped = (this._activePre.style.whiteSpace || "").includes("pre-wrap") || window.getComputedStyle(this._activePre).whiteSpace === "pre-wrap";
6280
9373
  this._wrapBtn.classList.toggle("active", wrapped);
6281
- this._wrapBtn.title = wrapped ? "Disable Word Wrap" : "Enable Word Wrap";
9374
+ this._wrapBtn.title = wrapped ? this.context.locale.tooltips.code.disableWordWrap : this.context.locale.tooltips.code.enableWordWrap;
6282
9375
  }
6283
9376
  _syncLangSelect() {
6284
9377
  if (!this._activePre || !this._langSelect) return;
@@ -8808,27 +11901,28 @@
8808
11901
  this._open();
8809
11902
  }
8810
11903
  _buildDialog() {
11904
+ const L = this.context.locale.emojiDialog;
8811
11905
  const overlay = createElement("div", {
8812
11906
  class: "an-dialog-overlay",
8813
11907
  role: "dialog",
8814
11908
  "aria-modal": "true",
8815
- "aria-label": "Insert emoji"
11909
+ "aria-label": L.ariaLabel
8816
11910
  });
8817
11911
  const box = createElement("div", { class: "an-dialog-box an-emoji-box" });
8818
11912
  const titleRow = createElement("div", { class: "an-icon-title-row" });
8819
11913
  const title = createElement("h3", { class: "an-dialog-title" });
8820
- title.textContent = "Insert Emoji";
11914
+ title.textContent = L.title;
8821
11915
  const closeBtn = createElement("button", {
8822
11916
  type: "button",
8823
11917
  class: "an-icon-close",
8824
- "aria-label": "Close"
11918
+ "aria-label": L.close
8825
11919
  });
8826
11920
  closeBtn.innerHTML = "&times;";
8827
11921
  titleRow.append(title, closeBtn);
8828
11922
  const searchInput = createElement("input", {
8829
11923
  type: "search",
8830
11924
  class: "an-input an-icon-search",
8831
- placeholder: "Search emojis…",
11925
+ placeholder: L.searchPlaceholder,
8832
11926
  autocomplete: "off"
8833
11927
  });
8834
11928
  this._searchInput = searchInput;
@@ -8838,7 +11932,7 @@
8838
11932
  class: "an-icon-cat active",
8839
11933
  "data-cat": "all"
8840
11934
  });
8841
- allTab.textContent = "All";
11935
+ allTab.textContent = L.all;
8842
11936
  catBar.appendChild(allTab);
8843
11937
  EMOJI_CATS.forEach(({ id, label }) => {
8844
11938
  const tab = createElement("button", {
@@ -8846,7 +11940,7 @@
8846
11940
  class: "an-icon-cat",
8847
11941
  "data-cat": id
8848
11942
  });
8849
- tab.textContent = label;
11943
+ tab.textContent = L.categories && L.categories[id] || label;
8850
11944
  catBar.appendChild(tab);
8851
11945
  });
8852
11946
  this._catBar = catBar;
@@ -8869,7 +11963,7 @@
8869
11963
  type: "button",
8870
11964
  class: "an-btn"
8871
11965
  });
8872
- cancelBtn.textContent = "Cancel";
11966
+ cancelBtn.textContent = L.cancelBtn;
8873
11967
  btnRow.appendChild(cancelBtn);
8874
11968
  box.append(titleRow, searchInput, catBar, grid, btnRow);
8875
11969
  overlay.appendChild(box);
@@ -8928,7 +12022,13 @@
8928
12022
  range.selectNodeContents(editable);
8929
12023
  range.collapse(false);
8930
12024
  }
12025
+ const _sc = range.startContainer;
12026
+ const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest?.("td, th");
8931
12027
  range.deleteContents();
12028
+ if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
12029
+ range.setStart(_tdAnchor, 0);
12030
+ range.collapse(true);
12031
+ }
8932
12032
  const textNode = document.createTextNode(char);
8933
12033
  range.insertNode(textNode);
8934
12034
  range.setStartAfter(textNode);
@@ -9274,27 +12374,28 @@
9274
12374
  this._open();
9275
12375
  }
9276
12376
  _buildDialog() {
12377
+ const L = this.context.locale.iconDialog;
9277
12378
  const overlay = createElement("div", {
9278
12379
  class: "an-dialog-overlay",
9279
12380
  role: "dialog",
9280
12381
  "aria-modal": "true",
9281
- "aria-label": "Insert FA icon"
12382
+ "aria-label": L.ariaLabel
9282
12383
  });
9283
12384
  const box = createElement("div", { class: "an-dialog-box an-icon-box" });
9284
12385
  const titleRow = createElement("div", { class: "an-icon-title-row" });
9285
12386
  const title = createElement("h3", { class: "an-dialog-title" });
9286
- title.textContent = "Insert FA Icon";
12387
+ title.textContent = L.title;
9287
12388
  const closeBtn = createElement("button", {
9288
12389
  type: "button",
9289
12390
  class: "an-icon-close",
9290
- "aria-label": "Close"
12391
+ "aria-label": L.close
9291
12392
  });
9292
12393
  closeBtn.innerHTML = "&times;";
9293
12394
  titleRow.append(title, closeBtn);
9294
12395
  const searchInput = createElement("input", {
9295
12396
  type: "search",
9296
12397
  class: "an-input an-icon-search",
9297
- placeholder: "Search icons…",
12398
+ placeholder: L.searchPlaceholder,
9298
12399
  autocomplete: "off"
9299
12400
  });
9300
12401
  this._searchInput = searchInput;
@@ -9304,7 +12405,7 @@
9304
12405
  class: "an-icon-cat active",
9305
12406
  "data-cat": "all"
9306
12407
  });
9307
- allTab.textContent = "All";
12408
+ allTab.textContent = L.all;
9308
12409
  catBar.appendChild(allTab);
9309
12410
  ICON_CATEGORIES.forEach(({ id, label }) => {
9310
12411
  const tab = createElement("button", {
@@ -9312,7 +12413,7 @@
9312
12413
  class: "an-icon-cat",
9313
12414
  "data-cat": id
9314
12415
  });
9315
- tab.textContent = label;
12416
+ tab.textContent = L.categories && L.categories[id] || label;
9316
12417
  catBar.appendChild(tab);
9317
12418
  });
9318
12419
  this._catBar = catBar;
@@ -9337,7 +12438,7 @@
9337
12438
  this._grid = grid;
9338
12439
  const optRow = createElement("div", { class: "an-icon-options" });
9339
12440
  const styleLabel = createElement("label", { class: "an-label" });
9340
- styleLabel.textContent = "Style";
12441
+ styleLabel.textContent = L.style;
9341
12442
  const styleSelect = createElement("select", { class: "an-input an-icon-option-select" });
9342
12443
  [
9343
12444
  ["fa-solid", "Solid"],
@@ -9351,7 +12452,7 @@
9351
12452
  styleSelect.value = "fa-solid";
9352
12453
  this._styleSelect = styleSelect;
9353
12454
  const sizeLabel = createElement("label", { class: "an-label" });
9354
- sizeLabel.textContent = "Size";
12455
+ sizeLabel.textContent = L.size;
9355
12456
  const sizeSelect = createElement("select", { class: "an-input an-icon-option-select" });
9356
12457
  [
9357
12458
  ["", "Inherit"],
@@ -9369,7 +12470,7 @@
9369
12470
  });
9370
12471
  this._sizeSelect = sizeSelect;
9371
12472
  const colorLabel = createElement("label", { class: "an-label" });
9372
- colorLabel.textContent = "Color";
12473
+ colorLabel.textContent = L.color;
9373
12474
  const colorInput = createElement("input", {
9374
12475
  type: "color",
9375
12476
  class: "an-icon-color",
@@ -9382,11 +12483,11 @@
9382
12483
  checked: ""
9383
12484
  });
9384
12485
  this._useColorCb = useColorCb;
9385
- useColorLabel.append(useColorCb, document.createTextNode(" Use color"));
12486
+ useColorLabel.append(useColorCb, document.createTextNode(L.useColor));
9386
12487
  optRow.append(styleLabel, styleSelect, sizeLabel, sizeSelect, colorLabel, colorInput, useColorLabel);
9387
12488
  const preview = createElement("div", { class: "an-icon-preview" });
9388
12489
  const previewHint = createElement("span", { class: "an-icon-preview-hint" });
9389
- previewHint.textContent = "Select an icon";
12490
+ previewHint.textContent = L.selectHint;
9390
12491
  preview.appendChild(previewHint);
9391
12492
  this._preview = preview;
9392
12493
  const btnRow = createElement("div", { class: "an-dialog-actions" });
@@ -9395,12 +12496,12 @@
9395
12496
  class: "an-btn an-btn-primary",
9396
12497
  disabled: ""
9397
12498
  });
9398
- insertBtn.textContent = "Insert FA Icon";
12499
+ insertBtn.textContent = L.insertBtn;
9399
12500
  const cancelBtn = createElement("button", {
9400
12501
  type: "button",
9401
12502
  class: "an-btn"
9402
12503
  });
9403
- cancelBtn.textContent = "Cancel";
12504
+ cancelBtn.textContent = L.cancelBtn;
9404
12505
  btnRow.append(insertBtn, cancelBtn);
9405
12506
  this._insertBtn = insertBtn;
9406
12507
  box.append(titleRow, searchInput, catBar, grid, optRow, preview, btnRow);
@@ -9497,7 +12598,13 @@
9497
12598
  range.selectNodeContents(editable);
9498
12599
  range.collapse(false);
9499
12600
  }
12601
+ const _sc = range.startContainer;
12602
+ const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest?.("td, th");
9500
12603
  range.deleteContents();
12604
+ if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
12605
+ range.setStart(_tdAnchor, 0);
12606
+ range.collapse(true);
12607
+ }
9501
12608
  range.insertNode(iconEl);
9502
12609
  let caretTextNode = iconEl.nextSibling;
9503
12610
  if (!caretTextNode || caretTextNode.nodeType !== Node.TEXT_NODE) {
@@ -9595,9 +12702,11 @@
9595
12702
  ];
9596
12703
  function makeColorSubItems(colorType) {
9597
12704
  const label = colorType === "foreColor" ? "Text Color" : "Highlight Color";
12705
+ const localeKey = colorType === "foreColor" ? "textColor" : "highlightColor";
9598
12706
  return () => [{
9599
12707
  back: true,
9600
12708
  label,
12709
+ localeKey,
9601
12710
  navigate: () => defaultItems
9602
12711
  }, {
9603
12712
  colorPalette: true,
@@ -9794,7 +12903,8 @@
9794
12903
  });
9795
12904
  iconSpan.innerHTML = ICONS.back;
9796
12905
  backBtn.appendChild(iconSpan);
9797
- backBtn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || "Back"]));
12906
+ const backLabel = it.localeKey && this.context.locale.contextMenu[it.localeKey] || it.label || this.context.locale.contextMenu.back || "Back";
12907
+ backBtn.appendChild(createElement("span", { class: "an-context-label" }, [backLabel]));
9798
12908
  const off = on(backBtn, "click", (e) => {
9799
12909
  e.stopPropagation();
9800
12910
  const curLeft = parseFloat(this.el.style.left);
@@ -9832,7 +12942,7 @@
9832
12942
  iconSpan.innerHTML = it.icon;
9833
12943
  btn.appendChild(iconSpan);
9834
12944
  }
9835
- btn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || it.name]));
12945
+ btn.appendChild(createElement("span", { class: "an-context-label" }, [this.context.locale.contextMenu[it.name] || it.label || it.name]));
9836
12946
  const chevron = createElement("span", {
9837
12947
  class: "an-context-chevron",
9838
12948
  "aria-hidden": "true"
@@ -9870,9 +12980,9 @@
9870
12980
  if (it.colorType === "hiliteColor") {
9871
12981
  const noColor = createElement("div", {
9872
12982
  class: "an-context-color-swatch an-context-color-none",
9873
- title: "No highlight",
12983
+ title: this.context.locale.contextMenu.noHighlight || "No highlight",
9874
12984
  role: "button",
9875
- "aria-label": "No highlight"
12985
+ "aria-label": this.context.locale.contextMenu.noHighlight || "No highlight"
9876
12986
  });
9877
12987
  noColor.innerHTML = ICONS.noColor;
9878
12988
  const offNo = on(noColor, "click", (e) => {
@@ -9887,10 +12997,10 @@
9887
12997
  const colorInput = createElement("input", {
9888
12998
  type: "color",
9889
12999
  value: it.colorType === "foreColor" ? "#000000" : "#ffff00",
9890
- title: "Custom color",
9891
- "aria-label": "Custom color"
13000
+ title: this.context.locale.contextMenu.customColor || "Custom color",
13001
+ "aria-label": this.context.locale.contextMenu.customColor || "Custom color"
9892
13002
  });
9893
- const customLabel = createElement("span", {}, ["Custom…"]);
13003
+ const customLabel = createElement("span", {}, [this.context.locale.contextMenu.customColorLabel || "Custom…"]);
9894
13004
  const offCustom = on(colorInput, "change", () => this._applyColor(it.colorType, colorInput.value));
9895
13005
  this._menuDisposers.push(offCustom);
9896
13006
  customRow.appendChild(colorInput);
@@ -9914,7 +13024,7 @@
9914
13024
  iconSpan.innerHTML = it.icon;
9915
13025
  headerBtn.appendChild(iconSpan);
9916
13026
  }
9917
- headerBtn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || "Insert Table"]));
13027
+ headerBtn.appendChild(createElement("span", { class: "an-context-label" }, [this.context.locale.contextMenu.table || it.label || "Insert Table"]));
9918
13028
  const chevron = createElement("span", {
9919
13029
  class: "an-context-chevron",
9920
13030
  "aria-hidden": "true"
@@ -9943,7 +13053,7 @@
9943
13053
  cells.forEach((cell) => {
9944
13054
  cell.classList.toggle("active", +cell.dataset.row <= rows && +cell.dataset.col <= cols);
9945
13055
  });
9946
- labelEl.textContent = rows && cols ? `${cols} × ${rows}` : "Insert Table";
13056
+ labelEl.textContent = rows && cols ? `${cols} × ${rows}` : this.context.locale.contextMenu.table || "Insert Table";
9947
13057
  };
9948
13058
  panel.appendChild(gridEl);
9949
13059
  panel.appendChild(labelEl);
@@ -9997,7 +13107,7 @@
9997
13107
  iconSpan.innerHTML = it.icon;
9998
13108
  btn.appendChild(iconSpan);
9999
13109
  }
10000
- btn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || it.name]));
13110
+ btn.appendChild(createElement("span", { class: "an-context-label" }, [this.context.locale.contextMenu[it.name] || it.label || it.name]));
10001
13111
  const off = on(btn, "click", (e) => {
10002
13112
  e.stopPropagation();
10003
13113
  this.hide();
@@ -10311,22 +13421,22 @@
10311
13421
  class: "an-dialog-overlay",
10312
13422
  role: "dialog",
10313
13423
  "aria-modal": "true",
10314
- "aria-label": "Keyboard Shortcuts"
13424
+ "aria-label": this.context.locale.shortcutsDialog.ariaLabel
10315
13425
  });
10316
13426
  const box = createElement("div", { class: "an-dialog-box an-shortcuts-box" });
10317
13427
  const titleRow = createElement("div", { class: "an-icon-title-row" });
10318
13428
  const title = createElement("h3", { class: "an-dialog-title" });
10319
- title.textContent = "Keyboard Shortcuts";
13429
+ title.textContent = this.context.locale.shortcutsDialog.title;
10320
13430
  const closeBtn = createElement("button", {
10321
13431
  type: "button",
10322
13432
  class: "an-icon-close",
10323
- "aria-label": "Close"
13433
+ "aria-label": this.context.locale.shortcutsDialog.close
10324
13434
  });
10325
13435
  closeBtn.textContent = "×";
10326
13436
  this._closeBtn = closeBtn;
10327
13437
  titleRow.append(title, closeBtn);
10328
13438
  box.appendChild(titleRow);
10329
- SHORTCUTS.forEach(({ category, items }) => {
13439
+ (this.context.locale.shortcutsDialog.shortcuts || SHORTCUTS).forEach(({ category, items }) => {
10330
13440
  const catEl = createElement("div", { class: "an-shortcuts-cat" });
10331
13441
  catEl.textContent = category;
10332
13442
  box.appendChild(catEl);
@@ -10451,25 +13561,26 @@
10451
13561
  const isReplace = this._mode === "replace";
10452
13562
  if (replaceRow) replaceRow.style.display = isReplace ? "" : "none";
10453
13563
  if (replaceActions) replaceActions.style.display = isReplace ? "" : "none";
10454
- if (title) title.textContent = isReplace ? "Find & Replace" : "Find";
13564
+ if (title) title.textContent = isReplace ? this.context.locale.findReplace.findReplaceTitle : this.context.locale.findReplace.findTitle;
10455
13565
  }
10456
13566
  _buildDialog() {
13567
+ const L = this.context.locale.findReplace;
10457
13568
  const overlay = createElement("div", {
10458
13569
  class: "an-dialog-overlay an-fr-dialog",
10459
13570
  role: "dialog",
10460
13571
  "aria-modal": "true",
10461
- "aria-label": "Find and Replace"
13572
+ "aria-label": L.findReplaceTitle
10462
13573
  });
10463
13574
  const box = createElement("div", { class: "an-dialog-box" });
10464
13575
  const titleRow = createElement("div", { class: "an-icon-title-row" });
10465
13576
  const title = createElement("h3", { class: "an-dialog-title" });
10466
- title.textContent = "Find";
13577
+ title.textContent = L.findTitle;
10467
13578
  const closeBtn = createElement("button", {
10468
13579
  type: "button",
10469
13580
  class: "an-icon-close",
10470
13581
  "aria-label": "Close"
10471
13582
  });
10472
- closeBtn.textContent = "×";
13583
+ closeBtn.textContent = L.close;
10473
13584
  this._closeBtn = closeBtn;
10474
13585
  titleRow.append(title, closeBtn);
10475
13586
  box.appendChild(titleRow);
@@ -10477,8 +13588,8 @@
10477
13588
  const findInput = createElement("input", {
10478
13589
  type: "text",
10479
13590
  class: "an-input",
10480
- placeholder: "Find…",
10481
- "aria-label": "Search text"
13591
+ placeholder: L.findPlaceholder,
13592
+ "aria-label": L.searchAriaLabel
10482
13593
  });
10483
13594
  this._findInput = findInput;
10484
13595
  findRow.appendChild(findInput);
@@ -10490,7 +13601,7 @@
10490
13601
  "aria-label": "Case sensitive"
10491
13602
  });
10492
13603
  this._caseCheckbox = caseCheckbox;
10493
- caseLabel.append(caseCheckbox, document.createTextNode("\xA0Case sensitive"));
13604
+ caseLabel.append(caseCheckbox, document.createTextNode(L.caseSensitive));
10494
13605
  const counter = createElement("span", { class: "an-fr-counter" });
10495
13606
  this._counterEl = counter;
10496
13607
  optRow.append(caseLabel, counter);
@@ -10500,12 +13611,12 @@
10500
13611
  type: "button",
10501
13612
  class: "an-btn"
10502
13613
  });
10503
- prevBtn.textContent = "← Prev";
13614
+ prevBtn.textContent = L.prevBtn;
10504
13615
  const nextBtn = createElement("button", {
10505
13616
  type: "button",
10506
13617
  class: "an-btn an-btn-primary"
10507
13618
  });
10508
- nextBtn.textContent = "Next →";
13619
+ nextBtn.textContent = L.nextBtn;
10509
13620
  findActions.append(prevBtn, nextBtn);
10510
13621
  box.appendChild(findActions);
10511
13622
  const replaceRow = createElement("div", { class: "an-fr-replace-row" });
@@ -10513,8 +13624,8 @@
10513
13624
  const replaceInput = createElement("input", {
10514
13625
  type: "text",
10515
13626
  class: "an-input",
10516
- placeholder: "Replace with…",
10517
- "aria-label": "Replace with"
13627
+ placeholder: L.replacePlaceholder,
13628
+ "aria-label": L.replaceAriaLabel
10518
13629
  });
10519
13630
  this._replaceInput = replaceInput;
10520
13631
  replaceRow.appendChild(replaceInput);
@@ -10525,12 +13636,12 @@
10525
13636
  type: "button",
10526
13637
  class: "an-btn"
10527
13638
  });
10528
- replaceBtn.textContent = "Replace";
13639
+ replaceBtn.textContent = L.replaceBtn;
10529
13640
  const replaceAllBtn = createElement("button", {
10530
13641
  type: "button",
10531
13642
  class: "an-btn an-btn-primary"
10532
13643
  });
10533
- replaceAllBtn.textContent = "Replace All";
13644
+ replaceAllBtn.textContent = L.replaceAllBtn;
10534
13645
  replaceActions.append(replaceBtn, replaceAllBtn);
10535
13646
  box.appendChild(replaceActions);
10536
13647
  overlay.appendChild(box);
@@ -11191,6 +14302,8 @@
11191
14302
  constructor(targetEl, userOptions = {}) {
11192
14303
  this.targetEl = targetEl;
11193
14304
  this.options = mergeDeep(defaultOptions, userOptions);
14305
+ /** @type {import('./i18n/index.js').AsnLocale} */
14306
+ this.locale = resolveLocale(this.options.lang);
11194
14307
  /** @type {{ container: HTMLElement, editable: HTMLElement, toolbar?: HTMLElement, statusbar?: HTMLElement }} */
11195
14308
  this.layoutInfo = {};
11196
14309
  /** @type {Map<string, Function[]>} */
@@ -11582,7 +14695,7 @@
11582
14695
  registerModule(name, ModuleClass) {
11583
14696
  _customModules.set(name, ModuleClass);
11584
14697
  },
11585
- version: "1.0.4"
14698
+ version: "1.0.9"
11586
14699
  };
11587
14700
  /**
11588
14701
  * @param {string|Element|NodeList|Element[]} selector