autumnnote 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +401 -628
  2. package/dist/autumnnote.css +169 -0
  3. package/dist/autumnnote.es.js +3669 -179
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +3668 -178
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +3 -2
  8. package/src/js/Context.js +18 -2
  9. package/src/js/core/func.js +5 -5
  10. package/src/js/i18n/de.js +320 -0
  11. package/src/js/i18n/en.js +328 -0
  12. package/src/js/i18n/es.js +320 -0
  13. package/src/js/i18n/fr.js +321 -0
  14. package/src/js/i18n/index.js +59 -0
  15. package/src/js/i18n/ja.js +321 -0
  16. package/src/js/i18n/ko.js +320 -0
  17. package/src/js/i18n/vi.js +321 -0
  18. package/src/js/i18n/zh.js +321 -0
  19. package/src/js/index.js +2 -1
  20. package/src/js/module/AutoSaveRestore.js +126 -0
  21. package/src/js/module/BubbleToolbar.js +243 -0
  22. package/src/js/module/CodeTooltip.js +12 -9
  23. package/src/js/module/ContextMenu.js +41 -37
  24. package/src/js/module/EmojiDialog.js +8 -7
  25. package/src/js/module/FindReplace.js +14 -13
  26. package/src/js/module/IconDialog.js +14 -13
  27. package/src/js/module/ImageDialog.js +17 -16
  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/MarkdownShortcuts.js +253 -0
  32. package/src/js/module/Mention.js +337 -0
  33. package/src/js/module/ShortcutsDialog.js +5 -4
  34. package/src/js/module/Statusbar.js +6 -5
  35. package/src/js/module/TableTooltip.js +22 -19
  36. package/src/js/module/Toolbar.js +21 -15
  37. package/src/js/module/VideoDialog.js +10 -9
  38. package/src/js/module/VideoTooltip.js +12 -11
  39. package/src/js/settings.js +23 -0
  40. package/src/styles/autumnnote.scss +191 -0
  41. package/types/index.d.ts +114 -1
@@ -28,8 +28,7 @@
28
28
  function mergeDeep(target, source) {
29
29
  const output = {};
30
30
  for (const key of Object.keys(target)) output[key] = Array.isArray(target[key]) ? [...target[key]] : target[key];
31
- if (isPlainObject(target) && isPlainObject(source)) for (const key of Object.keys(source)) if (isPlainObject(source[key])) if (!(key in target)) output[key] = mergeDeep({}, source[key]);
32
- else output[key] = mergeDeep(target[key], source[key]);
31
+ if (isPlainObject(target) && isPlainObject(source)) for (const key of Object.keys(source)) if (isPlainObject(source[key])) output[key] = mergeDeep(isPlainObject(target[key]) ? target[key] : {}, source[key]);
33
32
  else if (Array.isArray(source[key])) output[key] = [...source[key]];
34
33
  else output[key] = source[key];
35
34
  return output;
@@ -1161,9 +1160,2713 @@
1161
1160
  onDestroy: null,
1162
1161
  onCharLimitReached: null,
1163
1162
  onWordLimitReached: null,
1164
- focusColor: null
1163
+ focusColor: null,
1164
+ lang: "en",
1165
+ autoSaveRestore: false,
1166
+ autoSaveRestoreTimeout: 7,
1167
+ onAutoSaveRestore: null,
1168
+ markdownShortcuts: true,
1169
+ bubbleToolbar: false,
1170
+ bubbleToolbarItems: [
1171
+ "bold",
1172
+ "italic",
1173
+ "underline",
1174
+ "link",
1175
+ "foreColor",
1176
+ "removeFormat"
1177
+ ],
1178
+ mention: null
1165
1179
  };
1166
1180
  //#endregion
1181
+ //#region src/js/i18n/en.js
1182
+ /**
1183
+ * en.js - English locale (canonical reference)
1184
+ * All other locales deep-merge against this to fill missing keys.
1185
+ */
1186
+ /** @type {import('../../../types/index.js').AsnLocale} */
1187
+ var en = {
1188
+ toolbar: {
1189
+ bold: "Bold (Ctrl+B)",
1190
+ italic: "Italic (Ctrl+I)",
1191
+ underline: "Underline (Ctrl+U)",
1192
+ strikethrough: "Strikethrough",
1193
+ superscript: "Superscript",
1194
+ subscript: "Subscript",
1195
+ alignLeft: "Align Left",
1196
+ alignCenter: "Align Center",
1197
+ alignRight: "Align Right",
1198
+ alignJustify: "Justify",
1199
+ ul: "Unordered List",
1200
+ ol: "Ordered List",
1201
+ checklist: "Checklist",
1202
+ indent: "Indent",
1203
+ outdent: "Outdent",
1204
+ undo: "Undo (Ctrl+Z)",
1205
+ redo: "Redo (Ctrl+Y)",
1206
+ hr: "Horizontal Rule",
1207
+ link: "Insert Link",
1208
+ image: "Insert Image",
1209
+ video: "Insert Video",
1210
+ emoji: "Insert Emoji",
1211
+ icon: "Insert FA Icon",
1212
+ table: "Insert Table",
1213
+ fontSize: "Font Size",
1214
+ fontSizePlaceholder: "Size",
1215
+ removeFormat: "Remove Format",
1216
+ direction: "Toggle Text Direction (LTR / RTL)",
1217
+ fontFamily: "Font Family",
1218
+ paragraphStyle: "Paragraph Style",
1219
+ paragraphStylePlaceholder: "Style",
1220
+ lineHeight: "Line Height",
1221
+ lineHeightPlaceholder: "↕ Line",
1222
+ codeview: "HTML Code View",
1223
+ fullscreen: "Fullscreen",
1224
+ shortcuts: "Keyboard Shortcuts (Ctrl+Shift+/)",
1225
+ find: "Find (Ctrl+F)",
1226
+ findReplace: "Find & Replace (Ctrl+H)",
1227
+ inlineCode: "Inline Code (Ctrl+`)",
1228
+ print: "Print",
1229
+ foreColor: "Text Color",
1230
+ backColor: "Highlight Color",
1231
+ chooseTextColor: "Choose text color",
1232
+ chooseHighlightColor: "Choose highlight color",
1233
+ customColor: "Custom color",
1234
+ insertTableLabel: "Insert Table",
1235
+ paragraphItems: {
1236
+ p: "Normal",
1237
+ blockquote: "Quote",
1238
+ pre: "Code"
1239
+ }
1240
+ },
1241
+ linkDialog: {
1242
+ ariaLabel: "Insert link",
1243
+ title: "Insert Link",
1244
+ url: "URL",
1245
+ urlPlaceholder: "https://",
1246
+ displayText: "Display Text",
1247
+ textPlaceholder: "Link text",
1248
+ openInNewTab: "Open in new tab",
1249
+ insertBtn: "Insert",
1250
+ cancelBtn: "Cancel"
1251
+ },
1252
+ imageDialog: {
1253
+ ariaLabel: "Insert image",
1254
+ title: "Insert Image",
1255
+ imageUrl: "Image URL",
1256
+ urlPlaceholder: "https://example.com/image.png",
1257
+ altText: "Alt Text",
1258
+ altPlaceholder: "Describe the image",
1259
+ alignment: "Alignment",
1260
+ alignNone: "None",
1261
+ alignLeft: "Left",
1262
+ alignCenter: "Center",
1263
+ alignRight: "Right",
1264
+ uploadLabel: "Or upload a file",
1265
+ insertBtn: "Insert",
1266
+ cancelBtn: "Cancel"
1267
+ },
1268
+ videoDialog: {
1269
+ ariaLabel: "Insert video",
1270
+ title: "Insert Video",
1271
+ videoUrl: "Video URL",
1272
+ urlPlaceholder: "YouTube, Vimeo, or direct .mp4 URL",
1273
+ widthLabel: "Width (px)",
1274
+ widthPlaceholder: "560",
1275
+ insertBtn: "Insert",
1276
+ cancelBtn: "Cancel",
1277
+ detected: (type) => `Detected: ${type}`,
1278
+ unknownFormat: "Unknown format — will try direct video embed",
1279
+ invalidUrl: "Invalid URL — please enter a valid video link."
1280
+ },
1281
+ emojiDialog: {
1282
+ ariaLabel: "Insert emoji",
1283
+ title: "Insert Emoji",
1284
+ searchPlaceholder: "Search emojis…",
1285
+ all: "All",
1286
+ cancelBtn: "Cancel",
1287
+ close: "Close",
1288
+ categories: {
1289
+ smileys: "Smileys",
1290
+ people: "People",
1291
+ animals: "Animals",
1292
+ food: "Food",
1293
+ travel: "Travel",
1294
+ objects: "Objects",
1295
+ symbols: "Symbols"
1296
+ }
1297
+ },
1298
+ iconDialog: {
1299
+ ariaLabel: "Insert FA icon",
1300
+ title: "Insert FA Icon",
1301
+ searchPlaceholder: "Search icons…",
1302
+ all: "All",
1303
+ style: "Style",
1304
+ size: "Size",
1305
+ color: "Color",
1306
+ useColor: " Use color",
1307
+ selectHint: "Select an icon",
1308
+ insertBtn: "Insert FA Icon",
1309
+ cancelBtn: "Cancel",
1310
+ close: "Close",
1311
+ categories: {
1312
+ popular: "Popular",
1313
+ interface: "Interface",
1314
+ navigation: "Navigation",
1315
+ media: "Media",
1316
+ communication: "Communication",
1317
+ files: "Files",
1318
+ people: "People",
1319
+ objects: "Objects"
1320
+ }
1321
+ },
1322
+ findReplace: {
1323
+ findTitle: "Find",
1324
+ findReplaceTitle: "Find & Replace",
1325
+ findPlaceholder: "Find…",
1326
+ searchAriaLabel: "Search text",
1327
+ caseSensitive: "\xA0Case sensitive",
1328
+ prevBtn: "← Prev",
1329
+ nextBtn: "Next →",
1330
+ replacePlaceholder: "Replace with…",
1331
+ replaceAriaLabel: "Replace with",
1332
+ replaceBtn: "Replace",
1333
+ replaceAllBtn: "Replace All",
1334
+ close: "×"
1335
+ },
1336
+ shortcutsDialog: {
1337
+ title: "Keyboard Shortcuts",
1338
+ ariaLabel: "Keyboard Shortcuts",
1339
+ close: "Close",
1340
+ shortcuts: [
1341
+ {
1342
+ category: "Text Formatting",
1343
+ items: [
1344
+ {
1345
+ keys: "Ctrl + B",
1346
+ action: "Bold"
1347
+ },
1348
+ {
1349
+ keys: "Ctrl + I",
1350
+ action: "Italic"
1351
+ },
1352
+ {
1353
+ keys: "Ctrl + U",
1354
+ action: "Underline"
1355
+ },
1356
+ {
1357
+ keys: "Ctrl + K",
1358
+ action: "Insert / edit link"
1359
+ }
1360
+ ]
1361
+ },
1362
+ {
1363
+ category: "History",
1364
+ items: [{
1365
+ keys: "Ctrl + Z",
1366
+ action: "Undo"
1367
+ }, {
1368
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
1369
+ action: "Redo"
1370
+ }]
1371
+ },
1372
+ {
1373
+ category: "Selection & Navigation",
1374
+ items: [
1375
+ {
1376
+ keys: "Ctrl + A",
1377
+ action: "Select all content"
1378
+ },
1379
+ {
1380
+ keys: "Tab",
1381
+ action: "Indent list item / insert spaces"
1382
+ },
1383
+ {
1384
+ keys: "Shift + Tab",
1385
+ action: "Outdent list item"
1386
+ }
1387
+ ]
1388
+ },
1389
+ {
1390
+ category: "Clipboard",
1391
+ items: [{
1392
+ keys: "Ctrl + Shift + V",
1393
+ action: "Paste as plain text"
1394
+ }]
1395
+ },
1396
+ {
1397
+ category: "Find & Replace",
1398
+ items: [{
1399
+ keys: "Ctrl + F",
1400
+ action: "Find in document"
1401
+ }, {
1402
+ keys: "Ctrl + H",
1403
+ action: "Find & Replace"
1404
+ }]
1405
+ },
1406
+ {
1407
+ category: "Editor",
1408
+ items: [{
1409
+ keys: "Ctrl + Shift + /",
1410
+ action: "Show this keyboard shortcuts dialog"
1411
+ }]
1412
+ }
1413
+ ]
1414
+ },
1415
+ contextMenu: {
1416
+ cut: "Cut",
1417
+ copy: "Copy",
1418
+ paste: "Paste",
1419
+ bold: "Bold",
1420
+ italic: "Italic",
1421
+ underline: "Underline",
1422
+ textColor: "Text Color",
1423
+ highlightColor: "Highlight Color",
1424
+ copyFormat: "Copy Format",
1425
+ pasteFormat: "Paste Format",
1426
+ removeFormat: "Remove Format",
1427
+ link: "Insert Link",
1428
+ image: "Insert Image",
1429
+ video: "Insert Video",
1430
+ table: "Insert Table",
1431
+ back: "Back",
1432
+ noHighlight: "No highlight",
1433
+ customColor: "Custom color",
1434
+ customColorLabel: "Custom…"
1435
+ },
1436
+ statusbar: {
1437
+ resizeHandle: "Resize editor",
1438
+ words: (n) => `Words: ${n}`,
1439
+ wordsLimit: (n, max) => `Words: ${n}/${max}`,
1440
+ chars: (n) => `Chars: ${n}`,
1441
+ charsLimit: (n, max) => `Chars: ${n}/${max}`
1442
+ },
1443
+ tooltips: {
1444
+ link: {
1445
+ ariaLabel: "Link actions",
1446
+ openLink: "Open link",
1447
+ copyUrl: "Copy URL",
1448
+ editLink: "Edit link",
1449
+ removeLink: "Remove link"
1450
+ },
1451
+ image: {
1452
+ ariaLabel: "Image actions",
1453
+ label: "Image",
1454
+ floatLeft: "Float Left",
1455
+ noFloat: "No Float",
1456
+ alignCenter: "Align Center",
1457
+ floatRight: "Float Right",
1458
+ originalSize: "Original Size",
1459
+ rotateLeft: "Rotate Left",
1460
+ rotateRight: "Rotate Right",
1461
+ cropImage: "Crop Image",
1462
+ addCaption: "Add / Edit Caption",
1463
+ deleteImage: "Delete Image"
1464
+ },
1465
+ code: {
1466
+ ariaLabel: "Code block actions",
1467
+ label: "Code",
1468
+ syntaxLanguage: "Syntax Language",
1469
+ syntaxAriaLabel: "Syntax language",
1470
+ copyCode: "Copy Code",
1471
+ toggleWordWrap: "Toggle Word Wrap",
1472
+ enableWordWrap: "Enable Word Wrap",
1473
+ disableWordWrap: "Disable Word Wrap",
1474
+ convertToParagraph: "Convert to Paragraph",
1475
+ deleteCodeBlock: "Delete Code Block"
1476
+ },
1477
+ table: {
1478
+ ariaLabel: "Table actions",
1479
+ label: "Table",
1480
+ selectCells: "Select Cells",
1481
+ addRowAbove: "Add Row Above",
1482
+ addRowBelow: "Add Row Below",
1483
+ deleteRow: "Delete Row",
1484
+ addColumnLeft: "Add Column Left",
1485
+ addColumnRight: "Add Column Right",
1486
+ deleteColumn: "Delete Column",
1487
+ mergeCells: "Merge Cells",
1488
+ unmergeCells: "Unmerge Cells",
1489
+ columnWidth: "Column Width",
1490
+ rowHeight: "Row Height",
1491
+ tableBorderWidth: "Table Border Width",
1492
+ deleteTable: "Delete Table",
1493
+ columnWidthPx: "Column Width (px)",
1494
+ rowHeightPx: "Row Height (px)",
1495
+ tableBorderWidthPx: "Table Border Width (px)",
1496
+ cancelBtn: "Cancel",
1497
+ applyBtn: "Apply"
1498
+ },
1499
+ video: {
1500
+ ariaLabel: "Video actions",
1501
+ label: "Video",
1502
+ floatLeft: "Float Left",
1503
+ noFloat: "No Float",
1504
+ alignCenter: "Align Center",
1505
+ floatRight: "Float Right",
1506
+ originalSize: "Original Size",
1507
+ previewVideo: "Preview Video",
1508
+ exitPreview: "Exit Preview",
1509
+ deleteVideo: "Delete Video"
1510
+ }
1511
+ },
1512
+ errors: {
1513
+ imageFormat: (type) => `Format "${type}" is not supported for display in web browsers. Please convert to JPEG, PNG, or WebP first.`,
1514
+ imageSize: (maxSize) => `Image file is too large. Maximum allowed size is ${maxSize} MB.`
1515
+ }
1516
+ };
1517
+ //#endregion
1518
+ //#region src/js/i18n/index.js
1519
+ /**
1520
+ * i18n/index.js — Locale registry and resolver for autumn-note-ce.
1521
+ *
1522
+ * Usage:
1523
+ * lang: 'en' → built-in English (default)
1524
+ * lang: 'vi' → built-in Vietnamese
1525
+ * lang: 'ja' → built-in Japanese
1526
+ * lang: 'zh' → built-in Simplified Chinese
1527
+ * lang: 'fr' → built-in French
1528
+ * lang: 'de' → built-in German
1529
+ * lang: 'es' → built-in Spanish
1530
+ * lang: 'ko' → built-in Korean
1531
+ * lang: { ... } → custom locale object, deep-merged over English
1532
+ */
1533
+ /**
1534
+ * All built-in locales keyed by their language code.
1535
+ * @type {Record<string, Partial<AsnLocale>>}
1536
+ */
1537
+ var locales = {
1538
+ en,
1539
+ vi: {
1540
+ toolbar: {
1541
+ bold: "Đậm (Ctrl+B)",
1542
+ italic: "Nghiêng (Ctrl+I)",
1543
+ underline: "Gạch chân (Ctrl+U)",
1544
+ strikethrough: "Gạch ngang",
1545
+ superscript: "Chỉ số trên",
1546
+ subscript: "Chỉ số dưới",
1547
+ alignLeft: "Căn trái",
1548
+ alignCenter: "Căn giữa",
1549
+ alignRight: "Căn phải",
1550
+ alignJustify: "Căn đều",
1551
+ ul: "Danh sách không thứ tự",
1552
+ ol: "Danh sách có thứ tự",
1553
+ checklist: "Danh sách kiểm tra",
1554
+ indent: "Tăng thụt đầu dòng",
1555
+ outdent: "Giảm thụt đầu dòng",
1556
+ undo: "Hoàn tác (Ctrl+Z)",
1557
+ redo: "Làm lại (Ctrl+Y)",
1558
+ hr: "Đường kẻ ngang",
1559
+ link: "Chèn liên kết",
1560
+ image: "Chèn hình ảnh",
1561
+ video: "Chèn video",
1562
+ emoji: "Chèn biểu tượng cảm xúc",
1563
+ icon: "Chèn biểu tượng FA",
1564
+ table: "Chèn bảng",
1565
+ fontSize: "Cỡ chữ",
1566
+ fontSizePlaceholder: "Cỡ",
1567
+ removeFormat: "Xóa định dạng",
1568
+ direction: "Chuyển hướng văn bản (LTR / RTL)",
1569
+ fontFamily: "Phông chữ",
1570
+ paragraphStyle: "Kiểu đoạn văn",
1571
+ paragraphStylePlaceholder: "Kiểu",
1572
+ lineHeight: "Khoảng cách dòng",
1573
+ lineHeightPlaceholder: "↕ Dòng",
1574
+ codeview: "Xem mã HTML",
1575
+ fullscreen: "Toàn màn hình",
1576
+ shortcuts: "Phím tắt (Ctrl+Shift+/)",
1577
+ find: "Tìm kiếm (Ctrl+F)",
1578
+ findReplace: "Tìm & Thay thế (Ctrl+H)",
1579
+ inlineCode: "Mã nội tuyến (Ctrl+`)",
1580
+ print: "In",
1581
+ foreColor: "Màu chữ",
1582
+ backColor: "Màu nền chữ",
1583
+ chooseTextColor: "Chọn màu chữ",
1584
+ chooseHighlightColor: "Chọn màu nền",
1585
+ customColor: "Màu tùy chỉnh",
1586
+ insertTableLabel: "Chèn bảng",
1587
+ paragraphItems: {
1588
+ p: "Bình thường",
1589
+ blockquote: "Trích dẫn",
1590
+ pre: "Mã"
1591
+ }
1592
+ },
1593
+ linkDialog: {
1594
+ ariaLabel: "Chèn liên kết",
1595
+ title: "Chèn liên kết",
1596
+ url: "Địa chỉ URL",
1597
+ urlPlaceholder: "https://",
1598
+ displayText: "Văn bản hiển thị",
1599
+ textPlaceholder: "Nội dung liên kết",
1600
+ openInNewTab: "Mở trong tab mới",
1601
+ insertBtn: "Chèn",
1602
+ cancelBtn: "Hủy"
1603
+ },
1604
+ imageDialog: {
1605
+ ariaLabel: "Chèn hình ảnh",
1606
+ title: "Chèn hình ảnh",
1607
+ imageUrl: "URL hình ảnh",
1608
+ urlPlaceholder: "https://example.com/anh.png",
1609
+ altText: "Văn bản thay thế",
1610
+ altPlaceholder: "Mô tả hình ảnh",
1611
+ alignment: "Căn chỉnh",
1612
+ alignNone: "Không",
1613
+ alignLeft: "Trái",
1614
+ alignCenter: "Giữa",
1615
+ alignRight: "Phải",
1616
+ uploadLabel: "Hoặc tải lên tệp",
1617
+ insertBtn: "Chèn",
1618
+ cancelBtn: "Hủy"
1619
+ },
1620
+ videoDialog: {
1621
+ ariaLabel: "Chèn video",
1622
+ title: "Chèn video",
1623
+ videoUrl: "URL video",
1624
+ urlPlaceholder: "YouTube, Vimeo, hoặc URL .mp4 trực tiếp",
1625
+ widthLabel: "Chiều rộng (px)",
1626
+ widthPlaceholder: "560",
1627
+ insertBtn: "Chèn",
1628
+ cancelBtn: "Hủy",
1629
+ detected: (type) => `Đã phát hiện: ${type}`,
1630
+ unknownFormat: "Định dạng không xác định — sẽ thử nhúng video trực tiếp",
1631
+ invalidUrl: "URL không hợp lệ — vui lòng nhập đường dẫn video hợp lệ."
1632
+ },
1633
+ emojiDialog: {
1634
+ ariaLabel: "Chèn biểu tượng cảm xúc",
1635
+ title: "Chèn biểu tượng cảm xúc",
1636
+ searchPlaceholder: "Tìm kiếm biểu tượng…",
1637
+ all: "Tất cả",
1638
+ cancelBtn: "Hủy",
1639
+ close: "Đóng",
1640
+ categories: {
1641
+ smileys: "Mặt cười",
1642
+ people: "Con người",
1643
+ animals: "Động vật",
1644
+ food: "Thức ăn",
1645
+ travel: "Du lịch",
1646
+ objects: "Đồ vật",
1647
+ symbols: "Ký hiệu"
1648
+ }
1649
+ },
1650
+ iconDialog: {
1651
+ ariaLabel: "Chèn biểu tượng FA",
1652
+ title: "Chèn biểu tượng FA",
1653
+ searchPlaceholder: "Tìm kiếm biểu tượng…",
1654
+ all: "Tất cả",
1655
+ style: "Kiểu",
1656
+ size: "Kích thước",
1657
+ color: "Màu sắc",
1658
+ useColor: " Dùng màu",
1659
+ selectHint: "Chọn một biểu tượng",
1660
+ insertBtn: "Chèn biểu tượng FA",
1661
+ cancelBtn: "Hủy",
1662
+ close: "Đóng",
1663
+ categories: {
1664
+ popular: "Phổ biến",
1665
+ interface: "Giao diện",
1666
+ navigation: "Điều hướng",
1667
+ media: "Phương tiện",
1668
+ communication: "Liên lạc",
1669
+ files: "Tệp tin",
1670
+ people: "Con người",
1671
+ objects: "Đồ vật"
1672
+ }
1673
+ },
1674
+ findReplace: {
1675
+ findTitle: "Tìm kiếm",
1676
+ findReplaceTitle: "Tìm & Thay thế",
1677
+ findPlaceholder: "Tìm…",
1678
+ searchAriaLabel: "Văn bản tìm kiếm",
1679
+ caseSensitive: "\xA0Phân biệt hoa thường",
1680
+ prevBtn: "← Trước",
1681
+ nextBtn: "Tiếp →",
1682
+ replacePlaceholder: "Thay thế bằng…",
1683
+ replaceAriaLabel: "Thay thế bằng",
1684
+ replaceBtn: "Thay thế",
1685
+ replaceAllBtn: "Thay thế tất cả",
1686
+ close: "×"
1687
+ },
1688
+ shortcutsDialog: {
1689
+ title: "Phím tắt bàn phím",
1690
+ ariaLabel: "Phím tắt bàn phím",
1691
+ close: "Đóng",
1692
+ shortcuts: [
1693
+ {
1694
+ category: "Định dạng văn bản",
1695
+ items: [
1696
+ {
1697
+ keys: "Ctrl + B",
1698
+ action: "Đậm"
1699
+ },
1700
+ {
1701
+ keys: "Ctrl + I",
1702
+ action: "Nghiêng"
1703
+ },
1704
+ {
1705
+ keys: "Ctrl + U",
1706
+ action: "Gạch chân"
1707
+ },
1708
+ {
1709
+ keys: "Ctrl + K",
1710
+ action: "Chèn / sửa liên kết"
1711
+ }
1712
+ ]
1713
+ },
1714
+ {
1715
+ category: "Lịch sử",
1716
+ items: [{
1717
+ keys: "Ctrl + Z",
1718
+ action: "Hoàn tác"
1719
+ }, {
1720
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
1721
+ action: "Làm lại"
1722
+ }]
1723
+ },
1724
+ {
1725
+ category: "Chọn & Điều hướng",
1726
+ items: [
1727
+ {
1728
+ keys: "Ctrl + A",
1729
+ action: "Chọn tất cả"
1730
+ },
1731
+ {
1732
+ keys: "Tab",
1733
+ action: "Tăng thụt đầu dòng / chèn khoảng trắng"
1734
+ },
1735
+ {
1736
+ keys: "Shift + Tab",
1737
+ action: "Giảm thụt đầu dòng"
1738
+ }
1739
+ ]
1740
+ },
1741
+ {
1742
+ category: "Bảng nhớ tạm",
1743
+ items: [{
1744
+ keys: "Ctrl + Shift + V",
1745
+ action: "Dán dưới dạng văn bản thuần"
1746
+ }]
1747
+ },
1748
+ {
1749
+ category: "Tìm & Thay thế",
1750
+ items: [{
1751
+ keys: "Ctrl + F",
1752
+ action: "Tìm trong tài liệu"
1753
+ }, {
1754
+ keys: "Ctrl + H",
1755
+ action: "Tìm & Thay thế"
1756
+ }]
1757
+ },
1758
+ {
1759
+ category: "Trình soạn thảo",
1760
+ items: [{
1761
+ keys: "Ctrl + Shift + /",
1762
+ action: "Hiện hộp thoại phím tắt"
1763
+ }]
1764
+ }
1765
+ ]
1766
+ },
1767
+ contextMenu: {
1768
+ cut: "Cắt",
1769
+ copy: "Sao chép",
1770
+ paste: "Dán",
1771
+ bold: "Đậm",
1772
+ italic: "Nghiêng",
1773
+ underline: "Gạch chân",
1774
+ textColor: "Màu chữ",
1775
+ highlightColor: "Màu nền chữ",
1776
+ copyFormat: "Sao chép định dạng",
1777
+ pasteFormat: "Dán định dạng",
1778
+ removeFormat: "Xóa định dạng",
1779
+ link: "Chèn liên kết",
1780
+ image: "Chèn hình ảnh",
1781
+ video: "Chèn video",
1782
+ table: "Chèn bảng",
1783
+ back: "Quay lại",
1784
+ noHighlight: "Không tô màu",
1785
+ customColor: "Màu tùy chỉnh",
1786
+ customColorLabel: "Tùy chỉnh…"
1787
+ },
1788
+ statusbar: {
1789
+ resizeHandle: "Kéo để thay đổi kích thước",
1790
+ words: (n) => `Từ: ${n}`,
1791
+ wordsLimit: (n, max) => `Từ: ${n}/${max}`,
1792
+ chars: (n) => `Ký tự: ${n}`,
1793
+ charsLimit: (n, max) => `Ký tự: ${n}/${max}`
1794
+ },
1795
+ tooltips: {
1796
+ link: {
1797
+ ariaLabel: "Hành động liên kết",
1798
+ openLink: "Mở liên kết",
1799
+ copyUrl: "Sao chép URL",
1800
+ editLink: "Sửa liên kết",
1801
+ removeLink: "Xóa liên kết"
1802
+ },
1803
+ image: {
1804
+ ariaLabel: "Hành động hình ảnh",
1805
+ label: "Hình ảnh",
1806
+ floatLeft: "Nổi trái",
1807
+ noFloat: "Không nổi",
1808
+ alignCenter: "Căn giữa",
1809
+ floatRight: "Nổi phải",
1810
+ originalSize: "Kích thước gốc",
1811
+ rotateLeft: "Xoay trái",
1812
+ rotateRight: "Xoay phải",
1813
+ cropImage: "Cắt ảnh",
1814
+ addCaption: "Thêm / Sửa chú thích",
1815
+ deleteImage: "Xóa hình ảnh"
1816
+ },
1817
+ code: {
1818
+ ariaLabel: "Hành động khối mã",
1819
+ label: "Mã",
1820
+ syntaxLanguage: "Ngôn ngữ cú pháp",
1821
+ syntaxAriaLabel: "Ngôn ngữ cú pháp",
1822
+ copyCode: "Sao chép mã",
1823
+ toggleWordWrap: "Chuyển đổi xuống dòng",
1824
+ enableWordWrap: "Bật xuống dòng",
1825
+ disableWordWrap: "Tắt xuống dòng",
1826
+ convertToParagraph: "Chuyển thành đoạn văn",
1827
+ deleteCodeBlock: "Xóa khối mã"
1828
+ },
1829
+ table: {
1830
+ ariaLabel: "Hành động bảng",
1831
+ label: "Bảng",
1832
+ selectCells: "Chọn ô",
1833
+ addRowAbove: "Thêm hàng phía trên",
1834
+ addRowBelow: "Thêm hàng phía dưới",
1835
+ deleteRow: "Xóa hàng",
1836
+ addColumnLeft: "Thêm cột bên trái",
1837
+ addColumnRight: "Thêm cột bên phải",
1838
+ deleteColumn: "Xóa cột",
1839
+ mergeCells: "Gộp ô",
1840
+ unmergeCells: "Tách ô",
1841
+ columnWidth: "Chiều rộng cột",
1842
+ rowHeight: "Chiều cao hàng",
1843
+ tableBorderWidth: "Độ rộng viền bảng",
1844
+ deleteTable: "Xóa bảng",
1845
+ columnWidthPx: "Chiều rộng cột (px)",
1846
+ rowHeightPx: "Chiều cao hàng (px)",
1847
+ tableBorderWidthPx: "Độ rộng viền bảng (px)",
1848
+ cancelBtn: "Hủy",
1849
+ applyBtn: "Áp dụng"
1850
+ },
1851
+ video: {
1852
+ ariaLabel: "Hành động video",
1853
+ label: "Video",
1854
+ floatLeft: "Nổi trái",
1855
+ noFloat: "Không nổi",
1856
+ alignCenter: "Căn giữa",
1857
+ floatRight: "Nổi phải",
1858
+ originalSize: "Kích thước gốc",
1859
+ previewVideo: "Xem trước",
1860
+ exitPreview: "Thoát xem trước",
1861
+ deleteVideo: "Xóa video"
1862
+ }
1863
+ },
1864
+ errors: {
1865
+ 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.`,
1866
+ imageSize: (maxSize) => `Tệp hình ảnh quá lớn. Kích thước tối đa cho phép là ${maxSize} MB.`
1867
+ }
1868
+ },
1869
+ ja: {
1870
+ toolbar: {
1871
+ bold: "太字 (Ctrl+B)",
1872
+ italic: "斜体 (Ctrl+I)",
1873
+ underline: "下線 (Ctrl+U)",
1874
+ strikethrough: "取り消し線",
1875
+ superscript: "上付き文字",
1876
+ subscript: "下付き文字",
1877
+ alignLeft: "左揃え",
1878
+ alignCenter: "中央揃え",
1879
+ alignRight: "右揃え",
1880
+ alignJustify: "両端揃え",
1881
+ ul: "箇条書きリスト",
1882
+ ol: "番号付きリスト",
1883
+ checklist: "チェックリスト",
1884
+ indent: "インデントを増やす",
1885
+ outdent: "インデントを減らす",
1886
+ undo: "元に戻す (Ctrl+Z)",
1887
+ redo: "やり直す (Ctrl+Y)",
1888
+ hr: "水平線",
1889
+ link: "リンクを挿入",
1890
+ image: "画像を挿入",
1891
+ video: "動画を挿入",
1892
+ emoji: "絵文字を挿入",
1893
+ icon: "FAアイコンを挿入",
1894
+ table: "テーブルを挿入",
1895
+ fontSize: "フォントサイズ",
1896
+ fontSizePlaceholder: "サイズ",
1897
+ removeFormat: "書式をリセット",
1898
+ direction: "文字方向を切り替え (LTR / RTL)",
1899
+ fontFamily: "フォント",
1900
+ paragraphStyle: "段落スタイル",
1901
+ paragraphStylePlaceholder: "スタイル",
1902
+ lineHeight: "行の高さ",
1903
+ lineHeightPlaceholder: "↕ 行間",
1904
+ codeview: "HTMLソースを表示",
1905
+ fullscreen: "全画面表示",
1906
+ shortcuts: "キーボードショートカット (Ctrl+Shift+/)",
1907
+ find: "検索 (Ctrl+F)",
1908
+ findReplace: "検索と置換 (Ctrl+H)",
1909
+ inlineCode: "インラインコード (Ctrl+`)",
1910
+ print: "印刷",
1911
+ foreColor: "文字色",
1912
+ backColor: "ハイライト色",
1913
+ chooseTextColor: "文字色を選択",
1914
+ chooseHighlightColor: "ハイライト色を選択",
1915
+ customColor: "カスタムカラー",
1916
+ insertTableLabel: "テーブルを挿入",
1917
+ paragraphItems: {
1918
+ p: "標準",
1919
+ blockquote: "引用",
1920
+ pre: "コード"
1921
+ }
1922
+ },
1923
+ linkDialog: {
1924
+ ariaLabel: "リンクを挿入",
1925
+ title: "リンクを挿入",
1926
+ url: "URL",
1927
+ urlPlaceholder: "https://",
1928
+ displayText: "表示テキスト",
1929
+ textPlaceholder: "リンクテキスト",
1930
+ openInNewTab: "新しいタブで開く",
1931
+ insertBtn: "挿入",
1932
+ cancelBtn: "キャンセル"
1933
+ },
1934
+ imageDialog: {
1935
+ ariaLabel: "画像を挿入",
1936
+ title: "画像を挿入",
1937
+ imageUrl: "画像URL",
1938
+ urlPlaceholder: "https://example.com/image.png",
1939
+ altText: "代替テキスト",
1940
+ altPlaceholder: "画像の説明",
1941
+ alignment: "配置",
1942
+ alignNone: "なし",
1943
+ alignLeft: "左",
1944
+ alignCenter: "中央",
1945
+ alignRight: "右",
1946
+ uploadLabel: "ファイルをアップロード",
1947
+ insertBtn: "挿入",
1948
+ cancelBtn: "キャンセル"
1949
+ },
1950
+ videoDialog: {
1951
+ ariaLabel: "動画を挿入",
1952
+ title: "動画を挿入",
1953
+ videoUrl: "動画URL",
1954
+ urlPlaceholder: "YouTube、Vimeo、または直接動画URL",
1955
+ widthLabel: "幅 (px)",
1956
+ widthPlaceholder: "560",
1957
+ insertBtn: "挿入",
1958
+ cancelBtn: "キャンセル",
1959
+ detected: (type) => `検出: ${type}`,
1960
+ unknownFormat: "不明な形式 — 直接動画埋め込みを試みます",
1961
+ invalidUrl: "URLが無効です — 有効な動画URLを入力してください。"
1962
+ },
1963
+ emojiDialog: {
1964
+ ariaLabel: "絵文字を挿入",
1965
+ title: "絵文字を挿入",
1966
+ searchPlaceholder: "絵文字を検索…",
1967
+ all: "すべて",
1968
+ cancelBtn: "キャンセル",
1969
+ close: "閉じる",
1970
+ categories: {
1971
+ smileys: "顔文字",
1972
+ people: "人物",
1973
+ animals: "動物",
1974
+ food: "食べ物",
1975
+ travel: "旅行",
1976
+ objects: "モノ",
1977
+ symbols: "記号"
1978
+ }
1979
+ },
1980
+ iconDialog: {
1981
+ ariaLabel: "FAアイコンを挿入",
1982
+ title: "FAアイコンを挿入",
1983
+ searchPlaceholder: "アイコンを検索…",
1984
+ all: "すべて",
1985
+ style: "スタイル",
1986
+ size: "サイズ",
1987
+ color: "カラー",
1988
+ useColor: " カラーを使用",
1989
+ selectHint: "アイコンを選択してください",
1990
+ insertBtn: "FAアイコンを挿入",
1991
+ cancelBtn: "キャンセル",
1992
+ close: "閉じる",
1993
+ categories: {
1994
+ popular: "人気",
1995
+ interface: "インターフェース",
1996
+ navigation: "ナビゲーション",
1997
+ media: "メディア",
1998
+ communication: "通信",
1999
+ files: "ファイル",
2000
+ people: "人物",
2001
+ objects: "モノ"
2002
+ }
2003
+ },
2004
+ findReplace: {
2005
+ findTitle: "検索",
2006
+ findReplaceTitle: "検索と置換",
2007
+ findPlaceholder: "検索…",
2008
+ searchAriaLabel: "検索テキスト",
2009
+ caseSensitive: "\xA0大文字/小文字を区別",
2010
+ prevBtn: "← 前へ",
2011
+ nextBtn: "次へ →",
2012
+ replacePlaceholder: "置換後…",
2013
+ replaceAriaLabel: "置換後のテキスト",
2014
+ replaceBtn: "置換",
2015
+ replaceAllBtn: "すべて置換",
2016
+ close: "×"
2017
+ },
2018
+ shortcutsDialog: {
2019
+ title: "キーボードショートカット",
2020
+ ariaLabel: "キーボードショートカット",
2021
+ close: "閉じる",
2022
+ shortcuts: [
2023
+ {
2024
+ category: "テキスト書式",
2025
+ items: [
2026
+ {
2027
+ keys: "Ctrl + B",
2028
+ action: "太字"
2029
+ },
2030
+ {
2031
+ keys: "Ctrl + I",
2032
+ action: "斜体"
2033
+ },
2034
+ {
2035
+ keys: "Ctrl + U",
2036
+ action: "下線"
2037
+ },
2038
+ {
2039
+ keys: "Ctrl + K",
2040
+ action: "リンクの挿入 / 編集"
2041
+ }
2042
+ ]
2043
+ },
2044
+ {
2045
+ category: "履歴",
2046
+ items: [{
2047
+ keys: "Ctrl + Z",
2048
+ action: "元に戻す"
2049
+ }, {
2050
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
2051
+ action: "やり直す"
2052
+ }]
2053
+ },
2054
+ {
2055
+ category: "選択と移動",
2056
+ items: [
2057
+ {
2058
+ keys: "Ctrl + A",
2059
+ action: "すべて選択"
2060
+ },
2061
+ {
2062
+ keys: "Tab",
2063
+ action: "インデント増加 / スペース挿入"
2064
+ },
2065
+ {
2066
+ keys: "Shift + Tab",
2067
+ action: "インデント減少"
2068
+ }
2069
+ ]
2070
+ },
2071
+ {
2072
+ category: "クリップボード",
2073
+ items: [{
2074
+ keys: "Ctrl + Shift + V",
2075
+ action: "プレーンテキストとして貼り付け"
2076
+ }]
2077
+ },
2078
+ {
2079
+ category: "検索と置換",
2080
+ items: [{
2081
+ keys: "Ctrl + F",
2082
+ action: "文書内を検索"
2083
+ }, {
2084
+ keys: "Ctrl + H",
2085
+ action: "検索と置換"
2086
+ }]
2087
+ },
2088
+ {
2089
+ category: "エディター",
2090
+ items: [{
2091
+ keys: "Ctrl + Shift + /",
2092
+ action: "ショートカットダイアログを表示"
2093
+ }]
2094
+ }
2095
+ ]
2096
+ },
2097
+ contextMenu: {
2098
+ cut: "切り取り",
2099
+ copy: "コピー",
2100
+ paste: "貼り付け",
2101
+ bold: "太字",
2102
+ italic: "斜体",
2103
+ underline: "下線",
2104
+ textColor: "文字色",
2105
+ highlightColor: "ハイライト色",
2106
+ copyFormat: "書式をコピー",
2107
+ pasteFormat: "書式を貼り付け",
2108
+ removeFormat: "書式をリセット",
2109
+ link: "リンクを挿入",
2110
+ image: "画像を挿入",
2111
+ video: "動画を挿入",
2112
+ table: "テーブルを挿入",
2113
+ back: "戻る",
2114
+ noHighlight: "ハイライトなし",
2115
+ customColor: "カスタムカラー",
2116
+ customColorLabel: "カスタム…"
2117
+ },
2118
+ statusbar: {
2119
+ resizeHandle: "ドラッグしてリサイズ",
2120
+ words: (n) => `単語数: ${n}`,
2121
+ wordsLimit: (n, max) => `単語数: ${n}/${max}`,
2122
+ chars: (n) => `文字数: ${n}`,
2123
+ charsLimit: (n, max) => `文字数: ${n}/${max}`
2124
+ },
2125
+ tooltips: {
2126
+ link: {
2127
+ ariaLabel: "リンク操作",
2128
+ openLink: "リンクを開く",
2129
+ copyUrl: "URLをコピー",
2130
+ editLink: "リンクを編集",
2131
+ removeLink: "リンクを削除"
2132
+ },
2133
+ image: {
2134
+ ariaLabel: "画像操作",
2135
+ label: "画像",
2136
+ floatLeft: "左に回り込み",
2137
+ noFloat: "回り込みなし",
2138
+ alignCenter: "中央揃え",
2139
+ floatRight: "右に回り込み",
2140
+ originalSize: "元のサイズ",
2141
+ rotateLeft: "左に回転",
2142
+ rotateRight: "右に回転",
2143
+ cropImage: "画像をトリミング",
2144
+ addCaption: "キャプションを追加 / 編集",
2145
+ deleteImage: "画像を削除"
2146
+ },
2147
+ code: {
2148
+ ariaLabel: "コードブロック操作",
2149
+ label: "コード",
2150
+ syntaxLanguage: "言語",
2151
+ syntaxAriaLabel: "構文言語",
2152
+ copyCode: "コードをコピー",
2153
+ toggleWordWrap: "折り返しを切り替え",
2154
+ enableWordWrap: "折り返しを有効にする",
2155
+ disableWordWrap: "折り返しを無効にする",
2156
+ convertToParagraph: "段落に変換",
2157
+ deleteCodeBlock: "コードブロックを削除"
2158
+ },
2159
+ table: {
2160
+ ariaLabel: "テーブル操作",
2161
+ label: "テーブル",
2162
+ selectCells: "セルを選択",
2163
+ addRowAbove: "上に行を追加",
2164
+ addRowBelow: "下に行を追加",
2165
+ deleteRow: "行を削除",
2166
+ addColumnLeft: "左に列を追加",
2167
+ addColumnRight: "右に列を追加",
2168
+ deleteColumn: "列を削除",
2169
+ mergeCells: "セルを結合",
2170
+ unmergeCells: "セルの結合を解除",
2171
+ columnWidth: "列幅",
2172
+ rowHeight: "行の高さ",
2173
+ tableBorderWidth: "テーブルの枠幅",
2174
+ deleteTable: "テーブルを削除",
2175
+ columnWidthPx: "列幅 (px)",
2176
+ rowHeightPx: "行の高さ (px)",
2177
+ tableBorderWidthPx: "テーブルの枠幅 (px)",
2178
+ cancelBtn: "キャンセル",
2179
+ applyBtn: "適用"
2180
+ },
2181
+ video: {
2182
+ ariaLabel: "動画操作",
2183
+ label: "動画",
2184
+ floatLeft: "左に回り込み",
2185
+ noFloat: "回り込みなし",
2186
+ alignCenter: "中央揃え",
2187
+ floatRight: "右に回り込み",
2188
+ originalSize: "元のサイズ",
2189
+ previewVideo: "プレビュー",
2190
+ exitPreview: "プレビューを終了",
2191
+ deleteVideo: "動画を削除"
2192
+ }
2193
+ },
2194
+ errors: {
2195
+ imageFormat: (type) => `形式 "${type}" はブラウザでの表示をサポートしていません。JPEG、PNG、または WebP に変換してください。`,
2196
+ imageSize: (maxSize) => `画像ファイルが大きすぎます。最大許容サイズは ${maxSize} MB です。`
2197
+ }
2198
+ },
2199
+ zh: {
2200
+ toolbar: {
2201
+ bold: "粗体 (Ctrl+B)",
2202
+ italic: "斜体 (Ctrl+I)",
2203
+ underline: "下划线 (Ctrl+U)",
2204
+ strikethrough: "删除线",
2205
+ superscript: "上标",
2206
+ subscript: "下标",
2207
+ alignLeft: "左对齐",
2208
+ alignCenter: "居中",
2209
+ alignRight: "右对齐",
2210
+ alignJustify: "两端对齐",
2211
+ ul: "无序列表",
2212
+ ol: "有序列表",
2213
+ checklist: "待办列表",
2214
+ indent: "增加缩进",
2215
+ outdent: "减少缩进",
2216
+ undo: "撤销 (Ctrl+Z)",
2217
+ redo: "重做 (Ctrl+Y)",
2218
+ hr: "水平分割线",
2219
+ link: "插入链接",
2220
+ image: "插入图片",
2221
+ video: "插入视频",
2222
+ emoji: "插入表情",
2223
+ icon: "插入 FA 图标",
2224
+ table: "插入表格",
2225
+ fontSize: "字号",
2226
+ fontSizePlaceholder: "大小",
2227
+ removeFormat: "清除格式",
2228
+ direction: "切换文字方向 (LTR / RTL)",
2229
+ fontFamily: "字体",
2230
+ paragraphStyle: "段落样式",
2231
+ paragraphStylePlaceholder: "样式",
2232
+ lineHeight: "行高",
2233
+ lineHeightPlaceholder: "↕ 行距",
2234
+ codeview: "查看 HTML 源码",
2235
+ fullscreen: "全屏",
2236
+ shortcuts: "键盘快捷键 (Ctrl+Shift+/)",
2237
+ find: "搜索 (Ctrl+F)",
2238
+ findReplace: "查找和替换 (Ctrl+H)",
2239
+ inlineCode: "行内代码 (Ctrl+`)",
2240
+ print: "打印",
2241
+ foreColor: "文字颜色",
2242
+ backColor: "高亮颜色",
2243
+ chooseTextColor: "选择文字颜色",
2244
+ chooseHighlightColor: "选择高亮颜色",
2245
+ customColor: "自定义颜色",
2246
+ insertTableLabel: "插入表格",
2247
+ paragraphItems: {
2248
+ p: "正文",
2249
+ blockquote: "引用",
2250
+ pre: "代码"
2251
+ }
2252
+ },
2253
+ linkDialog: {
2254
+ ariaLabel: "插入链接",
2255
+ title: "插入链接",
2256
+ url: "链接地址",
2257
+ urlPlaceholder: "https://",
2258
+ displayText: "显示文字",
2259
+ textPlaceholder: "链接文字",
2260
+ openInNewTab: "在新标签页中打开",
2261
+ insertBtn: "插入",
2262
+ cancelBtn: "取消"
2263
+ },
2264
+ imageDialog: {
2265
+ ariaLabel: "插入图片",
2266
+ title: "插入图片",
2267
+ imageUrl: "图片地址",
2268
+ urlPlaceholder: "https://example.com/image.png",
2269
+ altText: "替代文字",
2270
+ altPlaceholder: "图片描述",
2271
+ alignment: "对齐方式",
2272
+ alignNone: "无",
2273
+ alignLeft: "左",
2274
+ alignCenter: "居中",
2275
+ alignRight: "右",
2276
+ uploadLabel: "或上传文件",
2277
+ insertBtn: "插入",
2278
+ cancelBtn: "取消"
2279
+ },
2280
+ videoDialog: {
2281
+ ariaLabel: "插入视频",
2282
+ title: "插入视频",
2283
+ videoUrl: "视频地址",
2284
+ urlPlaceholder: "YouTube、Vimeo 或直接 .mp4 链接",
2285
+ widthLabel: "宽度 (px)",
2286
+ widthPlaceholder: "560",
2287
+ insertBtn: "插入",
2288
+ cancelBtn: "取消",
2289
+ detected: (type) => `已识别: ${type}`,
2290
+ unknownFormat: "未知格式 — 将尝试直接嵌入视频",
2291
+ invalidUrl: "URL 无效 — 请输入有效的视频链接。"
2292
+ },
2293
+ emojiDialog: {
2294
+ ariaLabel: "插入表情",
2295
+ title: "插入表情",
2296
+ searchPlaceholder: "搜索表情…",
2297
+ all: "全部",
2298
+ cancelBtn: "取消",
2299
+ close: "关闭",
2300
+ categories: {
2301
+ smileys: "笑脸",
2302
+ people: "人物",
2303
+ animals: "动物",
2304
+ food: "食物",
2305
+ travel: "旅行",
2306
+ objects: "物品",
2307
+ symbols: "符号"
2308
+ }
2309
+ },
2310
+ iconDialog: {
2311
+ ariaLabel: "插入 FA 图标",
2312
+ title: "插入 FA 图标",
2313
+ searchPlaceholder: "搜索图标…",
2314
+ all: "全部",
2315
+ style: "样式",
2316
+ size: "大小",
2317
+ color: "颜色",
2318
+ useColor: " 使用颜色",
2319
+ selectHint: "请选择一个图标",
2320
+ insertBtn: "插入 FA 图标",
2321
+ cancelBtn: "取消",
2322
+ close: "关闭",
2323
+ categories: {
2324
+ popular: "热门",
2325
+ interface: "界面",
2326
+ navigation: "导航",
2327
+ media: "媒体",
2328
+ communication: "通讯",
2329
+ files: "文件",
2330
+ people: "人物",
2331
+ objects: "物品"
2332
+ }
2333
+ },
2334
+ findReplace: {
2335
+ findTitle: "搜索",
2336
+ findReplaceTitle: "查找和替换",
2337
+ findPlaceholder: "查找…",
2338
+ searchAriaLabel: "搜索文字",
2339
+ caseSensitive: "\xA0区分大小写",
2340
+ prevBtn: "← 上一个",
2341
+ nextBtn: "下一个 →",
2342
+ replacePlaceholder: "替换为…",
2343
+ replaceAriaLabel: "替换为",
2344
+ replaceBtn: "替换",
2345
+ replaceAllBtn: "全部替换",
2346
+ close: "×"
2347
+ },
2348
+ shortcutsDialog: {
2349
+ title: "键盘快捷键",
2350
+ ariaLabel: "键盘快捷键",
2351
+ close: "关闭",
2352
+ shortcuts: [
2353
+ {
2354
+ category: "文字格式",
2355
+ items: [
2356
+ {
2357
+ keys: "Ctrl + B",
2358
+ action: "粗体"
2359
+ },
2360
+ {
2361
+ keys: "Ctrl + I",
2362
+ action: "斜体"
2363
+ },
2364
+ {
2365
+ keys: "Ctrl + U",
2366
+ action: "下划线"
2367
+ },
2368
+ {
2369
+ keys: "Ctrl + K",
2370
+ action: "插入 / 编辑链接"
2371
+ }
2372
+ ]
2373
+ },
2374
+ {
2375
+ category: "历史记录",
2376
+ items: [{
2377
+ keys: "Ctrl + Z",
2378
+ action: "撤销"
2379
+ }, {
2380
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
2381
+ action: "重做"
2382
+ }]
2383
+ },
2384
+ {
2385
+ category: "选择与导航",
2386
+ items: [
2387
+ {
2388
+ keys: "Ctrl + A",
2389
+ action: "全选"
2390
+ },
2391
+ {
2392
+ keys: "Tab",
2393
+ action: "增加缩进 / 插入空格"
2394
+ },
2395
+ {
2396
+ keys: "Shift + Tab",
2397
+ action: "减少缩进"
2398
+ }
2399
+ ]
2400
+ },
2401
+ {
2402
+ category: "剪贴板",
2403
+ items: [{
2404
+ keys: "Ctrl + Shift + V",
2405
+ action: "粘贴为纯文本"
2406
+ }]
2407
+ },
2408
+ {
2409
+ category: "查找和替换",
2410
+ items: [{
2411
+ keys: "Ctrl + F",
2412
+ action: "在文档中搜索"
2413
+ }, {
2414
+ keys: "Ctrl + H",
2415
+ action: "查找和替换"
2416
+ }]
2417
+ },
2418
+ {
2419
+ category: "编辑器",
2420
+ items: [{
2421
+ keys: "Ctrl + Shift + /",
2422
+ action: "显示快捷键对话框"
2423
+ }]
2424
+ }
2425
+ ]
2426
+ },
2427
+ contextMenu: {
2428
+ cut: "剪切",
2429
+ copy: "复制",
2430
+ paste: "粘贴",
2431
+ bold: "粗体",
2432
+ italic: "斜体",
2433
+ underline: "下划线",
2434
+ textColor: "文字颜色",
2435
+ highlightColor: "高亮颜色",
2436
+ copyFormat: "复制格式",
2437
+ pasteFormat: "粘贴格式",
2438
+ removeFormat: "清除格式",
2439
+ link: "插入链接",
2440
+ image: "插入图片",
2441
+ video: "插入视频",
2442
+ table: "插入表格",
2443
+ back: "返回",
2444
+ noHighlight: "无高亮",
2445
+ customColor: "自定义颜色",
2446
+ customColorLabel: "自定义…"
2447
+ },
2448
+ statusbar: {
2449
+ resizeHandle: "拖动以调整大小",
2450
+ words: (n) => `字数: ${n}`,
2451
+ wordsLimit: (n, max) => `字数: ${n}/${max}`,
2452
+ chars: (n) => `字符数: ${n}`,
2453
+ charsLimit: (n, max) => `字符数: ${n}/${max}`
2454
+ },
2455
+ tooltips: {
2456
+ link: {
2457
+ ariaLabel: "链接操作",
2458
+ openLink: "打开链接",
2459
+ copyUrl: "复制 URL",
2460
+ editLink: "编辑链接",
2461
+ removeLink: "删除链接"
2462
+ },
2463
+ image: {
2464
+ ariaLabel: "图片操作",
2465
+ label: "图片",
2466
+ floatLeft: "左浮动",
2467
+ noFloat: "不浮动",
2468
+ alignCenter: "居中对齐",
2469
+ floatRight: "右浮动",
2470
+ originalSize: "原始尺寸",
2471
+ rotateLeft: "向左旋转",
2472
+ rotateRight: "向右旋转",
2473
+ cropImage: "裁剪图片",
2474
+ addCaption: "添加 / 编辑说明",
2475
+ deleteImage: "删除图片"
2476
+ },
2477
+ code: {
2478
+ ariaLabel: "代码块操作",
2479
+ label: "代码",
2480
+ syntaxLanguage: "语法语言",
2481
+ syntaxAriaLabel: "语法语言",
2482
+ copyCode: "复制代码",
2483
+ toggleWordWrap: "切换自动换行",
2484
+ enableWordWrap: "启用自动换行",
2485
+ disableWordWrap: "禁用自动换行",
2486
+ convertToParagraph: "转换为段落",
2487
+ deleteCodeBlock: "删除代码块"
2488
+ },
2489
+ table: {
2490
+ ariaLabel: "表格操作",
2491
+ label: "表格",
2492
+ selectCells: "选择单元格",
2493
+ addRowAbove: "在上方插入行",
2494
+ addRowBelow: "在下方插入行",
2495
+ deleteRow: "删除行",
2496
+ addColumnLeft: "在左侧插入列",
2497
+ addColumnRight: "在右侧插入列",
2498
+ deleteColumn: "删除列",
2499
+ mergeCells: "合并单元格",
2500
+ unmergeCells: "拆分单元格",
2501
+ columnWidth: "列宽",
2502
+ rowHeight: "行高",
2503
+ tableBorderWidth: "表格边框宽度",
2504
+ deleteTable: "删除表格",
2505
+ columnWidthPx: "列宽 (px)",
2506
+ rowHeightPx: "行高 (px)",
2507
+ tableBorderWidthPx: "表格边框宽度 (px)",
2508
+ cancelBtn: "取消",
2509
+ applyBtn: "应用"
2510
+ },
2511
+ video: {
2512
+ ariaLabel: "视频操作",
2513
+ label: "视频",
2514
+ floatLeft: "左浮动",
2515
+ noFloat: "不浮动",
2516
+ alignCenter: "居中对齐",
2517
+ floatRight: "右浮动",
2518
+ originalSize: "原始尺寸",
2519
+ previewVideo: "预览视频",
2520
+ exitPreview: "退出预览",
2521
+ deleteVideo: "删除视频"
2522
+ }
2523
+ },
2524
+ errors: {
2525
+ imageFormat: (type) => `格式 "${type}" 不支持在浏览器中显示。请转换为 JPEG、PNG 或 WebP。`,
2526
+ imageSize: (maxSize) => `图片文件过大。最大允许大小为 ${maxSize} MB。`
2527
+ }
2528
+ },
2529
+ fr: {
2530
+ toolbar: {
2531
+ bold: "Gras (Ctrl+B)",
2532
+ italic: "Italique (Ctrl+I)",
2533
+ underline: "Souligné (Ctrl+U)",
2534
+ strikethrough: "Barré",
2535
+ superscript: "Exposant",
2536
+ subscript: "Indice",
2537
+ alignLeft: "Aligner à gauche",
2538
+ alignCenter: "Centrer",
2539
+ alignRight: "Aligner à droite",
2540
+ alignJustify: "Justifier",
2541
+ ul: "Liste à puces",
2542
+ ol: "Liste numérotée",
2543
+ checklist: "Liste de tâches",
2544
+ indent: "Augmenter le retrait",
2545
+ outdent: "Diminuer le retrait",
2546
+ undo: "Annuler (Ctrl+Z)",
2547
+ redo: "Rétablir (Ctrl+Y)",
2548
+ hr: "Ligne horizontale",
2549
+ link: "Insérer un lien",
2550
+ image: "Insérer une image",
2551
+ video: "Insérer une vidéo",
2552
+ emoji: "Insérer un emoji",
2553
+ icon: "Insérer une icône FA",
2554
+ table: "Insérer un tableau",
2555
+ fontSize: "Taille de police",
2556
+ fontSizePlaceholder: "Taille",
2557
+ removeFormat: "Effacer la mise en forme",
2558
+ direction: "Basculer la direction du texte (LTR / RTL)",
2559
+ fontFamily: "Police",
2560
+ paragraphStyle: "Style de paragraphe",
2561
+ paragraphStylePlaceholder: "Style",
2562
+ lineHeight: "Interligne",
2563
+ lineHeightPlaceholder: "↕ Ligne",
2564
+ codeview: "Afficher le code HTML",
2565
+ fullscreen: "Plein écran",
2566
+ shortcuts: "Raccourcis clavier (Ctrl+Shift+/)",
2567
+ find: "Rechercher (Ctrl+F)",
2568
+ findReplace: "Rechercher et remplacer (Ctrl+H)",
2569
+ inlineCode: "Code inline (Ctrl+`)",
2570
+ print: "Imprimer",
2571
+ foreColor: "Couleur du texte",
2572
+ backColor: "Couleur de surbrillance",
2573
+ chooseTextColor: "Choisir la couleur du texte",
2574
+ chooseHighlightColor: "Choisir la couleur de surbrillance",
2575
+ customColor: "Couleur personnalisée",
2576
+ insertTableLabel: "Insérer un tableau",
2577
+ paragraphItems: {
2578
+ p: "Normal",
2579
+ blockquote: "Citation",
2580
+ pre: "Code"
2581
+ }
2582
+ },
2583
+ linkDialog: {
2584
+ ariaLabel: "Insérer un lien",
2585
+ title: "Insérer un lien",
2586
+ url: "URL",
2587
+ urlPlaceholder: "https://",
2588
+ displayText: "Texte affiché",
2589
+ textPlaceholder: "Texte du lien",
2590
+ openInNewTab: "Ouvrir dans un nouvel onglet",
2591
+ insertBtn: "Insérer",
2592
+ cancelBtn: "Annuler"
2593
+ },
2594
+ imageDialog: {
2595
+ ariaLabel: "Insérer une image",
2596
+ title: "Insérer une image",
2597
+ imageUrl: "URL de l'image",
2598
+ urlPlaceholder: "https://example.com/image.png",
2599
+ altText: "Texte alternatif",
2600
+ altPlaceholder: "Description de l'image",
2601
+ alignment: "Alignement",
2602
+ alignNone: "Aucun",
2603
+ alignLeft: "Gauche",
2604
+ alignCenter: "Centre",
2605
+ alignRight: "Droite",
2606
+ uploadLabel: "Ou téléverser un fichier",
2607
+ insertBtn: "Insérer",
2608
+ cancelBtn: "Annuler"
2609
+ },
2610
+ videoDialog: {
2611
+ ariaLabel: "Insérer une vidéo",
2612
+ title: "Insérer une vidéo",
2613
+ videoUrl: "URL de la vidéo",
2614
+ urlPlaceholder: "YouTube, Vimeo ou URL .mp4 directe",
2615
+ widthLabel: "Largeur (px)",
2616
+ widthPlaceholder: "560",
2617
+ insertBtn: "Insérer",
2618
+ cancelBtn: "Annuler",
2619
+ detected: (type) => `Détecté\u00a0: ${type}`,
2620
+ unknownFormat: "Format inconnu — tentative d'intégration directe",
2621
+ invalidUrl: "URL invalide — veuillez saisir un lien vidéo valide."
2622
+ },
2623
+ emojiDialog: {
2624
+ ariaLabel: "Insérer un emoji",
2625
+ title: "Insérer un emoji",
2626
+ searchPlaceholder: "Rechercher des emojis…",
2627
+ all: "Tout",
2628
+ cancelBtn: "Annuler",
2629
+ close: "Fermer",
2630
+ categories: {
2631
+ smileys: "Smileys",
2632
+ people: "Personnes",
2633
+ animals: "Animaux",
2634
+ food: "Nourriture",
2635
+ travel: "Voyage",
2636
+ objects: "Objets",
2637
+ symbols: "Symboles"
2638
+ }
2639
+ },
2640
+ iconDialog: {
2641
+ ariaLabel: "Insérer une icône FA",
2642
+ title: "Insérer une icône FA",
2643
+ searchPlaceholder: "Rechercher des icônes…",
2644
+ all: "Tout",
2645
+ style: "Style",
2646
+ size: "Taille",
2647
+ color: "Couleur",
2648
+ useColor: " Utiliser la couleur",
2649
+ selectHint: "Sélectionner une icône",
2650
+ insertBtn: "Insérer une icône FA",
2651
+ cancelBtn: "Annuler",
2652
+ close: "Fermer",
2653
+ categories: {
2654
+ popular: "Populaire",
2655
+ interface: "Interface",
2656
+ navigation: "Navigation",
2657
+ media: "Média",
2658
+ communication: "Communication",
2659
+ files: "Fichiers",
2660
+ people: "Personnes",
2661
+ objects: "Objets"
2662
+ }
2663
+ },
2664
+ findReplace: {
2665
+ findTitle: "Rechercher",
2666
+ findReplaceTitle: "Rechercher et remplacer",
2667
+ findPlaceholder: "Rechercher…",
2668
+ searchAriaLabel: "Texte à rechercher",
2669
+ caseSensitive: "\xA0Respecter la casse",
2670
+ prevBtn: "← Préc.",
2671
+ nextBtn: "Suiv. →",
2672
+ replacePlaceholder: "Remplacer par…",
2673
+ replaceAriaLabel: "Remplacer par",
2674
+ replaceBtn: "Remplacer",
2675
+ replaceAllBtn: "Tout remplacer",
2676
+ close: "×"
2677
+ },
2678
+ shortcutsDialog: {
2679
+ title: "Raccourcis clavier",
2680
+ ariaLabel: "Raccourcis clavier",
2681
+ close: "Fermer",
2682
+ shortcuts: [
2683
+ {
2684
+ category: "Mise en forme du texte",
2685
+ items: [
2686
+ {
2687
+ keys: "Ctrl + B",
2688
+ action: "Gras"
2689
+ },
2690
+ {
2691
+ keys: "Ctrl + I",
2692
+ action: "Italique"
2693
+ },
2694
+ {
2695
+ keys: "Ctrl + U",
2696
+ action: "Souligné"
2697
+ },
2698
+ {
2699
+ keys: "Ctrl + K",
2700
+ action: "Insérer / modifier un lien"
2701
+ }
2702
+ ]
2703
+ },
2704
+ {
2705
+ category: "Historique",
2706
+ items: [{
2707
+ keys: "Ctrl + Z",
2708
+ action: "Annuler"
2709
+ }, {
2710
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
2711
+ action: "Rétablir"
2712
+ }]
2713
+ },
2714
+ {
2715
+ category: "Sélection et navigation",
2716
+ items: [
2717
+ {
2718
+ keys: "Ctrl + A",
2719
+ action: "Tout sélectionner"
2720
+ },
2721
+ {
2722
+ keys: "Tab",
2723
+ action: "Augmenter le retrait / insérer des espaces"
2724
+ },
2725
+ {
2726
+ keys: "Shift + Tab",
2727
+ action: "Diminuer le retrait"
2728
+ }
2729
+ ]
2730
+ },
2731
+ {
2732
+ category: "Presse-papiers",
2733
+ items: [{
2734
+ keys: "Ctrl + Shift + V",
2735
+ action: "Coller en texte brut"
2736
+ }]
2737
+ },
2738
+ {
2739
+ category: "Rechercher et remplacer",
2740
+ items: [{
2741
+ keys: "Ctrl + F",
2742
+ action: "Rechercher dans le document"
2743
+ }, {
2744
+ keys: "Ctrl + H",
2745
+ action: "Rechercher et remplacer"
2746
+ }]
2747
+ },
2748
+ {
2749
+ category: "Éditeur",
2750
+ items: [{
2751
+ keys: "Ctrl + Shift + /",
2752
+ action: "Afficher les raccourcis clavier"
2753
+ }]
2754
+ }
2755
+ ]
2756
+ },
2757
+ contextMenu: {
2758
+ cut: "Couper",
2759
+ copy: "Copier",
2760
+ paste: "Coller",
2761
+ bold: "Gras",
2762
+ italic: "Italique",
2763
+ underline: "Souligné",
2764
+ textColor: "Couleur du texte",
2765
+ highlightColor: "Couleur de surbrillance",
2766
+ copyFormat: "Copier la mise en forme",
2767
+ pasteFormat: "Coller la mise en forme",
2768
+ removeFormat: "Effacer la mise en forme",
2769
+ link: "Insérer un lien",
2770
+ image: "Insérer une image",
2771
+ video: "Insérer une vidéo",
2772
+ table: "Insérer un tableau",
2773
+ back: "Retour",
2774
+ noHighlight: "Sans surbrillance",
2775
+ customColor: "Couleur personnalisée",
2776
+ customColorLabel: "Personnaliser…"
2777
+ },
2778
+ statusbar: {
2779
+ resizeHandle: "Faire glisser pour redimensionner",
2780
+ words: (n) => `Mots\u00a0: ${n}`,
2781
+ wordsLimit: (n, max) => `Mots\u00a0: ${n}/${max}`,
2782
+ chars: (n) => `Caractères\u00a0: ${n}`,
2783
+ charsLimit: (n, max) => `Caractères\u00a0: ${n}/${max}`
2784
+ },
2785
+ tooltips: {
2786
+ link: {
2787
+ ariaLabel: "Actions du lien",
2788
+ openLink: "Ouvrir le lien",
2789
+ copyUrl: "Copier l'URL",
2790
+ editLink: "Modifier le lien",
2791
+ removeLink: "Supprimer le lien"
2792
+ },
2793
+ image: {
2794
+ ariaLabel: "Actions de l'image",
2795
+ label: "Image",
2796
+ floatLeft: "Flottant à gauche",
2797
+ noFloat: "Sans flottant",
2798
+ alignCenter: "Centré",
2799
+ floatRight: "Flottant à droite",
2800
+ originalSize: "Taille originale",
2801
+ rotateLeft: "Rotation à gauche",
2802
+ rotateRight: "Rotation à droite",
2803
+ cropImage: "Recadrer l'image",
2804
+ addCaption: "Ajouter / modifier la légende",
2805
+ deleteImage: "Supprimer l'image"
2806
+ },
2807
+ code: {
2808
+ ariaLabel: "Actions du bloc de code",
2809
+ label: "Code",
2810
+ syntaxLanguage: "Langage de syntaxe",
2811
+ syntaxAriaLabel: "Langage de syntaxe",
2812
+ copyCode: "Copier le code",
2813
+ toggleWordWrap: "Activer/désactiver le retour à la ligne",
2814
+ enableWordWrap: "Activer le retour à la ligne",
2815
+ disableWordWrap: "Désactiver le retour à la ligne",
2816
+ convertToParagraph: "Convertir en paragraphe",
2817
+ deleteCodeBlock: "Supprimer le bloc de code"
2818
+ },
2819
+ table: {
2820
+ ariaLabel: "Actions du tableau",
2821
+ label: "Tableau",
2822
+ selectCells: "Sélectionner des cellules",
2823
+ addRowAbove: "Ajouter une ligne au-dessus",
2824
+ addRowBelow: "Ajouter une ligne en-dessous",
2825
+ deleteRow: "Supprimer la ligne",
2826
+ addColumnLeft: "Ajouter une colonne à gauche",
2827
+ addColumnRight: "Ajouter une colonne à droite",
2828
+ deleteColumn: "Supprimer la colonne",
2829
+ mergeCells: "Fusionner les cellules",
2830
+ unmergeCells: "Scinder les cellules",
2831
+ columnWidth: "Largeur de colonne",
2832
+ rowHeight: "Hauteur de ligne",
2833
+ tableBorderWidth: "Épaisseur des bordures",
2834
+ deleteTable: "Supprimer le tableau",
2835
+ columnWidthPx: "Largeur de colonne (px)",
2836
+ rowHeightPx: "Hauteur de ligne (px)",
2837
+ tableBorderWidthPx: "Épaisseur des bordures (px)",
2838
+ cancelBtn: "Annuler",
2839
+ applyBtn: "Appliquer"
2840
+ },
2841
+ video: {
2842
+ ariaLabel: "Actions de la vidéo",
2843
+ label: "Vidéo",
2844
+ floatLeft: "Flottant à gauche",
2845
+ noFloat: "Sans flottant",
2846
+ alignCenter: "Centré",
2847
+ floatRight: "Flottant à droite",
2848
+ originalSize: "Taille originale",
2849
+ previewVideo: "Aperçu",
2850
+ exitPreview: "Quitter l'aperçu",
2851
+ deleteVideo: "Supprimer la vidéo"
2852
+ }
2853
+ },
2854
+ errors: {
2855
+ imageFormat: (type) => `Le format "${type}" n'est pas pris en charge par le navigateur. Veuillez le convertir en JPEG, PNG ou WebP.`,
2856
+ imageSize: (maxSize) => `Le fichier image est trop volumineux. La taille maximale autorisée est de ${maxSize}\u00a0Mo.`
2857
+ }
2858
+ },
2859
+ de: {
2860
+ toolbar: {
2861
+ bold: "Fett (Ctrl+B)",
2862
+ italic: "Kursiv (Ctrl+I)",
2863
+ underline: "Unterstrichen (Ctrl+U)",
2864
+ strikethrough: "Durchgestrichen",
2865
+ superscript: "Hochgestellt",
2866
+ subscript: "Tiefgestellt",
2867
+ alignLeft: "Linksbündig",
2868
+ alignCenter: "Zentriert",
2869
+ alignRight: "Rechtsbündig",
2870
+ alignJustify: "Blocksatz",
2871
+ ul: "Ungeordnete Liste",
2872
+ ol: "Geordnete Liste",
2873
+ checklist: "Checkliste",
2874
+ indent: "Einzug vergrößern",
2875
+ outdent: "Einzug verkleinern",
2876
+ undo: "Rückgängig (Ctrl+Z)",
2877
+ redo: "Wiederholen (Ctrl+Y)",
2878
+ hr: "Horizontale Linie",
2879
+ link: "Link einfügen",
2880
+ image: "Bild einfügen",
2881
+ video: "Video einfügen",
2882
+ emoji: "Emoji einfügen",
2883
+ icon: "FA-Symbol einfügen",
2884
+ table: "Tabelle einfügen",
2885
+ fontSize: "Schriftgröße",
2886
+ fontSizePlaceholder: "Größe",
2887
+ removeFormat: "Formatierung entfernen",
2888
+ direction: "Textrichtung umschalten (LTR / RTL)",
2889
+ fontFamily: "Schriftart",
2890
+ paragraphStyle: "Absatzstil",
2891
+ paragraphStylePlaceholder: "Stil",
2892
+ lineHeight: "Zeilenhöhe",
2893
+ lineHeightPlaceholder: "↕ Zeile",
2894
+ codeview: "HTML-Code-Ansicht",
2895
+ fullscreen: "Vollbild",
2896
+ shortcuts: "Tastenkürzel (Ctrl+Shift+/)",
2897
+ find: "Suchen (Ctrl+F)",
2898
+ findReplace: "Suchen & Ersetzen (Ctrl+H)",
2899
+ inlineCode: "Inline-Code (Ctrl+`)",
2900
+ print: "Drucken",
2901
+ foreColor: "Textfarbe",
2902
+ backColor: "Hervorhebungsfarbe",
2903
+ chooseTextColor: "Textfarbe auswählen",
2904
+ chooseHighlightColor: "Hervorhebungsfarbe auswählen",
2905
+ customColor: "Benutzerdefinierte Farbe",
2906
+ insertTableLabel: "Tabelle einfügen",
2907
+ paragraphItems: {
2908
+ p: "Normal",
2909
+ blockquote: "Zitat",
2910
+ pre: "Code"
2911
+ }
2912
+ },
2913
+ linkDialog: {
2914
+ ariaLabel: "Link einfügen",
2915
+ title: "Link einfügen",
2916
+ url: "URL",
2917
+ urlPlaceholder: "https://",
2918
+ displayText: "Anzeigetext",
2919
+ textPlaceholder: "Linktext",
2920
+ openInNewTab: "In neuem Tab öffnen",
2921
+ insertBtn: "Einfügen",
2922
+ cancelBtn: "Abbrechen"
2923
+ },
2924
+ imageDialog: {
2925
+ ariaLabel: "Bild einfügen",
2926
+ title: "Bild einfügen",
2927
+ imageUrl: "Bild-URL",
2928
+ urlPlaceholder: "https://example.com/image.png",
2929
+ altText: "Alt-Text",
2930
+ altPlaceholder: "Bild beschreiben",
2931
+ alignment: "Ausrichtung",
2932
+ alignNone: "Keine",
2933
+ alignLeft: "Links",
2934
+ alignCenter: "Mitte",
2935
+ alignRight: "Rechts",
2936
+ uploadLabel: "Oder Datei hochladen",
2937
+ insertBtn: "Einfügen",
2938
+ cancelBtn: "Abbrechen"
2939
+ },
2940
+ videoDialog: {
2941
+ ariaLabel: "Video einfügen",
2942
+ title: "Video einfügen",
2943
+ videoUrl: "Video-URL",
2944
+ urlPlaceholder: "YouTube, Vimeo oder direkte .mp4-URL",
2945
+ widthLabel: "Breite (px)",
2946
+ widthPlaceholder: "560",
2947
+ insertBtn: "Einfügen",
2948
+ cancelBtn: "Abbrechen",
2949
+ detected: (type) => `Erkannt: ${type}`,
2950
+ unknownFormat: "Unbekanntes Format — direktes Video-Einbetten wird versucht",
2951
+ invalidUrl: "Ungültige URL — bitte geben Sie einen gültigen Videolink ein."
2952
+ },
2953
+ emojiDialog: {
2954
+ ariaLabel: "Emoji einfügen",
2955
+ title: "Emoji einfügen",
2956
+ searchPlaceholder: "Emojis suchen…",
2957
+ all: "Alle",
2958
+ cancelBtn: "Abbrechen",
2959
+ close: "Schließen",
2960
+ categories: {
2961
+ smileys: "Smileys",
2962
+ people: "Menschen",
2963
+ animals: "Tiere",
2964
+ food: "Essen",
2965
+ travel: "Reisen",
2966
+ objects: "Objekte",
2967
+ symbols: "Symbole"
2968
+ }
2969
+ },
2970
+ iconDialog: {
2971
+ ariaLabel: "FA-Symbol einfügen",
2972
+ title: "FA-Symbol einfügen",
2973
+ searchPlaceholder: "Symbole suchen…",
2974
+ all: "Alle",
2975
+ style: "Stil",
2976
+ size: "Größe",
2977
+ color: "Farbe",
2978
+ useColor: " Farbe verwenden",
2979
+ selectHint: "Symbol auswählen",
2980
+ insertBtn: "FA-Symbol einfügen",
2981
+ cancelBtn: "Abbrechen",
2982
+ close: "Schließen",
2983
+ categories: {
2984
+ popular: "Beliebt",
2985
+ interface: "Benutzeroberfläche",
2986
+ navigation: "Navigation",
2987
+ media: "Medien",
2988
+ communication: "Kommunikation",
2989
+ files: "Dateien",
2990
+ people: "Menschen",
2991
+ objects: "Objekte"
2992
+ }
2993
+ },
2994
+ findReplace: {
2995
+ findTitle: "Suchen",
2996
+ findReplaceTitle: "Suchen & Ersetzen",
2997
+ findPlaceholder: "Suchen…",
2998
+ searchAriaLabel: "Suchtext",
2999
+ caseSensitive: "\xA0Groß-/Kleinschreibung",
3000
+ prevBtn: "← Zurück",
3001
+ nextBtn: "Weiter →",
3002
+ replacePlaceholder: "Ersetzen durch…",
3003
+ replaceAriaLabel: "Ersetzen durch",
3004
+ replaceBtn: "Ersetzen",
3005
+ replaceAllBtn: "Alle ersetzen",
3006
+ close: "×"
3007
+ },
3008
+ shortcutsDialog: {
3009
+ title: "Tastenkürzel",
3010
+ ariaLabel: "Tastenkürzel",
3011
+ close: "Schließen",
3012
+ shortcuts: [
3013
+ {
3014
+ category: "Textformatierung",
3015
+ items: [
3016
+ {
3017
+ keys: "Ctrl + B",
3018
+ action: "Fett"
3019
+ },
3020
+ {
3021
+ keys: "Ctrl + I",
3022
+ action: "Kursiv"
3023
+ },
3024
+ {
3025
+ keys: "Ctrl + U",
3026
+ action: "Unterstrichen"
3027
+ },
3028
+ {
3029
+ keys: "Ctrl + K",
3030
+ action: "Link einfügen / bearbeiten"
3031
+ }
3032
+ ]
3033
+ },
3034
+ {
3035
+ category: "Verlauf",
3036
+ items: [{
3037
+ keys: "Ctrl + Z",
3038
+ action: "Rückgängig"
3039
+ }, {
3040
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
3041
+ action: "Wiederholen"
3042
+ }]
3043
+ },
3044
+ {
3045
+ category: "Auswahl & Navigation",
3046
+ items: [
3047
+ {
3048
+ keys: "Ctrl + A",
3049
+ action: "Alles auswählen"
3050
+ },
3051
+ {
3052
+ keys: "Tab",
3053
+ action: "Listenebene erhöhen / Leerzeichen einfügen"
3054
+ },
3055
+ {
3056
+ keys: "Shift + Tab",
3057
+ action: "Listenebene verringern"
3058
+ }
3059
+ ]
3060
+ },
3061
+ {
3062
+ category: "Zwischenablage",
3063
+ items: [{
3064
+ keys: "Ctrl + Shift + V",
3065
+ action: "Als einfachen Text einfügen"
3066
+ }]
3067
+ },
3068
+ {
3069
+ category: "Suchen & Ersetzen",
3070
+ items: [{
3071
+ keys: "Ctrl + F",
3072
+ action: "Im Dokument suchen"
3073
+ }, {
3074
+ keys: "Ctrl + H",
3075
+ action: "Suchen & Ersetzen"
3076
+ }]
3077
+ },
3078
+ {
3079
+ category: "Editor",
3080
+ items: [{
3081
+ keys: "Ctrl + Shift + /",
3082
+ action: "Diesen Tastenkürzel-Dialog anzeigen"
3083
+ }]
3084
+ }
3085
+ ]
3086
+ },
3087
+ contextMenu: {
3088
+ cut: "Ausschneiden",
3089
+ copy: "Kopieren",
3090
+ paste: "Einfügen",
3091
+ bold: "Fett",
3092
+ italic: "Kursiv",
3093
+ underline: "Unterstrichen",
3094
+ textColor: "Textfarbe",
3095
+ highlightColor: "Hervorhebungsfarbe",
3096
+ copyFormat: "Format kopieren",
3097
+ pasteFormat: "Format einfügen",
3098
+ removeFormat: "Formatierung entfernen",
3099
+ link: "Link einfügen",
3100
+ image: "Bild einfügen",
3101
+ video: "Video einfügen",
3102
+ table: "Tabelle einfügen",
3103
+ back: "Zurück",
3104
+ noHighlight: "Keine Hervorhebung",
3105
+ customColor: "Benutzerdefinierte Farbe",
3106
+ customColorLabel: "Benutzerdefiniert…"
3107
+ },
3108
+ statusbar: {
3109
+ resizeHandle: "Editor-Größe ändern",
3110
+ words: (n) => `Wörter: ${n}`,
3111
+ wordsLimit: (n, max) => `Wörter: ${n}/${max}`,
3112
+ chars: (n) => `Zeichen: ${n}`,
3113
+ charsLimit: (n, max) => `Zeichen: ${n}/${max}`
3114
+ },
3115
+ tooltips: {
3116
+ link: {
3117
+ ariaLabel: "Link-Aktionen",
3118
+ openLink: "Link öffnen",
3119
+ copyUrl: "URL kopieren",
3120
+ editLink: "Link bearbeiten",
3121
+ removeLink: "Link entfernen"
3122
+ },
3123
+ image: {
3124
+ ariaLabel: "Bild-Aktionen",
3125
+ label: "Bild",
3126
+ floatLeft: "Links umfließen",
3127
+ noFloat: "Kein Umfluss",
3128
+ alignCenter: "Zentriert",
3129
+ floatRight: "Rechts umfließen",
3130
+ originalSize: "Originalgröße",
3131
+ rotateLeft: "Links drehen",
3132
+ rotateRight: "Rechts drehen",
3133
+ cropImage: "Bild zuschneiden",
3134
+ addCaption: "Beschriftung hinzufügen / bearbeiten",
3135
+ deleteImage: "Bild löschen"
3136
+ },
3137
+ code: {
3138
+ ariaLabel: "Codeblock-Aktionen",
3139
+ label: "Code",
3140
+ syntaxLanguage: "Syntaxsprache",
3141
+ syntaxAriaLabel: "Syntaxsprache",
3142
+ copyCode: "Code kopieren",
3143
+ toggleWordWrap: "Zeilenumbruch umschalten",
3144
+ enableWordWrap: "Zeilenumbruch aktivieren",
3145
+ disableWordWrap: "Zeilenumbruch deaktivieren",
3146
+ convertToParagraph: "In Absatz umwandeln",
3147
+ deleteCodeBlock: "Codeblock löschen"
3148
+ },
3149
+ table: {
3150
+ ariaLabel: "Tabellen-Aktionen",
3151
+ label: "Tabelle",
3152
+ selectCells: "Zellen auswählen",
3153
+ addRowAbove: "Zeile oben hinzufügen",
3154
+ addRowBelow: "Zeile unten hinzufügen",
3155
+ deleteRow: "Zeile löschen",
3156
+ addColumnLeft: "Spalte links hinzufügen",
3157
+ addColumnRight: "Spalte rechts hinzufügen",
3158
+ deleteColumn: "Spalte löschen",
3159
+ mergeCells: "Zellen zusammenführen",
3160
+ unmergeCells: "Zellen trennen",
3161
+ columnWidth: "Spaltenbreite",
3162
+ rowHeight: "Zeilenhöhe",
3163
+ tableBorderWidth: "Tabellenrahmenbreite",
3164
+ deleteTable: "Tabelle löschen",
3165
+ columnWidthPx: "Spaltenbreite (px)",
3166
+ rowHeightPx: "Zeilenhöhe (px)",
3167
+ tableBorderWidthPx: "Tabellenrahmenbreite (px)",
3168
+ cancelBtn: "Abbrechen",
3169
+ applyBtn: "Anwenden"
3170
+ },
3171
+ video: {
3172
+ ariaLabel: "Video-Aktionen",
3173
+ label: "Video",
3174
+ floatLeft: "Links umfließen",
3175
+ noFloat: "Kein Umfluss",
3176
+ alignCenter: "Zentriert",
3177
+ floatRight: "Rechts umfließen",
3178
+ originalSize: "Originalgröße",
3179
+ previewVideo: "Video-Vorschau",
3180
+ exitPreview: "Vorschau beenden",
3181
+ deleteVideo: "Video löschen"
3182
+ }
3183
+ },
3184
+ errors: {
3185
+ imageFormat: (type) => `Das Format „${type}" wird in Webbrowsern nicht unterstützt. Bitte konvertieren Sie es zuerst in JPEG, PNG oder WebP.`,
3186
+ imageSize: (maxSize) => `Die Bilddatei ist zu groß. Die maximal zulässige Größe beträgt ${maxSize} MB.`
3187
+ }
3188
+ },
3189
+ es: {
3190
+ toolbar: {
3191
+ bold: "Negrita (Ctrl+B)",
3192
+ italic: "Cursiva (Ctrl+I)",
3193
+ underline: "Subrayado (Ctrl+U)",
3194
+ strikethrough: "Tachado",
3195
+ superscript: "Superíndice",
3196
+ subscript: "Subíndice",
3197
+ alignLeft: "Alinear a la izquierda",
3198
+ alignCenter: "Centrar",
3199
+ alignRight: "Alinear a la derecha",
3200
+ alignJustify: "Justificar",
3201
+ ul: "Lista sin orden",
3202
+ ol: "Lista ordenada",
3203
+ checklist: "Lista de verificación",
3204
+ indent: "Aumentar sangría",
3205
+ outdent: "Reducir sangría",
3206
+ undo: "Deshacer (Ctrl+Z)",
3207
+ redo: "Rehacer (Ctrl+Y)",
3208
+ hr: "Línea horizontal",
3209
+ link: "Insertar enlace",
3210
+ image: "Insertar imagen",
3211
+ video: "Insertar vídeo",
3212
+ emoji: "Insertar emoji",
3213
+ icon: "Insertar icono FA",
3214
+ table: "Insertar tabla",
3215
+ fontSize: "Tamaño de fuente",
3216
+ fontSizePlaceholder: "Tamaño",
3217
+ removeFormat: "Eliminar formato",
3218
+ direction: "Cambiar dirección del texto (LTR / RTL)",
3219
+ fontFamily: "Fuente",
3220
+ paragraphStyle: "Estilo de párrafo",
3221
+ paragraphStylePlaceholder: "Estilo",
3222
+ lineHeight: "Interlineado",
3223
+ lineHeightPlaceholder: "↕ Línea",
3224
+ codeview: "Vista de código HTML",
3225
+ fullscreen: "Pantalla completa",
3226
+ shortcuts: "Atajos de teclado (Ctrl+Shift+/)",
3227
+ find: "Buscar (Ctrl+F)",
3228
+ findReplace: "Buscar y reemplazar (Ctrl+H)",
3229
+ inlineCode: "Código en línea (Ctrl+`)",
3230
+ print: "Imprimir",
3231
+ foreColor: "Color del texto",
3232
+ backColor: "Color de resaltado",
3233
+ chooseTextColor: "Elegir color del texto",
3234
+ chooseHighlightColor: "Elegir color de resaltado",
3235
+ customColor: "Color personalizado",
3236
+ insertTableLabel: "Insertar tabla",
3237
+ paragraphItems: {
3238
+ p: "Normal",
3239
+ blockquote: "Cita",
3240
+ pre: "Código"
3241
+ }
3242
+ },
3243
+ linkDialog: {
3244
+ ariaLabel: "Insertar enlace",
3245
+ title: "Insertar enlace",
3246
+ url: "URL",
3247
+ urlPlaceholder: "https://",
3248
+ displayText: "Texto a mostrar",
3249
+ textPlaceholder: "Texto del enlace",
3250
+ openInNewTab: "Abrir en nueva pestaña",
3251
+ insertBtn: "Insertar",
3252
+ cancelBtn: "Cancelar"
3253
+ },
3254
+ imageDialog: {
3255
+ ariaLabel: "Insertar imagen",
3256
+ title: "Insertar imagen",
3257
+ imageUrl: "URL de imagen",
3258
+ urlPlaceholder: "https://example.com/image.png",
3259
+ altText: "Texto alternativo",
3260
+ altPlaceholder: "Describir la imagen",
3261
+ alignment: "Alineación",
3262
+ alignNone: "Ninguna",
3263
+ alignLeft: "Izquierda",
3264
+ alignCenter: "Centro",
3265
+ alignRight: "Derecha",
3266
+ uploadLabel: "O subir un archivo",
3267
+ insertBtn: "Insertar",
3268
+ cancelBtn: "Cancelar"
3269
+ },
3270
+ videoDialog: {
3271
+ ariaLabel: "Insertar vídeo",
3272
+ title: "Insertar vídeo",
3273
+ videoUrl: "URL del vídeo",
3274
+ urlPlaceholder: "YouTube, Vimeo o URL directa .mp4",
3275
+ widthLabel: "Ancho (px)",
3276
+ widthPlaceholder: "560",
3277
+ insertBtn: "Insertar",
3278
+ cancelBtn: "Cancelar",
3279
+ detected: (type) => `Detectado: ${type}`,
3280
+ unknownFormat: "Formato desconocido — se intentará incrustar el vídeo directamente",
3281
+ invalidUrl: "URL no válida — introduzca un enlace de vídeo válido."
3282
+ },
3283
+ emojiDialog: {
3284
+ ariaLabel: "Insertar emoji",
3285
+ title: "Insertar emoji",
3286
+ searchPlaceholder: "Buscar emojis…",
3287
+ all: "Todos",
3288
+ cancelBtn: "Cancelar",
3289
+ close: "Cerrar",
3290
+ categories: {
3291
+ smileys: "Caritas",
3292
+ people: "Personas",
3293
+ animals: "Animales",
3294
+ food: "Comida",
3295
+ travel: "Viajes",
3296
+ objects: "Objetos",
3297
+ symbols: "Símbolos"
3298
+ }
3299
+ },
3300
+ iconDialog: {
3301
+ ariaLabel: "Insertar icono FA",
3302
+ title: "Insertar icono FA",
3303
+ searchPlaceholder: "Buscar iconos…",
3304
+ all: "Todos",
3305
+ style: "Estilo",
3306
+ size: "Tamaño",
3307
+ color: "Color",
3308
+ useColor: " Usar color",
3309
+ selectHint: "Seleccionar un icono",
3310
+ insertBtn: "Insertar icono FA",
3311
+ cancelBtn: "Cancelar",
3312
+ close: "Cerrar",
3313
+ categories: {
3314
+ popular: "Popular",
3315
+ interface: "Interfaz",
3316
+ navigation: "Navegación",
3317
+ media: "Medios",
3318
+ communication: "Comunicación",
3319
+ files: "Archivos",
3320
+ people: "Personas",
3321
+ objects: "Objetos"
3322
+ }
3323
+ },
3324
+ findReplace: {
3325
+ findTitle: "Buscar",
3326
+ findReplaceTitle: "Buscar y reemplazar",
3327
+ findPlaceholder: "Buscar…",
3328
+ searchAriaLabel: "Texto de búsqueda",
3329
+ caseSensitive: "\xA0Distinguir mayúsculas",
3330
+ prevBtn: "← Anterior",
3331
+ nextBtn: "Siguiente →",
3332
+ replacePlaceholder: "Reemplazar con…",
3333
+ replaceAriaLabel: "Reemplazar con",
3334
+ replaceBtn: "Reemplazar",
3335
+ replaceAllBtn: "Reemplazar todo",
3336
+ close: "×"
3337
+ },
3338
+ shortcutsDialog: {
3339
+ title: "Atajos de teclado",
3340
+ ariaLabel: "Atajos de teclado",
3341
+ close: "Cerrar",
3342
+ shortcuts: [
3343
+ {
3344
+ category: "Formato de texto",
3345
+ items: [
3346
+ {
3347
+ keys: "Ctrl + B",
3348
+ action: "Negrita"
3349
+ },
3350
+ {
3351
+ keys: "Ctrl + I",
3352
+ action: "Cursiva"
3353
+ },
3354
+ {
3355
+ keys: "Ctrl + U",
3356
+ action: "Subrayado"
3357
+ },
3358
+ {
3359
+ keys: "Ctrl + K",
3360
+ action: "Insertar / editar enlace"
3361
+ }
3362
+ ]
3363
+ },
3364
+ {
3365
+ category: "Historial",
3366
+ items: [{
3367
+ keys: "Ctrl + Z",
3368
+ action: "Deshacer"
3369
+ }, {
3370
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
3371
+ action: "Rehacer"
3372
+ }]
3373
+ },
3374
+ {
3375
+ category: "Selección y navegación",
3376
+ items: [
3377
+ {
3378
+ keys: "Ctrl + A",
3379
+ action: "Seleccionar todo"
3380
+ },
3381
+ {
3382
+ keys: "Tab",
3383
+ action: "Aumentar sangría / insertar espacios"
3384
+ },
3385
+ {
3386
+ keys: "Shift + Tab",
3387
+ action: "Reducir sangría"
3388
+ }
3389
+ ]
3390
+ },
3391
+ {
3392
+ category: "Portapapeles",
3393
+ items: [{
3394
+ keys: "Ctrl + Shift + V",
3395
+ action: "Pegar como texto plano"
3396
+ }]
3397
+ },
3398
+ {
3399
+ category: "Buscar y reemplazar",
3400
+ items: [{
3401
+ keys: "Ctrl + F",
3402
+ action: "Buscar en el documento"
3403
+ }, {
3404
+ keys: "Ctrl + H",
3405
+ action: "Buscar y reemplazar"
3406
+ }]
3407
+ },
3408
+ {
3409
+ category: "Editor",
3410
+ items: [{
3411
+ keys: "Ctrl + Shift + /",
3412
+ action: "Mostrar este diálogo de atajos"
3413
+ }]
3414
+ }
3415
+ ]
3416
+ },
3417
+ contextMenu: {
3418
+ cut: "Cortar",
3419
+ copy: "Copiar",
3420
+ paste: "Pegar",
3421
+ bold: "Negrita",
3422
+ italic: "Cursiva",
3423
+ underline: "Subrayado",
3424
+ textColor: "Color del texto",
3425
+ highlightColor: "Color de resaltado",
3426
+ copyFormat: "Copiar formato",
3427
+ pasteFormat: "Pegar formato",
3428
+ removeFormat: "Eliminar formato",
3429
+ link: "Insertar enlace",
3430
+ image: "Insertar imagen",
3431
+ video: "Insertar vídeo",
3432
+ table: "Insertar tabla",
3433
+ back: "Atrás",
3434
+ noHighlight: "Sin resaltado",
3435
+ customColor: "Color personalizado",
3436
+ customColorLabel: "Personalizado…"
3437
+ },
3438
+ statusbar: {
3439
+ resizeHandle: "Redimensionar editor",
3440
+ words: (n) => `Palabras: ${n}`,
3441
+ wordsLimit: (n, max) => `Palabras: ${n}/${max}`,
3442
+ chars: (n) => `Caracteres: ${n}`,
3443
+ charsLimit: (n, max) => `Caracteres: ${n}/${max}`
3444
+ },
3445
+ tooltips: {
3446
+ link: {
3447
+ ariaLabel: "Acciones de enlace",
3448
+ openLink: "Abrir enlace",
3449
+ copyUrl: "Copiar URL",
3450
+ editLink: "Editar enlace",
3451
+ removeLink: "Eliminar enlace"
3452
+ },
3453
+ image: {
3454
+ ariaLabel: "Acciones de imagen",
3455
+ label: "Imagen",
3456
+ floatLeft: "Flotar a la izquierda",
3457
+ noFloat: "Sin flotado",
3458
+ alignCenter: "Centrar",
3459
+ floatRight: "Flotar a la derecha",
3460
+ originalSize: "Tamaño original",
3461
+ rotateLeft: "Rotar a la izquierda",
3462
+ rotateRight: "Rotar a la derecha",
3463
+ cropImage: "Recortar imagen",
3464
+ addCaption: "Añadir / editar pie de foto",
3465
+ deleteImage: "Eliminar imagen"
3466
+ },
3467
+ code: {
3468
+ ariaLabel: "Acciones de bloque de código",
3469
+ label: "Código",
3470
+ syntaxLanguage: "Lenguaje de sintaxis",
3471
+ syntaxAriaLabel: "Lenguaje de sintaxis",
3472
+ copyCode: "Copiar código",
3473
+ toggleWordWrap: "Alternar ajuste de línea",
3474
+ enableWordWrap: "Activar ajuste de línea",
3475
+ disableWordWrap: "Desactivar ajuste de línea",
3476
+ convertToParagraph: "Convertir en párrafo",
3477
+ deleteCodeBlock: "Eliminar bloque de código"
3478
+ },
3479
+ table: {
3480
+ ariaLabel: "Acciones de tabla",
3481
+ label: "Tabla",
3482
+ selectCells: "Seleccionar celdas",
3483
+ addRowAbove: "Añadir fila encima",
3484
+ addRowBelow: "Añadir fila debajo",
3485
+ deleteRow: "Eliminar fila",
3486
+ addColumnLeft: "Añadir columna a la izquierda",
3487
+ addColumnRight: "Añadir columna a la derecha",
3488
+ deleteColumn: "Eliminar columna",
3489
+ mergeCells: "Combinar celdas",
3490
+ unmergeCells: "Separar celdas",
3491
+ columnWidth: "Ancho de columna",
3492
+ rowHeight: "Alto de fila",
3493
+ tableBorderWidth: "Grosor del borde de la tabla",
3494
+ deleteTable: "Eliminar tabla",
3495
+ columnWidthPx: "Ancho de columna (px)",
3496
+ rowHeightPx: "Alto de fila (px)",
3497
+ tableBorderWidthPx: "Grosor del borde (px)",
3498
+ cancelBtn: "Cancelar",
3499
+ applyBtn: "Aplicar"
3500
+ },
3501
+ video: {
3502
+ ariaLabel: "Acciones de vídeo",
3503
+ label: "Vídeo",
3504
+ floatLeft: "Flotar a la izquierda",
3505
+ noFloat: "Sin flotado",
3506
+ alignCenter: "Centrar",
3507
+ floatRight: "Flotar a la derecha",
3508
+ originalSize: "Tamaño original",
3509
+ previewVideo: "Vista previa de vídeo",
3510
+ exitPreview: "Salir de la vista previa",
3511
+ deleteVideo: "Eliminar vídeo"
3512
+ }
3513
+ },
3514
+ errors: {
3515
+ imageFormat: (type) => `El formato "${type}" no es compatible con los navegadores web. Por favor, conviértalo primero a JPEG, PNG o WebP.`,
3516
+ imageSize: (maxSize) => `El archivo de imagen es demasiado grande. El tamaño máximo permitido es ${maxSize} MB.`
3517
+ }
3518
+ },
3519
+ ko: {
3520
+ toolbar: {
3521
+ bold: "굵게 (Ctrl+B)",
3522
+ italic: "기울임꼴 (Ctrl+I)",
3523
+ underline: "밑줄 (Ctrl+U)",
3524
+ strikethrough: "취소선",
3525
+ superscript: "위 첨자",
3526
+ subscript: "아래 첨자",
3527
+ alignLeft: "왼쪽 정렬",
3528
+ alignCenter: "가운데 정렬",
3529
+ alignRight: "오른쪽 정렬",
3530
+ alignJustify: "양쪽 정렬",
3531
+ ul: "순서 없는 목록",
3532
+ ol: "순서 있는 목록",
3533
+ checklist: "체크리스트",
3534
+ indent: "들여쓰기",
3535
+ outdent: "내어쓰기",
3536
+ undo: "실행 취소 (Ctrl+Z)",
3537
+ redo: "다시 실행 (Ctrl+Y)",
3538
+ hr: "수평선",
3539
+ link: "링크 삽입",
3540
+ image: "이미지 삽입",
3541
+ video: "동영상 삽입",
3542
+ emoji: "이모지 삽입",
3543
+ icon: "FA 아이콘 삽입",
3544
+ table: "표 삽입",
3545
+ fontSize: "글꼴 크기",
3546
+ fontSizePlaceholder: "크기",
3547
+ removeFormat: "서식 제거",
3548
+ direction: "텍스트 방향 전환 (LTR / RTL)",
3549
+ fontFamily: "글꼴",
3550
+ paragraphStyle: "단락 스타일",
3551
+ paragraphStylePlaceholder: "스타일",
3552
+ lineHeight: "줄 간격",
3553
+ lineHeightPlaceholder: "↕ 줄",
3554
+ codeview: "HTML 코드 보기",
3555
+ fullscreen: "전체 화면",
3556
+ shortcuts: "키보드 단축키 (Ctrl+Shift+/)",
3557
+ find: "찾기 (Ctrl+F)",
3558
+ findReplace: "찾기 및 바꾸기 (Ctrl+H)",
3559
+ inlineCode: "인라인 코드 (Ctrl+`)",
3560
+ print: "인쇄",
3561
+ foreColor: "글자 색",
3562
+ backColor: "강조 색",
3563
+ chooseTextColor: "글자 색 선택",
3564
+ chooseHighlightColor: "강조 색 선택",
3565
+ customColor: "사용자 지정 색",
3566
+ insertTableLabel: "표 삽입",
3567
+ paragraphItems: {
3568
+ p: "기본",
3569
+ blockquote: "인용",
3570
+ pre: "코드"
3571
+ }
3572
+ },
3573
+ linkDialog: {
3574
+ ariaLabel: "링크 삽입",
3575
+ title: "링크 삽입",
3576
+ url: "URL",
3577
+ urlPlaceholder: "https://",
3578
+ displayText: "표시 텍스트",
3579
+ textPlaceholder: "링크 텍스트",
3580
+ openInNewTab: "새 탭에서 열기",
3581
+ insertBtn: "삽입",
3582
+ cancelBtn: "취소"
3583
+ },
3584
+ imageDialog: {
3585
+ ariaLabel: "이미지 삽입",
3586
+ title: "이미지 삽입",
3587
+ imageUrl: "이미지 URL",
3588
+ urlPlaceholder: "https://example.com/image.png",
3589
+ altText: "대체 텍스트",
3590
+ altPlaceholder: "이미지 설명",
3591
+ alignment: "정렬",
3592
+ alignNone: "없음",
3593
+ alignLeft: "왼쪽",
3594
+ alignCenter: "가운데",
3595
+ alignRight: "오른쪽",
3596
+ uploadLabel: "또는 파일 업로드",
3597
+ insertBtn: "삽입",
3598
+ cancelBtn: "취소"
3599
+ },
3600
+ videoDialog: {
3601
+ ariaLabel: "동영상 삽입",
3602
+ title: "동영상 삽입",
3603
+ videoUrl: "동영상 URL",
3604
+ urlPlaceholder: "YouTube, Vimeo 또는 직접 .mp4 URL",
3605
+ widthLabel: "너비 (px)",
3606
+ widthPlaceholder: "560",
3607
+ insertBtn: "삽입",
3608
+ cancelBtn: "취소",
3609
+ detected: (type) => `감지됨: ${type}`,
3610
+ unknownFormat: "알 수 없는 형식 — 직접 동영상 임베드를 시도합니다",
3611
+ invalidUrl: "유효하지 않은 URL — 올바른 동영상 링크를 입력하세요."
3612
+ },
3613
+ emojiDialog: {
3614
+ ariaLabel: "이모지 삽입",
3615
+ title: "이모지 삽입",
3616
+ searchPlaceholder: "이모지 검색…",
3617
+ all: "전체",
3618
+ cancelBtn: "취소",
3619
+ close: "닫기",
3620
+ categories: {
3621
+ smileys: "스마일",
3622
+ people: "사람",
3623
+ animals: "동물",
3624
+ food: "음식",
3625
+ travel: "여행",
3626
+ objects: "사물",
3627
+ symbols: "기호"
3628
+ }
3629
+ },
3630
+ iconDialog: {
3631
+ ariaLabel: "FA 아이콘 삽입",
3632
+ title: "FA 아이콘 삽입",
3633
+ searchPlaceholder: "아이콘 검색…",
3634
+ all: "전체",
3635
+ style: "스타일",
3636
+ size: "크기",
3637
+ color: "색상",
3638
+ useColor: " 색상 사용",
3639
+ selectHint: "아이콘을 선택하세요",
3640
+ insertBtn: "FA 아이콘 삽입",
3641
+ cancelBtn: "취소",
3642
+ close: "닫기",
3643
+ categories: {
3644
+ popular: "인기",
3645
+ interface: "인터페이스",
3646
+ navigation: "탐색",
3647
+ media: "미디어",
3648
+ communication: "커뮤니케이션",
3649
+ files: "파일",
3650
+ people: "사람",
3651
+ objects: "사물"
3652
+ }
3653
+ },
3654
+ findReplace: {
3655
+ findTitle: "찾기",
3656
+ findReplaceTitle: "찾기 및 바꾸기",
3657
+ findPlaceholder: "찾기…",
3658
+ searchAriaLabel: "검색 텍스트",
3659
+ caseSensitive: "\xA0대소문자 구분",
3660
+ prevBtn: "← 이전",
3661
+ nextBtn: "다음 →",
3662
+ replacePlaceholder: "바꿀 내용…",
3663
+ replaceAriaLabel: "바꿀 내용",
3664
+ replaceBtn: "바꾸기",
3665
+ replaceAllBtn: "모두 바꾸기",
3666
+ close: "×"
3667
+ },
3668
+ shortcutsDialog: {
3669
+ title: "키보드 단축키",
3670
+ ariaLabel: "키보드 단축키",
3671
+ close: "닫기",
3672
+ shortcuts: [
3673
+ {
3674
+ category: "텍스트 서식",
3675
+ items: [
3676
+ {
3677
+ keys: "Ctrl + B",
3678
+ action: "굵게"
3679
+ },
3680
+ {
3681
+ keys: "Ctrl + I",
3682
+ action: "기울임꼴"
3683
+ },
3684
+ {
3685
+ keys: "Ctrl + U",
3686
+ action: "밑줄"
3687
+ },
3688
+ {
3689
+ keys: "Ctrl + K",
3690
+ action: "링크 삽입 / 편집"
3691
+ }
3692
+ ]
3693
+ },
3694
+ {
3695
+ category: "실행 기록",
3696
+ items: [{
3697
+ keys: "Ctrl + Z",
3698
+ action: "실행 취소"
3699
+ }, {
3700
+ keys: "Ctrl + Y / Ctrl + Shift + Z",
3701
+ action: "다시 실행"
3702
+ }]
3703
+ },
3704
+ {
3705
+ category: "선택 및 탐색",
3706
+ items: [
3707
+ {
3708
+ keys: "Ctrl + A",
3709
+ action: "전체 선택"
3710
+ },
3711
+ {
3712
+ keys: "Tab",
3713
+ action: "목록 들여쓰기 / 공백 삽입"
3714
+ },
3715
+ {
3716
+ keys: "Shift + Tab",
3717
+ action: "목록 내어쓰기"
3718
+ }
3719
+ ]
3720
+ },
3721
+ {
3722
+ category: "클립보드",
3723
+ items: [{
3724
+ keys: "Ctrl + Shift + V",
3725
+ action: "일반 텍스트로 붙여넣기"
3726
+ }]
3727
+ },
3728
+ {
3729
+ category: "찾기 및 바꾸기",
3730
+ items: [{
3731
+ keys: "Ctrl + F",
3732
+ action: "문서에서 찾기"
3733
+ }, {
3734
+ keys: "Ctrl + H",
3735
+ action: "찾기 및 바꾸기"
3736
+ }]
3737
+ },
3738
+ {
3739
+ category: "편집기",
3740
+ items: [{
3741
+ keys: "Ctrl + Shift + /",
3742
+ action: "이 단축키 대화상자 표시"
3743
+ }]
3744
+ }
3745
+ ]
3746
+ },
3747
+ contextMenu: {
3748
+ cut: "잘라내기",
3749
+ copy: "복사",
3750
+ paste: "붙여넣기",
3751
+ bold: "굵게",
3752
+ italic: "기울임꼴",
3753
+ underline: "밑줄",
3754
+ textColor: "글자 색",
3755
+ highlightColor: "강조 색",
3756
+ copyFormat: "서식 복사",
3757
+ pasteFormat: "서식 붙여넣기",
3758
+ removeFormat: "서식 제거",
3759
+ link: "링크 삽입",
3760
+ image: "이미지 삽입",
3761
+ video: "동영상 삽입",
3762
+ table: "표 삽입",
3763
+ back: "뒤로",
3764
+ noHighlight: "강조 없음",
3765
+ customColor: "사용자 지정 색",
3766
+ customColorLabel: "사용자 지정…"
3767
+ },
3768
+ statusbar: {
3769
+ resizeHandle: "편집기 크기 조정",
3770
+ words: (n) => `단어: ${n}`,
3771
+ wordsLimit: (n, max) => `단어: ${n}/${max}`,
3772
+ chars: (n) => `글자: ${n}`,
3773
+ charsLimit: (n, max) => `글자: ${n}/${max}`
3774
+ },
3775
+ tooltips: {
3776
+ link: {
3777
+ ariaLabel: "링크 작업",
3778
+ openLink: "링크 열기",
3779
+ copyUrl: "URL 복사",
3780
+ editLink: "링크 편집",
3781
+ removeLink: "링크 제거"
3782
+ },
3783
+ image: {
3784
+ ariaLabel: "이미지 작업",
3785
+ label: "이미지",
3786
+ floatLeft: "왼쪽 배치",
3787
+ noFloat: "배치 없음",
3788
+ alignCenter: "가운데 정렬",
3789
+ floatRight: "오른쪽 배치",
3790
+ originalSize: "원본 크기",
3791
+ rotateLeft: "왼쪽 회전",
3792
+ rotateRight: "오른쪽 회전",
3793
+ cropImage: "이미지 자르기",
3794
+ addCaption: "캡션 추가 / 편집",
3795
+ deleteImage: "이미지 삭제"
3796
+ },
3797
+ code: {
3798
+ ariaLabel: "코드 블록 작업",
3799
+ label: "코드",
3800
+ syntaxLanguage: "구문 언어",
3801
+ syntaxAriaLabel: "구문 언어",
3802
+ copyCode: "코드 복사",
3803
+ toggleWordWrap: "줄 바꿈 전환",
3804
+ enableWordWrap: "줄 바꿈 사용",
3805
+ disableWordWrap: "줄 바꿈 해제",
3806
+ convertToParagraph: "단락으로 변환",
3807
+ deleteCodeBlock: "코드 블록 삭제"
3808
+ },
3809
+ table: {
3810
+ ariaLabel: "표 작업",
3811
+ label: "표",
3812
+ selectCells: "셀 선택",
3813
+ addRowAbove: "위에 행 추가",
3814
+ addRowBelow: "아래에 행 추가",
3815
+ deleteRow: "행 삭제",
3816
+ addColumnLeft: "왼쪽에 열 추가",
3817
+ addColumnRight: "오른쪽에 열 추가",
3818
+ deleteColumn: "열 삭제",
3819
+ mergeCells: "셀 병합",
3820
+ unmergeCells: "셀 분할",
3821
+ columnWidth: "열 너비",
3822
+ rowHeight: "행 높이",
3823
+ tableBorderWidth: "표 테두리 너비",
3824
+ deleteTable: "표 삭제",
3825
+ columnWidthPx: "열 너비 (px)",
3826
+ rowHeightPx: "행 높이 (px)",
3827
+ tableBorderWidthPx: "표 테두리 너비 (px)",
3828
+ cancelBtn: "취소",
3829
+ applyBtn: "적용"
3830
+ },
3831
+ video: {
3832
+ ariaLabel: "동영상 작업",
3833
+ label: "동영상",
3834
+ floatLeft: "왼쪽 배치",
3835
+ noFloat: "배치 없음",
3836
+ alignCenter: "가운데 정렬",
3837
+ floatRight: "오른쪽 배치",
3838
+ originalSize: "원본 크기",
3839
+ previewVideo: "동영상 미리보기",
3840
+ exitPreview: "미리보기 종료",
3841
+ deleteVideo: "동영상 삭제"
3842
+ }
3843
+ },
3844
+ errors: {
3845
+ imageFormat: (type) => `"${type}" 형식은 웹 브라우저에서 지원되지 않습니다. JPEG, PNG 또는 WebP로 변환해 주세요.`,
3846
+ imageSize: (maxSize) => `이미지 파일이 너무 큽니다. 최대 허용 크기는 ${maxSize} MB입니다.`
3847
+ }
3848
+ }
3849
+ };
3850
+ /**
3851
+ * Resolve a locale object from a lang option value.
3852
+ *
3853
+ * @param {string | Partial<AsnLocale> | null | undefined} lang
3854
+ * @returns {AsnLocale} A fully-populated locale (always contains every key from en.js).
3855
+ */
3856
+ function resolveLocale(lang) {
3857
+ if (!lang || lang === "en") return en;
3858
+ if (typeof lang === "string") {
3859
+ const partial = locales[lang];
3860
+ if (!partial) return en;
3861
+ return mergeDeep(mergeDeep({}, en), partial);
3862
+ }
3863
+ if (typeof lang === "object") return mergeDeep(mergeDeep({}, en), lang);
3864
+ return en;
3865
+ }
3866
+ /**
3867
+ * @typedef {Object} AsnLocale (see types/index.d.ts for the full definition)
3868
+ */
3869
+ //#endregion
1167
3870
  //#region src/js/core/sanitise.js
1168
3871
  /**
1169
3872
  * sanitise.js - Shared HTML and URL sanitisation utilities
@@ -2683,8 +5386,8 @@
2683
5386
  * Inspired by Summernote's Toolbar module — rewritten without jQuery
2684
5387
  */
2685
5388
  var _faPageLevelReady = null;
2686
- var _S = "stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
2687
- var _svgWrap = (paths) => `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" ${_S} style="display:block">${paths}</svg>`;
5389
+ var _S$1 = "stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
5390
+ var _svgWrap = (paths) => `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" ${_S$1} style="display:block">${paths}</svg>`;
2688
5391
  var _SVG_MAP = new Map([
2689
5392
  ["bold", _svgWrap("<path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/>")],
2690
5393
  ["italic", _svgWrap("<line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"/><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"/><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"/>")],
@@ -2814,9 +5517,9 @@
2814
5517
  const btn = createElement("button", {
2815
5518
  type: "button",
2816
5519
  class: !!this.options.useBootstrap ? this.options.toolbarButtonClass || "btn btn-sm btn-light" : "an-btn",
2817
- title: def.tooltip || "",
5520
+ title: this.context.locale.toolbar[def.name] || def.tooltip || "",
2818
5521
  "data-btn": def.name,
2819
- "aria-label": def.tooltip || def.name,
5522
+ "aria-label": this.context.locale.toolbar[def.name] || def.tooltip || def.name,
2820
5523
  "aria-haspopup": "true",
2821
5524
  "aria-expanded": "false"
2822
5525
  });
@@ -2829,7 +5532,7 @@
2829
5532
  });
2830
5533
  const grid = createElement("div", { class: "an-table-grid" });
2831
5534
  const label = createElement("div", { class: "an-table-label" });
2832
- label.textContent = "Insert Table";
5535
+ label.textContent = this.context.locale.toolbar.insertTableLabel || "Insert Table";
2833
5536
  const cells = [];
2834
5537
  for (let r = 1; r <= ROWS; r++) for (let c = 1; c <= COLS; c++) {
2835
5538
  const cell = createElement("div", {
@@ -2849,7 +5552,7 @@
2849
5552
  const c = +cell.getAttribute("data-col");
2850
5553
  cell.classList.toggle("active", r <= rows && c <= cols);
2851
5554
  });
2852
- label.textContent = rows && cols ? `${rows} × ${cols}` : "Insert Table";
5555
+ label.textContent = rows && cols ? `${rows} × ${cols}` : this.context.locale.toolbar.insertTableLabel || "Insert Table";
2853
5556
  };
2854
5557
  const openPopup = () => {
2855
5558
  isOpen = true;
@@ -2929,9 +5632,9 @@
2929
5632
  const applyBtn = createElement("button", {
2930
5633
  type: "button",
2931
5634
  class: `${baseClass} an-color-btn`,
2932
- title: def.tooltip || "",
5635
+ title: this.context.locale.toolbar[def.name] || def.tooltip || "",
2933
5636
  "data-btn": def.name,
2934
- "aria-label": def.tooltip || def.name
5637
+ "aria-label": this.context.locale.toolbar[def.name] || def.tooltip || def.name
2935
5638
  });
2936
5639
  const S = "stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
2937
5640
  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>`;
@@ -2941,7 +5644,7 @@
2941
5644
  const arrowBtn = createElement("button", {
2942
5645
  type: "button",
2943
5646
  class: `${baseClass} an-color-arrow`,
2944
- title: `Choose ${def.name === "foreColor" ? "text" : "highlight"} color`,
5647
+ title: def.name === "foreColor" ? this.context.locale.toolbar.chooseTextColor || "Choose text color" : this.context.locale.toolbar.chooseHighlightColor || "Choose highlight color",
2945
5648
  "aria-haspopup": "true",
2946
5649
  "aria-expanded": "false"
2947
5650
  });
@@ -2963,9 +5666,9 @@
2963
5666
  const colorInput = createElement("input", {
2964
5667
  type: "color",
2965
5668
  value: currentColor,
2966
- title: "Custom color"
5669
+ title: this.context.locale.toolbar.customColor || "Custom color"
2967
5670
  });
2968
- const customLabel = createElement("span", {}, ["Custom color"]);
5671
+ const customLabel = createElement("span", {}, [this.context.locale.toolbar.customColor || "Custom color"]);
2969
5672
  customRow.appendChild(colorInput);
2970
5673
  customRow.appendChild(customLabel);
2971
5674
  popup.appendChild(swatches);
@@ -3075,19 +5778,19 @@
3075
5778
  const items = def.name === "fontFamily" ? this.options.fontFamilies || [] : def.items || [];
3076
5779
  const select = createElement("select", {
3077
5780
  class: def.selectClass ? `an-select ${def.selectClass}` : "an-select",
3078
- title: def.tooltip || "",
5781
+ title: this.context.locale.toolbar[def.name] || def.tooltip || "",
3079
5782
  "data-btn": def.name,
3080
- "aria-label": def.tooltip || def.name
5783
+ "aria-label": this.context.locale.toolbar[def.name] || def.tooltip || def.name
3081
5784
  });
3082
5785
  const placeholder = createElement("option", {
3083
5786
  value: "",
3084
5787
  disabled: "",
3085
5788
  hidden: ""
3086
- }, [def.placeholder || "Font"]);
5789
+ }, [this.context.locale.toolbar[def.name + "Placeholder"] || def.placeholder || "Font"]);
3087
5790
  select.appendChild(placeholder);
3088
5791
  items.forEach((item) => {
3089
5792
  const value = typeof item === "object" ? item.value : item;
3090
- const label = typeof item === "object" ? item.label : item;
5793
+ const label = typeof item === "object" ? def.name === "paragraphStyle" ? (this.context.locale.toolbar.paragraphItems || {})[item.value] || item.label : item.label : item;
3091
5794
  const isHeader = typeof item === "object" && !!item.disabled;
3092
5795
  const attrs = { value };
3093
5796
  if (isHeader) attrs.disabled = "";
@@ -3127,9 +5830,9 @@
3127
5830
  const btn = createElement("button", {
3128
5831
  type: "button",
3129
5832
  class: `${!!this.options.useBootstrap ? this.options.toolbarButtonClass || "btn btn-sm btn-light" : `an-btn`}${btnDef.className ? ` ${btnDef.className}` : ""}`,
3130
- title: btnDef.tooltip || "",
5833
+ title: this.context.locale.toolbar[btnDef.name] || btnDef.tooltip || "",
3131
5834
  "data-btn": btnDef.name,
3132
- "aria-label": btnDef.tooltip || btnDef.name
5835
+ "aria-label": this.context.locale.toolbar[btnDef.name] || btnDef.tooltip || btnDef.name
3133
5836
  });
3134
5837
  const faPrefix = this.options.fontAwesomeClass || "fas";
3135
5838
  if (this._faReady) {
@@ -3254,7 +5957,7 @@
3254
5957
  if (this.options.resizable !== false) {
3255
5958
  const handle = createElement("div", {
3256
5959
  class: "an-resize-handle",
3257
- title: "Resize editor",
5960
+ title: this.context.locale.statusbar.resizeHandle,
3258
5961
  "aria-hidden": "true"
3259
5962
  });
3260
5963
  this._bindResize(handle);
@@ -3344,8 +6047,9 @@
3344
6047
  const chars = text.replace(/\n/g, "").length;
3345
6048
  const maxWords = this.options.maxWords || 0;
3346
6049
  const maxChars = this.options.maxChars || 0;
3347
- this._wordCountEl.textContent = maxWords ? `Words: ${words}/${maxWords}` : `Words: ${words}`;
3348
- this._charCountEl.textContent = maxChars ? `Chars: ${chars}/${maxChars}` : `Chars: ${chars}`;
6050
+ const LS = this.context.locale.statusbar;
6051
+ this._wordCountEl.textContent = maxWords ? LS.wordsLimit(words, maxWords) : LS.words(words);
6052
+ this._charCountEl.textContent = maxChars ? LS.charsLimit(chars, maxChars) : LS.chars(chars);
3349
6053
  _applyLimitClass(this._wordCountEl, words, maxWords);
3350
6054
  _applyLimitClass(this._charCountEl, chars, maxChars);
3351
6055
  }
@@ -3942,32 +6646,33 @@
3942
6646
  this._open();
3943
6647
  }
3944
6648
  _buildDialog() {
6649
+ const L = this.context.locale.linkDialog;
3945
6650
  const overlay = createElement("div", {
3946
6651
  class: "an-dialog-overlay",
3947
6652
  role: "dialog",
3948
6653
  "aria-modal": "true",
3949
- "aria-label": "Insert link"
6654
+ "aria-label": L.ariaLabel
3950
6655
  });
3951
6656
  const box = createElement("div", { class: "an-dialog-box" });
3952
6657
  const title = createElement("h3", { class: "an-dialog-title" });
3953
- title.textContent = "Insert Link";
6658
+ title.textContent = L.title;
3954
6659
  const urlLabel = createElement("label", { class: "an-label" });
3955
- urlLabel.textContent = "URL";
6660
+ urlLabel.textContent = L.url;
3956
6661
  const urlInput = createElement("input", {
3957
6662
  type: "url",
3958
6663
  class: "an-input",
3959
- placeholder: "https://",
6664
+ placeholder: L.urlPlaceholder,
3960
6665
  id: "an-link-url",
3961
6666
  name: "url",
3962
6667
  autocomplete: "off"
3963
6668
  });
3964
6669
  this._urlInput = urlInput;
3965
6670
  const textLabel = createElement("label", { class: "an-label" });
3966
- textLabel.textContent = "Display Text";
6671
+ textLabel.textContent = L.displayText;
3967
6672
  const textInput = createElement("input", {
3968
6673
  type: "text",
3969
6674
  class: "an-input",
3970
- placeholder: "Link text",
6675
+ placeholder: L.textPlaceholder,
3971
6676
  id: "an-link-text",
3972
6677
  name: "linkText",
3973
6678
  autocomplete: "off"
@@ -3981,18 +6686,18 @@
3981
6686
  });
3982
6687
  this._tabCheckbox = tabCheckbox;
3983
6688
  tabLabel.appendChild(tabCheckbox);
3984
- tabLabel.appendChild(document.createTextNode(" Open in new tab"));
6689
+ tabLabel.appendChild(document.createTextNode(" " + L.openInNewTab));
3985
6690
  const btnRow = createElement("div", { class: "an-dialog-actions" });
3986
6691
  const insertBtn = createElement("button", {
3987
6692
  type: "button",
3988
6693
  class: "an-btn an-btn-primary"
3989
6694
  });
3990
- insertBtn.textContent = "Insert";
6695
+ insertBtn.textContent = L.insertBtn;
3991
6696
  const cancelBtn = createElement("button", {
3992
6697
  type: "button",
3993
6698
  class: "an-btn"
3994
6699
  });
3995
- cancelBtn.textContent = "Cancel";
6700
+ cancelBtn.textContent = L.cancelBtn;
3996
6701
  btnRow.appendChild(insertBtn);
3997
6702
  btnRow.appendChild(cancelBtn);
3998
6703
  box.append(title, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
@@ -4118,53 +6823,54 @@
4118
6823
  this._open();
4119
6824
  }
4120
6825
  _buildDialog() {
6826
+ const L = this.context.locale.imageDialog;
4121
6827
  const overlay = createElement("div", {
4122
6828
  class: "an-dialog-overlay",
4123
6829
  role: "dialog",
4124
6830
  "aria-modal": "true",
4125
- "aria-label": "Insert image"
6831
+ "aria-label": L.ariaLabel
4126
6832
  });
4127
6833
  const box = createElement("div", { class: "an-dialog-box" });
4128
6834
  const title = createElement("h3", { class: "an-dialog-title" });
4129
- title.textContent = "Insert Image";
6835
+ title.textContent = L.title;
4130
6836
  const urlLabel = createElement("label", { class: "an-label" });
4131
- urlLabel.textContent = "Image URL";
6837
+ urlLabel.textContent = L.imageUrl;
4132
6838
  const urlInput = createElement("input", {
4133
6839
  type: "url",
4134
6840
  class: "an-input",
4135
- placeholder: "https://example.com/image.png",
6841
+ placeholder: L.urlPlaceholder,
4136
6842
  autocomplete: "off"
4137
6843
  });
4138
6844
  this._urlInput = urlInput;
4139
6845
  const altLabel = createElement("label", { class: "an-label" });
4140
- altLabel.textContent = "Alt Text";
6846
+ altLabel.textContent = L.altText;
4141
6847
  const altInput = createElement("input", {
4142
6848
  type: "text",
4143
6849
  class: "an-input",
4144
- placeholder: "Describe the image",
6850
+ placeholder: L.altPlaceholder,
4145
6851
  autocomplete: "off"
4146
6852
  });
4147
6853
  this._altInput = altInput;
4148
6854
  box.append(title, urlLabel, urlInput, altLabel, altInput);
4149
6855
  const alignLabel = createElement("label", { class: "an-label" });
4150
- alignLabel.textContent = "Alignment";
6856
+ alignLabel.textContent = L.alignment;
4151
6857
  const alignRow = createElement("div", { class: "an-align-row" });
4152
6858
  [
4153
6859
  {
4154
6860
  value: "",
4155
- label: "None"
6861
+ label: L.alignNone
4156
6862
  },
4157
6863
  {
4158
6864
  value: "left",
4159
- label: "Left"
6865
+ label: L.alignLeft
4160
6866
  },
4161
6867
  {
4162
6868
  value: "center",
4163
- label: "Center"
6869
+ label: L.alignCenter
4164
6870
  },
4165
6871
  {
4166
6872
  value: "right",
4167
- label: "Right"
6873
+ label: L.alignRight
4168
6874
  }
4169
6875
  ].forEach(({ value, label }) => {
4170
6876
  const radioId = `an-align-${value || "none"}`;
@@ -4186,7 +6892,7 @@
4186
6892
  box.append(alignLabel, alignRow);
4187
6893
  if (this.options.allowImageUpload !== false) {
4188
6894
  const fileLabel = createElement("label", { class: "an-label" });
4189
- fileLabel.textContent = "Or upload a file";
6895
+ fileLabel.textContent = L.uploadLabel;
4190
6896
  const fileInput = createElement("input", {
4191
6897
  type: "file",
4192
6898
  class: "an-input",
@@ -4204,12 +6910,12 @@
4204
6910
  type: "button",
4205
6911
  class: "an-btn an-btn-primary"
4206
6912
  });
4207
- insertBtn.textContent = "Insert";
6913
+ insertBtn.textContent = L.insertBtn;
4208
6914
  const cancelBtn = createElement("button", {
4209
6915
  type: "button",
4210
6916
  class: "an-btn"
4211
6917
  });
4212
- cancelBtn.textContent = "Cancel";
6918
+ cancelBtn.textContent = L.cancelBtn;
4213
6919
  btnRow.appendChild(insertBtn);
4214
6920
  btnRow.appendChild(cancelBtn);
4215
6921
  box.append(btnRow);
@@ -4245,7 +6951,7 @@
4245
6951
  "image/svg+xml",
4246
6952
  "image/avif"
4247
6953
  ]).has(file.type)) {
4248
- const message = `Format "${file.type}" is not supported for display in web browsers. Please convert to JPEG, PNG, or WebP first.`;
6954
+ const message = this.context.locale.errors.imageFormat(file.type);
4249
6955
  if (this._fileHint) this._fileHint.textContent = message;
4250
6956
  this.context.triggerEvent("imageError", {
4251
6957
  file,
@@ -4257,7 +6963,7 @@
4257
6963
  if (this._fileHint) this._fileHint.textContent = "";
4258
6964
  const maxSize = (this.options.maxImageSize || 5) * 1024 * 1024;
4259
6965
  if (file.size > maxSize) {
4260
- const message = `Image file is too large. Maximum allowed size is ${this.options.maxImageSize || 5} MB.`;
6966
+ const message = this.context.locale.errors.imageSize(this.options.maxImageSize || 5);
4261
6967
  if (this._fileHint) this._fileHint.textContent = message;
4262
6968
  console.warn("[AutumnNote] ImageDialog:", message);
4263
6969
  this.context.triggerEvent("imageError", {
@@ -4343,28 +7049,29 @@
4343
7049
  this._open();
4344
7050
  }
4345
7051
  _buildDialog() {
7052
+ const L = this.context.locale.videoDialog;
4346
7053
  const overlay = createElement("div", {
4347
7054
  class: "an-dialog-overlay",
4348
7055
  role: "dialog",
4349
7056
  "aria-modal": "true",
4350
- "aria-label": "Insert video"
7057
+ "aria-label": L.ariaLabel
4351
7058
  });
4352
7059
  const box = createElement("div", { class: "an-dialog-box" });
4353
7060
  const title = createElement("h3", { class: "an-dialog-title" });
4354
- title.textContent = "Insert Video";
7061
+ title.textContent = L.title;
4355
7062
  const urlLabel = createElement("label", { class: "an-label" });
4356
- urlLabel.textContent = "Video URL";
7063
+ urlLabel.textContent = L.videoUrl;
4357
7064
  const urlInput = createElement("input", {
4358
7065
  type: "url",
4359
7066
  class: "an-input",
4360
- placeholder: "YouTube, Vimeo, or direct .mp4 URL",
7067
+ placeholder: L.urlPlaceholder,
4361
7068
  autocomplete: "off"
4362
7069
  });
4363
7070
  this._urlInput = urlInput;
4364
7071
  const hintEl = createElement("p", { class: "an-dialog-hint" });
4365
7072
  this._hintEl = hintEl;
4366
7073
  const widthLabel = createElement("label", { class: "an-label" });
4367
- widthLabel.textContent = "Width (px)";
7074
+ widthLabel.textContent = L.widthLabel;
4368
7075
  const widthInput = createElement("input", {
4369
7076
  type: "number",
4370
7077
  class: "an-input",
@@ -4379,19 +7086,19 @@
4379
7086
  type: "button",
4380
7087
  class: "an-btn an-btn-primary"
4381
7088
  });
4382
- insertBtn.textContent = "Insert";
7089
+ insertBtn.textContent = L.insertBtn;
4383
7090
  const cancelBtn = createElement("button", {
4384
7091
  type: "button",
4385
7092
  class: "an-btn"
4386
7093
  });
4387
- cancelBtn.textContent = "Cancel";
7094
+ cancelBtn.textContent = L.cancelBtn;
4388
7095
  btnRow.appendChild(insertBtn);
4389
7096
  btnRow.appendChild(cancelBtn);
4390
7097
  box.append(title, urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
4391
7098
  overlay.appendChild(box);
4392
7099
  const d0 = on(urlInput, "input", () => {
4393
7100
  const info = this._parseVideoUrl(urlInput.value.trim());
4394
- hintEl.textContent = info ? `Detected: ${info.type}` : urlInput.value ? "Unknown format — will try direct video embed" : "";
7101
+ hintEl.textContent = info ? this.context.locale.videoDialog.detected(info.type) : urlInput.value ? this.context.locale.videoDialog.unknownFormat : "";
4395
7102
  });
4396
7103
  const d1 = on(insertBtn, "click", () => this._onInsert());
4397
7104
  const d2 = on(cancelBtn, "click", () => this._close());
@@ -4416,7 +7123,7 @@
4416
7123
  }
4417
7124
  const html = this._buildEmbedHtml(rawUrl, width);
4418
7125
  if (!html) {
4419
- this._hintEl.textContent = "Invalid URL — please enter a valid video link.";
7126
+ this._hintEl.textContent = this.context.locale.videoDialog.invalidUrl;
4420
7127
  this._urlInput.focus();
4421
7128
  return;
4422
7129
  }
@@ -4988,19 +7695,20 @@
4988
7695
  this._el = null;
4989
7696
  }
4990
7697
  _buildTooltip() {
7698
+ const L = this.context.locale.tooltips.link;
4991
7699
  const el = createElement("div", {
4992
7700
  class: "an-link-tooltip",
4993
7701
  role: "toolbar",
4994
- "aria-label": "Link actions"
7702
+ "aria-label": L.ariaLabel
4995
7703
  });
4996
7704
  el.style.display = "none";
4997
7705
  this._urlLabel = createElement("span", { class: "an-link-tooltip-url" });
4998
7706
  el.appendChild(this._urlLabel);
4999
7707
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5000
- this._openBtn = this._makeBtn(ICONS$5.open, "Open link", () => this._openLink());
5001
- this._copyBtn = this._makeBtn(ICONS$5.copy, "Copy URL", () => this._copyLink());
5002
- this._editBtn = this._makeBtn(ICONS$5.edit, "Edit link", () => this._editLink());
5003
- this._unlinkBtn = this._makeBtn(ICONS$5.unlink, "Remove link", () => this._unlink());
7708
+ this._openBtn = this._makeBtn(ICONS$5.open, L.openLink, () => this._openLink());
7709
+ this._copyBtn = this._makeBtn(ICONS$5.copy, L.copyUrl, () => this._copyLink());
7710
+ this._editBtn = this._makeBtn(ICONS$5.edit, L.editLink, () => this._editLink());
7711
+ this._unlinkBtn = this._makeBtn(ICONS$5.unlink, L.removeLink, () => this._unlink());
5004
7712
  el.appendChild(this._openBtn);
5005
7713
  el.appendChild(this._copyBtn);
5006
7714
  el.appendChild(this._editBtn);
@@ -5179,38 +7887,39 @@
5179
7887
  this._el = null;
5180
7888
  }
5181
7889
  _buildTooltip() {
7890
+ const L = this.context.locale.tooltips.image;
5182
7891
  const el = createElement("div", {
5183
7892
  class: "an-link-tooltip an-image-tooltip",
5184
7893
  role: "toolbar",
5185
- "aria-label": "Image actions"
7894
+ "aria-label": L.ariaLabel
5186
7895
  });
5187
7896
  el.style.display = "none";
5188
7897
  this._label = createElement("span", { class: "an-link-tooltip-url" });
5189
- this._label.textContent = "Image";
7898
+ this._label.textContent = L.label;
5190
7899
  el.appendChild(this._label);
5191
7900
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5192
- this._floatLeftBtn = this._makeBtn(ICONS$4.floatLeft, "Float Left", () => this._setFloat("left"));
5193
- this._floatNoneBtn = this._makeBtn(ICONS$4.floatNone, "No Float", () => this._setFloat(""));
5194
- this._alignCenterBtn = this._makeBtn(ICONS$4.alignCenter, "Align Center", () => this._setCenter());
5195
- this._floatRightBtn = this._makeBtn(ICONS$4.floatRight, "Float Right", () => this._setFloat("right"));
7901
+ this._floatLeftBtn = this._makeBtn(ICONS$4.floatLeft, L.floatLeft, () => this._setFloat("left"));
7902
+ this._floatNoneBtn = this._makeBtn(ICONS$4.floatNone, L.noFloat, () => this._setFloat(""));
7903
+ this._alignCenterBtn = this._makeBtn(ICONS$4.alignCenter, L.alignCenter, () => this._setCenter());
7904
+ this._floatRightBtn = this._makeBtn(ICONS$4.floatRight, L.floatRight, () => this._setFloat("right"));
5196
7905
  el.appendChild(this._floatLeftBtn);
5197
7906
  el.appendChild(this._floatNoneBtn);
5198
7907
  el.appendChild(this._alignCenterBtn);
5199
7908
  el.appendChild(this._floatRightBtn);
5200
7909
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5201
- this._originalBtn = this._makeBtn(ICONS$4.originalSize, "Original Size", () => this._resetSize());
7910
+ this._originalBtn = this._makeBtn(ICONS$4.originalSize, L.originalSize, () => this._resetSize());
5202
7911
  el.appendChild(this._originalBtn);
5203
7912
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5204
- el.appendChild(this._makeBtn(ICONS$4.rotateLeft, "Rotate Left", () => this._rotate(-90)));
5205
- el.appendChild(this._makeBtn(ICONS$4.rotateRight, "Rotate Right", () => this._rotate(90)));
7913
+ el.appendChild(this._makeBtn(ICONS$4.rotateLeft, L.rotateLeft, () => this._rotate(-90)));
7914
+ el.appendChild(this._makeBtn(ICONS$4.rotateRight, L.rotateRight, () => this._rotate(90)));
5206
7915
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5207
- this._cropBtn = this._makeBtn(ICONS$4.crop, "Crop Image", () => this._crop());
7916
+ this._cropBtn = this._makeBtn(ICONS$4.crop, L.cropImage, () => this._crop());
5208
7917
  el.appendChild(this._cropBtn);
5209
7918
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5210
- this._captionBtn = this._makeBtn(ICONS$4.caption, "Add / Edit Caption", () => this._toggleCaption());
7919
+ this._captionBtn = this._makeBtn(ICONS$4.caption, L.addCaption, () => this._toggleCaption());
5211
7920
  el.appendChild(this._captionBtn);
5212
7921
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5213
- this._deleteBtn = this._makeBtn(ICONS$4.deleteImg, "Delete Image", () => this._delete(), true);
7922
+ this._deleteBtn = this._makeBtn(ICONS$4.deleteImg, L.deleteImage, () => this._delete(), true);
5214
7923
  el.appendChild(this._deleteBtn);
5215
7924
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => this._scheduleHide()));
5216
7925
  return el;
@@ -5462,32 +8171,33 @@
5462
8171
  this._el = null;
5463
8172
  }
5464
8173
  _buildTooltip() {
8174
+ const L = this.context.locale.tooltips.video;
5465
8175
  const el = createElement("div", {
5466
8176
  class: "an-link-tooltip an-video-tooltip",
5467
8177
  role: "toolbar",
5468
- "aria-label": "Video actions"
8178
+ "aria-label": L.ariaLabel
5469
8179
  });
5470
8180
  el.style.display = "none";
5471
8181
  this._label = createElement("span", { class: "an-link-tooltip-url" });
5472
- this._label.textContent = "Video";
8182
+ this._label.textContent = L.label;
5473
8183
  el.appendChild(this._label);
5474
8184
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5475
- this._floatLeftBtn = this._makeBtn(ICONS$3.floatLeft, "Float Left", () => this._setFloat("left"));
5476
- this._floatNoneBtn = this._makeBtn(ICONS$3.floatNone, "No Float", () => this._setFloat(""));
5477
- this._alignCenterBtn = this._makeBtn(ICONS$3.alignCenter, "Align Center", () => this._setCenter());
5478
- this._floatRightBtn = this._makeBtn(ICONS$3.floatRight, "Float Right", () => this._setFloat("right"));
8185
+ this._floatLeftBtn = this._makeBtn(ICONS$3.floatLeft, L.floatLeft, () => this._setFloat("left"));
8186
+ this._floatNoneBtn = this._makeBtn(ICONS$3.floatNone, L.noFloat, () => this._setFloat(""));
8187
+ this._alignCenterBtn = this._makeBtn(ICONS$3.alignCenter, L.alignCenter, () => this._setCenter());
8188
+ this._floatRightBtn = this._makeBtn(ICONS$3.floatRight, L.floatRight, () => this._setFloat("right"));
5479
8189
  el.appendChild(this._floatLeftBtn);
5480
8190
  el.appendChild(this._floatNoneBtn);
5481
8191
  el.appendChild(this._alignCenterBtn);
5482
8192
  el.appendChild(this._floatRightBtn);
5483
8193
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5484
- this._originalBtn = this._makeBtn(ICONS$3.originalSize, "Original Size", () => this._resetSize());
8194
+ this._originalBtn = this._makeBtn(ICONS$3.originalSize, L.originalSize, () => this._resetSize());
5485
8195
  el.appendChild(this._originalBtn);
5486
8196
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5487
- this._previewBtn = this._makeBtn(ICONS$3.preview, "Preview Video", () => this._togglePreview());
8197
+ this._previewBtn = this._makeBtn(ICONS$3.preview, L.previewVideo, () => this._togglePreview());
5488
8198
  el.appendChild(this._previewBtn);
5489
8199
  el.appendChild(createElement("div", { class: "an-link-tooltip-sep" }));
5490
- this._deleteBtn = this._makeBtn(ICONS$3.deleteVideo, "Delete Video", () => this._delete(), true);
8200
+ this._deleteBtn = this._makeBtn(ICONS$3.deleteVideo, L.deleteVideo, () => this._delete(), true);
5491
8201
  el.appendChild(this._deleteBtn);
5492
8202
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => this._scheduleHide()));
5493
8203
  return el;
@@ -5624,7 +8334,7 @@
5624
8334
  if (shield) shield.style.display = "none";
5625
8335
  this.context.invoke("videoResizer.deselect");
5626
8336
  this._previewBtn.classList.add("an-link-tooltip-btn--copied");
5627
- this._previewBtn.title = "Exit Preview";
8337
+ this._previewBtn.title = this.context.locale.tooltips.video.exitPreview;
5628
8338
  this._previewClickOff = (e) => {
5629
8339
  if (!wrapper.contains(e.target) && !this._el.contains(e.target)) this._exitPreview();
5630
8340
  };
@@ -5638,7 +8348,7 @@
5638
8348
  if (shield) shield.style.display = "";
5639
8349
  }
5640
8350
  this._previewBtn.classList.remove("an-link-tooltip-btn--copied");
5641
- this._previewBtn.title = "Preview Video";
8351
+ this._previewBtn.title = this.context.locale.tooltips.video.previewVideo;
5642
8352
  if (this._previewClickOff) {
5643
8353
  document.removeEventListener("mousedown", this._previewClickOff, true);
5644
8354
  this._previewClickOff = null;
@@ -5952,35 +8662,36 @@
5952
8662
  this._sizePopover = null;
5953
8663
  }
5954
8664
  _buildTooltip() {
8665
+ const L = this.context.locale.tooltips.table;
5955
8666
  const el = createElement("div", {
5956
8667
  class: "an-link-tooltip an-table-tooltip",
5957
8668
  role: "toolbar",
5958
- "aria-label": "Table actions"
8669
+ "aria-label": L.ariaLabel
5959
8670
  });
5960
8671
  el.style.display = "none";
5961
8672
  this._label = createElement("span", { class: "an-link-tooltip-url" });
5962
- this._label.textContent = "Table";
8673
+ this._label.textContent = L.label;
5963
8674
  el.appendChild(this._label);
5964
8675
  el.appendChild(this._sep());
5965
- this._selectBtn = this._makeBtn(ICONS$2.selectCells, "Select Cells", () => this._toggleSelectMode());
8676
+ this._selectBtn = this._makeBtn(ICONS$2.selectCells, L.selectCells, () => this._toggleSelectMode());
5966
8677
  el.appendChild(this._selectBtn);
5967
8678
  el.appendChild(this._sep());
5968
- el.appendChild(this._makeBtn(ICONS$2.rowAbove, "Add Row Above", () => this._addRow("above")));
5969
- el.appendChild(this._makeBtn(ICONS$2.rowBelow, "Add Row Below", () => this._addRow("below")));
5970
- el.appendChild(this._makeBtn(ICONS$2.deleteRow, "Delete Row", () => this._deleteRow()));
8679
+ el.appendChild(this._makeBtn(ICONS$2.rowAbove, L.addRowAbove, () => this._addRow("above")));
8680
+ el.appendChild(this._makeBtn(ICONS$2.rowBelow, L.addRowBelow, () => this._addRow("below")));
8681
+ el.appendChild(this._makeBtn(ICONS$2.deleteRow, L.deleteRow, () => this._deleteRow()));
5971
8682
  el.appendChild(this._sep());
5972
- el.appendChild(this._makeBtn(ICONS$2.colLeft, "Add Column Left", () => this._addColumn("left")));
5973
- el.appendChild(this._makeBtn(ICONS$2.colRight, "Add Column Right", () => this._addColumn("right")));
5974
- el.appendChild(this._makeBtn(ICONS$2.deleteCol, "Delete Column", () => this._deleteColumn()));
8683
+ el.appendChild(this._makeBtn(ICONS$2.colLeft, L.addColumnLeft, () => this._addColumn("left")));
8684
+ el.appendChild(this._makeBtn(ICONS$2.colRight, L.addColumnRight, () => this._addColumn("right")));
8685
+ el.appendChild(this._makeBtn(ICONS$2.deleteCol, L.deleteColumn, () => this._deleteColumn()));
5975
8686
  el.appendChild(this._sep());
5976
- el.appendChild(this._makeBtn(ICONS$2.mergeCells, "Merge Cells", () => this._mergeCells()));
5977
- el.appendChild(this._makeBtn(ICONS$2.unmergeCells, "Unmerge Cells", () => this._unmergeCells()));
8687
+ el.appendChild(this._makeBtn(ICONS$2.mergeCells, L.mergeCells, () => this._mergeCells()));
8688
+ el.appendChild(this._makeBtn(ICONS$2.unmergeCells, L.unmergeCells, () => this._unmergeCells()));
5978
8689
  el.appendChild(this._sep());
5979
- el.appendChild(this._makeBtn(ICONS$2.colWidth, "Column Width", () => this._openSizePopover("col")));
5980
- el.appendChild(this._makeBtn(ICONS$2.rowHeight, "Row Height", () => this._openSizePopover("row")));
5981
- el.appendChild(this._makeBtn(ICONS$2.tableBorder, "Table Border Width", () => this._openSizePopover("border")));
8690
+ el.appendChild(this._makeBtn(ICONS$2.colWidth, L.columnWidth, () => this._openSizePopover("col")));
8691
+ el.appendChild(this._makeBtn(ICONS$2.rowHeight, L.rowHeight, () => this._openSizePopover("row")));
8692
+ el.appendChild(this._makeBtn(ICONS$2.tableBorder, L.tableBorderWidth, () => this._openSizePopover("border")));
5982
8693
  el.appendChild(this._sep());
5983
- el.appendChild(this._makeBtn(ICONS$2.deleteTable, "Delete Table", () => this._deleteTable(), true));
8694
+ el.appendChild(this._makeBtn(ICONS$2.deleteTable, L.deleteTable, () => this._deleteTable(), true));
5984
8695
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => {
5985
8696
  if (this._selectMode) return;
5986
8697
  if (this._sizePopover && this._sizePopover.style.display !== "none") return;
@@ -6378,12 +9089,12 @@
6378
9089
  type: "button",
6379
9090
  class: "an-btn"
6380
9091
  });
6381
- cancelBtn.textContent = "Cancel";
9092
+ cancelBtn.textContent = this.context.locale.tooltips.table.cancelBtn;
6382
9093
  const applyBtn = createElement("button", {
6383
9094
  type: "button",
6384
9095
  class: "an-btn an-btn-primary"
6385
9096
  });
6386
- applyBtn.textContent = "Apply";
9097
+ applyBtn.textContent = this.context.locale.tooltips.table.applyBtn;
6387
9098
  actionsEl.appendChild(cancelBtn);
6388
9099
  actionsEl.appendChild(applyBtn);
6389
9100
  popover.appendChild(titleEl);
@@ -6421,7 +9132,7 @@
6421
9132
  if (!table) return;
6422
9133
  const firstCell = table.querySelector("td, th");
6423
9134
  const currentPx = firstCell ? parseInt(firstCell.style.borderWidth, 10) || parseInt(window.getComputedStyle(firstCell).borderWidth, 10) || 1 : 1;
6424
- this._sizeTitleEl.textContent = "Table Border Width (px)";
9135
+ this._sizeTitleEl.textContent = this.context.locale.tooltips.table.tableBorderWidthPx;
6425
9136
  this._sizeInputEl.min = "0";
6426
9137
  this._sizeInputEl.max = "10";
6427
9138
  this._sizeInputEl.value = currentPx;
@@ -6443,7 +9154,7 @@
6443
9154
  const t = c.closest("table");
6444
9155
  return t && t === cell.closest("table");
6445
9156
  });
6446
- this._sizeTitleEl.textContent = isCol ? "Column Width (px)" : "Row Height (px)";
9157
+ this._sizeTitleEl.textContent = isCol ? this.context.locale.tooltips.table.columnWidthPx : this.context.locale.tooltips.table.rowHeightPx;
6447
9158
  this._sizeInputEl.min = "1";
6448
9159
  this._sizeInputEl.max = "2000";
6449
9160
  this._sizeInputEl.value = isCol ? cell.offsetWidth || 120 : cell.closest("tr") ? cell.closest("tr").offsetHeight || 40 : 40;
@@ -6543,20 +9254,21 @@
6543
9254
  this._el = null;
6544
9255
  }
6545
9256
  _buildTooltip() {
9257
+ const L = this.context.locale.tooltips.code;
6546
9258
  const el = createElement("div", {
6547
9259
  class: "an-link-tooltip an-code-tooltip",
6548
9260
  role: "toolbar",
6549
- "aria-label": "Code block actions"
9261
+ "aria-label": L.ariaLabel
6550
9262
  });
6551
9263
  el.style.display = "none";
6552
9264
  this._label = createElement("span", { class: "an-link-tooltip-url" });
6553
- this._label.textContent = "Code";
9265
+ this._label.textContent = L.label;
6554
9266
  el.appendChild(this._label);
6555
9267
  el.appendChild(this._sep());
6556
9268
  this._langSelect = createElement("select", {
6557
9269
  class: "an-code-lang-select",
6558
- title: "Syntax Language",
6559
- "aria-label": "Syntax language"
9270
+ title: L.syntaxLanguage,
9271
+ "aria-label": L.syntaxAriaLabel
6560
9272
  });
6561
9273
  [
6562
9274
  ["", "Plain text"],
@@ -6587,15 +9299,15 @@
6587
9299
  this._disposers.push(on(this._langSelect, "change", () => this._onLangChange()));
6588
9300
  el.appendChild(this._langSelect);
6589
9301
  el.appendChild(this._sep());
6590
- this._copyBtn = this._makeBtn(ICONS$1.copy, "Copy Code", () => this._copyCode());
9302
+ this._copyBtn = this._makeBtn(ICONS$1.copy, L.copyCode, () => this._copyCode());
6591
9303
  el.appendChild(this._copyBtn);
6592
9304
  el.appendChild(this._sep());
6593
- this._wrapBtn = this._makeBtn(ICONS$1.wrapOn, "Toggle Word Wrap", () => this._toggleWrap());
9305
+ this._wrapBtn = this._makeBtn(ICONS$1.wrapOn, L.toggleWordWrap, () => this._toggleWrap());
6594
9306
  el.appendChild(this._wrapBtn);
6595
9307
  el.appendChild(this._sep());
6596
- el.appendChild(this._makeBtn(ICONS$1.toParagraph, "Convert to Paragraph", () => this._toParagraph()));
9308
+ el.appendChild(this._makeBtn(ICONS$1.toParagraph, L.convertToParagraph, () => this._toParagraph()));
6597
9309
  el.appendChild(this._sep());
6598
- el.appendChild(this._makeBtn(ICONS$1.deleteCode, "Delete Code Block", () => this._delete(), true));
9310
+ el.appendChild(this._makeBtn(ICONS$1.deleteCode, L.deleteCodeBlock, () => this._delete(), true));
6599
9311
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => this._scheduleHide()));
6600
9312
  return el;
6601
9313
  }
@@ -6672,7 +9384,7 @@
6672
9384
  if (!this._activePre || !this._wrapBtn) return;
6673
9385
  const wrapped = (this._activePre.style.whiteSpace || "").includes("pre-wrap") || window.getComputedStyle(this._activePre).whiteSpace === "pre-wrap";
6674
9386
  this._wrapBtn.classList.toggle("active", wrapped);
6675
- this._wrapBtn.title = wrapped ? "Disable Word Wrap" : "Enable Word Wrap";
9387
+ this._wrapBtn.title = wrapped ? this.context.locale.tooltips.code.disableWordWrap : this.context.locale.tooltips.code.enableWordWrap;
6676
9388
  }
6677
9389
  _syncLangSelect() {
6678
9390
  if (!this._activePre || !this._langSelect) return;
@@ -9202,27 +11914,28 @@
9202
11914
  this._open();
9203
11915
  }
9204
11916
  _buildDialog() {
11917
+ const L = this.context.locale.emojiDialog;
9205
11918
  const overlay = createElement("div", {
9206
11919
  class: "an-dialog-overlay",
9207
11920
  role: "dialog",
9208
11921
  "aria-modal": "true",
9209
- "aria-label": "Insert emoji"
11922
+ "aria-label": L.ariaLabel
9210
11923
  });
9211
11924
  const box = createElement("div", { class: "an-dialog-box an-emoji-box" });
9212
11925
  const titleRow = createElement("div", { class: "an-icon-title-row" });
9213
11926
  const title = createElement("h3", { class: "an-dialog-title" });
9214
- title.textContent = "Insert Emoji";
11927
+ title.textContent = L.title;
9215
11928
  const closeBtn = createElement("button", {
9216
11929
  type: "button",
9217
11930
  class: "an-icon-close",
9218
- "aria-label": "Close"
11931
+ "aria-label": L.close
9219
11932
  });
9220
11933
  closeBtn.innerHTML = "&times;";
9221
11934
  titleRow.append(title, closeBtn);
9222
11935
  const searchInput = createElement("input", {
9223
11936
  type: "search",
9224
11937
  class: "an-input an-icon-search",
9225
- placeholder: "Search emojis…",
11938
+ placeholder: L.searchPlaceholder,
9226
11939
  autocomplete: "off"
9227
11940
  });
9228
11941
  this._searchInput = searchInput;
@@ -9232,7 +11945,7 @@
9232
11945
  class: "an-icon-cat active",
9233
11946
  "data-cat": "all"
9234
11947
  });
9235
- allTab.textContent = "All";
11948
+ allTab.textContent = L.all;
9236
11949
  catBar.appendChild(allTab);
9237
11950
  EMOJI_CATS.forEach(({ id, label }) => {
9238
11951
  const tab = createElement("button", {
@@ -9240,7 +11953,7 @@
9240
11953
  class: "an-icon-cat",
9241
11954
  "data-cat": id
9242
11955
  });
9243
- tab.textContent = label;
11956
+ tab.textContent = L.categories && L.categories[id] || label;
9244
11957
  catBar.appendChild(tab);
9245
11958
  });
9246
11959
  this._catBar = catBar;
@@ -9263,7 +11976,7 @@
9263
11976
  type: "button",
9264
11977
  class: "an-btn"
9265
11978
  });
9266
- cancelBtn.textContent = "Cancel";
11979
+ cancelBtn.textContent = L.cancelBtn;
9267
11980
  btnRow.appendChild(cancelBtn);
9268
11981
  box.append(titleRow, searchInput, catBar, grid, btnRow);
9269
11982
  overlay.appendChild(box);
@@ -9674,27 +12387,28 @@
9674
12387
  this._open();
9675
12388
  }
9676
12389
  _buildDialog() {
12390
+ const L = this.context.locale.iconDialog;
9677
12391
  const overlay = createElement("div", {
9678
12392
  class: "an-dialog-overlay",
9679
12393
  role: "dialog",
9680
12394
  "aria-modal": "true",
9681
- "aria-label": "Insert FA icon"
12395
+ "aria-label": L.ariaLabel
9682
12396
  });
9683
12397
  const box = createElement("div", { class: "an-dialog-box an-icon-box" });
9684
12398
  const titleRow = createElement("div", { class: "an-icon-title-row" });
9685
12399
  const title = createElement("h3", { class: "an-dialog-title" });
9686
- title.textContent = "Insert FA Icon";
12400
+ title.textContent = L.title;
9687
12401
  const closeBtn = createElement("button", {
9688
12402
  type: "button",
9689
12403
  class: "an-icon-close",
9690
- "aria-label": "Close"
12404
+ "aria-label": L.close
9691
12405
  });
9692
12406
  closeBtn.innerHTML = "&times;";
9693
12407
  titleRow.append(title, closeBtn);
9694
12408
  const searchInput = createElement("input", {
9695
12409
  type: "search",
9696
12410
  class: "an-input an-icon-search",
9697
- placeholder: "Search icons…",
12411
+ placeholder: L.searchPlaceholder,
9698
12412
  autocomplete: "off"
9699
12413
  });
9700
12414
  this._searchInput = searchInput;
@@ -9704,7 +12418,7 @@
9704
12418
  class: "an-icon-cat active",
9705
12419
  "data-cat": "all"
9706
12420
  });
9707
- allTab.textContent = "All";
12421
+ allTab.textContent = L.all;
9708
12422
  catBar.appendChild(allTab);
9709
12423
  ICON_CATEGORIES.forEach(({ id, label }) => {
9710
12424
  const tab = createElement("button", {
@@ -9712,7 +12426,7 @@
9712
12426
  class: "an-icon-cat",
9713
12427
  "data-cat": id
9714
12428
  });
9715
- tab.textContent = label;
12429
+ tab.textContent = L.categories && L.categories[id] || label;
9716
12430
  catBar.appendChild(tab);
9717
12431
  });
9718
12432
  this._catBar = catBar;
@@ -9737,7 +12451,7 @@
9737
12451
  this._grid = grid;
9738
12452
  const optRow = createElement("div", { class: "an-icon-options" });
9739
12453
  const styleLabel = createElement("label", { class: "an-label" });
9740
- styleLabel.textContent = "Style";
12454
+ styleLabel.textContent = L.style;
9741
12455
  const styleSelect = createElement("select", { class: "an-input an-icon-option-select" });
9742
12456
  [
9743
12457
  ["fa-solid", "Solid"],
@@ -9751,7 +12465,7 @@
9751
12465
  styleSelect.value = "fa-solid";
9752
12466
  this._styleSelect = styleSelect;
9753
12467
  const sizeLabel = createElement("label", { class: "an-label" });
9754
- sizeLabel.textContent = "Size";
12468
+ sizeLabel.textContent = L.size;
9755
12469
  const sizeSelect = createElement("select", { class: "an-input an-icon-option-select" });
9756
12470
  [
9757
12471
  ["", "Inherit"],
@@ -9769,7 +12483,7 @@
9769
12483
  });
9770
12484
  this._sizeSelect = sizeSelect;
9771
12485
  const colorLabel = createElement("label", { class: "an-label" });
9772
- colorLabel.textContent = "Color";
12486
+ colorLabel.textContent = L.color;
9773
12487
  const colorInput = createElement("input", {
9774
12488
  type: "color",
9775
12489
  class: "an-icon-color",
@@ -9782,11 +12496,11 @@
9782
12496
  checked: ""
9783
12497
  });
9784
12498
  this._useColorCb = useColorCb;
9785
- useColorLabel.append(useColorCb, document.createTextNode(" Use color"));
12499
+ useColorLabel.append(useColorCb, document.createTextNode(L.useColor));
9786
12500
  optRow.append(styleLabel, styleSelect, sizeLabel, sizeSelect, colorLabel, colorInput, useColorLabel);
9787
12501
  const preview = createElement("div", { class: "an-icon-preview" });
9788
12502
  const previewHint = createElement("span", { class: "an-icon-preview-hint" });
9789
- previewHint.textContent = "Select an icon";
12503
+ previewHint.textContent = L.selectHint;
9790
12504
  preview.appendChild(previewHint);
9791
12505
  this._preview = preview;
9792
12506
  const btnRow = createElement("div", { class: "an-dialog-actions" });
@@ -9795,12 +12509,12 @@
9795
12509
  class: "an-btn an-btn-primary",
9796
12510
  disabled: ""
9797
12511
  });
9798
- insertBtn.textContent = "Insert FA Icon";
12512
+ insertBtn.textContent = L.insertBtn;
9799
12513
  const cancelBtn = createElement("button", {
9800
12514
  type: "button",
9801
12515
  class: "an-btn"
9802
12516
  });
9803
- cancelBtn.textContent = "Cancel";
12517
+ cancelBtn.textContent = L.cancelBtn;
9804
12518
  btnRow.append(insertBtn, cancelBtn);
9805
12519
  this._insertBtn = insertBtn;
9806
12520
  box.append(titleRow, searchInput, catBar, grid, optRow, preview, btnRow);
@@ -10001,9 +12715,11 @@
10001
12715
  ];
10002
12716
  function makeColorSubItems(colorType) {
10003
12717
  const label = colorType === "foreColor" ? "Text Color" : "Highlight Color";
12718
+ const localeKey = colorType === "foreColor" ? "textColor" : "highlightColor";
10004
12719
  return () => [{
10005
12720
  back: true,
10006
12721
  label,
12722
+ localeKey,
10007
12723
  navigate: () => defaultItems
10008
12724
  }, {
10009
12725
  colorPalette: true,
@@ -10200,7 +12916,8 @@
10200
12916
  });
10201
12917
  iconSpan.innerHTML = ICONS.back;
10202
12918
  backBtn.appendChild(iconSpan);
10203
- backBtn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || "Back"]));
12919
+ const backLabel = it.localeKey && this.context.locale.contextMenu[it.localeKey] || it.label || this.context.locale.contextMenu.back || "Back";
12920
+ backBtn.appendChild(createElement("span", { class: "an-context-label" }, [backLabel]));
10204
12921
  const off = on(backBtn, "click", (e) => {
10205
12922
  e.stopPropagation();
10206
12923
  const curLeft = parseFloat(this.el.style.left);
@@ -10238,7 +12955,7 @@
10238
12955
  iconSpan.innerHTML = it.icon;
10239
12956
  btn.appendChild(iconSpan);
10240
12957
  }
10241
- btn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || it.name]));
12958
+ btn.appendChild(createElement("span", { class: "an-context-label" }, [this.context.locale.contextMenu[it.name] || it.label || it.name]));
10242
12959
  const chevron = createElement("span", {
10243
12960
  class: "an-context-chevron",
10244
12961
  "aria-hidden": "true"
@@ -10276,9 +12993,9 @@
10276
12993
  if (it.colorType === "hiliteColor") {
10277
12994
  const noColor = createElement("div", {
10278
12995
  class: "an-context-color-swatch an-context-color-none",
10279
- title: "No highlight",
12996
+ title: this.context.locale.contextMenu.noHighlight || "No highlight",
10280
12997
  role: "button",
10281
- "aria-label": "No highlight"
12998
+ "aria-label": this.context.locale.contextMenu.noHighlight || "No highlight"
10282
12999
  });
10283
13000
  noColor.innerHTML = ICONS.noColor;
10284
13001
  const offNo = on(noColor, "click", (e) => {
@@ -10293,10 +13010,10 @@
10293
13010
  const colorInput = createElement("input", {
10294
13011
  type: "color",
10295
13012
  value: it.colorType === "foreColor" ? "#000000" : "#ffff00",
10296
- title: "Custom color",
10297
- "aria-label": "Custom color"
13013
+ title: this.context.locale.contextMenu.customColor || "Custom color",
13014
+ "aria-label": this.context.locale.contextMenu.customColor || "Custom color"
10298
13015
  });
10299
- const customLabel = createElement("span", {}, ["Custom…"]);
13016
+ const customLabel = createElement("span", {}, [this.context.locale.contextMenu.customColorLabel || "Custom…"]);
10300
13017
  const offCustom = on(colorInput, "change", () => this._applyColor(it.colorType, colorInput.value));
10301
13018
  this._menuDisposers.push(offCustom);
10302
13019
  customRow.appendChild(colorInput);
@@ -10320,7 +13037,7 @@
10320
13037
  iconSpan.innerHTML = it.icon;
10321
13038
  headerBtn.appendChild(iconSpan);
10322
13039
  }
10323
- headerBtn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || "Insert Table"]));
13040
+ headerBtn.appendChild(createElement("span", { class: "an-context-label" }, [this.context.locale.contextMenu.table || it.label || "Insert Table"]));
10324
13041
  const chevron = createElement("span", {
10325
13042
  class: "an-context-chevron",
10326
13043
  "aria-hidden": "true"
@@ -10349,7 +13066,7 @@
10349
13066
  cells.forEach((cell) => {
10350
13067
  cell.classList.toggle("active", +cell.dataset.row <= rows && +cell.dataset.col <= cols);
10351
13068
  });
10352
- labelEl.textContent = rows && cols ? `${cols} × ${rows}` : "Insert Table";
13069
+ labelEl.textContent = rows && cols ? `${cols} × ${rows}` : this.context.locale.contextMenu.table || "Insert Table";
10353
13070
  };
10354
13071
  panel.appendChild(gridEl);
10355
13072
  panel.appendChild(labelEl);
@@ -10403,7 +13120,7 @@
10403
13120
  iconSpan.innerHTML = it.icon;
10404
13121
  btn.appendChild(iconSpan);
10405
13122
  }
10406
- btn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || it.name]));
13123
+ btn.appendChild(createElement("span", { class: "an-context-label" }, [this.context.locale.contextMenu[it.name] || it.label || it.name]));
10407
13124
  const off = on(btn, "click", (e) => {
10408
13125
  e.stopPropagation();
10409
13126
  this.hide();
@@ -10423,12 +13140,18 @@
10423
13140
  if (!editable.contains(event.target)) return;
10424
13141
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
10425
13142
  event.preventDefault();
10426
- this._lastX = event.clientX;
10427
- this._lastY = event.clientY;
10428
13143
  const winSel = window.getSelection();
10429
13144
  this._savedRange = winSel && winSel.rangeCount > 0 ? winSel.getRangeAt(0).cloneRange() : null;
10430
13145
  this._renderItems(this._items);
10431
- this.showAt(event.clientX, event.clientY);
13146
+ let openX = event.clientX;
13147
+ let openY = event.clientY;
13148
+ if (this._savedRange && !this._savedRange.collapsed) try {
13149
+ const selRect = this._savedRange.getBoundingClientRect();
13150
+ if (selRect.width > 0 && selRect.height > 0) openY = selRect.bottom + 4;
13151
+ } catch (_) {}
13152
+ this._lastX = openX;
13153
+ this._lastY = openY;
13154
+ this.showAt(openX, openY);
10432
13155
  }
10433
13156
  _maybeHide(event) {
10434
13157
  if (!this.el) return;
@@ -10439,28 +13162,20 @@
10439
13162
  this.el.style.display = "block";
10440
13163
  this._reposition(x, y);
10441
13164
  this.el.setAttribute("aria-hidden", "false");
13165
+ this.context.triggerEvent("contextMenu:show");
10442
13166
  }
10443
13167
  _reposition(x, y) {
10444
13168
  if (!this.el) return;
10445
13169
  const rx = x !== void 0 ? x : this._lastX;
10446
13170
  const ry = y !== void 0 ? y : this._lastY;
10447
- const rect = this.el.getBoundingClientRect();
13171
+ const w = this.el.offsetWidth;
13172
+ const h = this.el.offsetHeight;
10448
13173
  let left = rx;
10449
13174
  let top = ry;
10450
- if (left + rect.width > window.innerWidth) left = window.innerWidth - rect.width - 8;
13175
+ if (left + w > window.innerWidth - 8) left = window.innerWidth - w - 8;
10451
13176
  if (left < 8) left = 8;
10452
- if (top + rect.height > window.innerHeight) top = window.innerHeight - rect.height - 8;
13177
+ if (top + h > window.innerHeight - 8) top = window.innerHeight - h - 8;
10453
13178
  if (top < 8) top = 8;
10454
- if (this._savedRange) try {
10455
- const sel = this._savedRange.getBoundingClientRect();
10456
- if (sel.width > 0 || sel.height > 0) {
10457
- if (top < sel.bottom && top + rect.height > sel.top && left < sel.right && left + rect.width > sel.left) {
10458
- const belowTop = sel.bottom + 6;
10459
- if (belowTop + rect.height <= window.innerHeight - 8) top = belowTop;
10460
- else top = Math.max(8, sel.top - rect.height - 6);
10461
- }
10462
- }
10463
- } catch (_) {}
10464
13179
  this.el.style.left = `${left}px`;
10465
13180
  this.el.style.top = `${top}px`;
10466
13181
  }
@@ -10468,6 +13183,7 @@
10468
13183
  if (!this.el) return;
10469
13184
  this.el.style.display = "none";
10470
13185
  this.el.setAttribute("aria-hidden", "true");
13186
+ this.context.triggerEvent("contextMenu:hide");
10471
13187
  }
10472
13188
  /** Read the current selection's text or highlight color for the strip.
10473
13189
  * @param {'foreColor'|'hiliteColor'} type
@@ -10717,22 +13433,22 @@
10717
13433
  class: "an-dialog-overlay",
10718
13434
  role: "dialog",
10719
13435
  "aria-modal": "true",
10720
- "aria-label": "Keyboard Shortcuts"
13436
+ "aria-label": this.context.locale.shortcutsDialog.ariaLabel
10721
13437
  });
10722
13438
  const box = createElement("div", { class: "an-dialog-box an-shortcuts-box" });
10723
13439
  const titleRow = createElement("div", { class: "an-icon-title-row" });
10724
13440
  const title = createElement("h3", { class: "an-dialog-title" });
10725
- title.textContent = "Keyboard Shortcuts";
13441
+ title.textContent = this.context.locale.shortcutsDialog.title;
10726
13442
  const closeBtn = createElement("button", {
10727
13443
  type: "button",
10728
13444
  class: "an-icon-close",
10729
- "aria-label": "Close"
13445
+ "aria-label": this.context.locale.shortcutsDialog.close
10730
13446
  });
10731
13447
  closeBtn.textContent = "×";
10732
13448
  this._closeBtn = closeBtn;
10733
13449
  titleRow.append(title, closeBtn);
10734
13450
  box.appendChild(titleRow);
10735
- SHORTCUTS.forEach(({ category, items }) => {
13451
+ (this.context.locale.shortcutsDialog.shortcuts || SHORTCUTS).forEach(({ category, items }) => {
10736
13452
  const catEl = createElement("div", { class: "an-shortcuts-cat" });
10737
13453
  catEl.textContent = category;
10738
13454
  box.appendChild(catEl);
@@ -10857,25 +13573,26 @@
10857
13573
  const isReplace = this._mode === "replace";
10858
13574
  if (replaceRow) replaceRow.style.display = isReplace ? "" : "none";
10859
13575
  if (replaceActions) replaceActions.style.display = isReplace ? "" : "none";
10860
- if (title) title.textContent = isReplace ? "Find & Replace" : "Find";
13576
+ if (title) title.textContent = isReplace ? this.context.locale.findReplace.findReplaceTitle : this.context.locale.findReplace.findTitle;
10861
13577
  }
10862
13578
  _buildDialog() {
13579
+ const L = this.context.locale.findReplace;
10863
13580
  const overlay = createElement("div", {
10864
13581
  class: "an-dialog-overlay an-fr-dialog",
10865
13582
  role: "dialog",
10866
13583
  "aria-modal": "true",
10867
- "aria-label": "Find and Replace"
13584
+ "aria-label": L.findReplaceTitle
10868
13585
  });
10869
13586
  const box = createElement("div", { class: "an-dialog-box" });
10870
13587
  const titleRow = createElement("div", { class: "an-icon-title-row" });
10871
13588
  const title = createElement("h3", { class: "an-dialog-title" });
10872
- title.textContent = "Find";
13589
+ title.textContent = L.findTitle;
10873
13590
  const closeBtn = createElement("button", {
10874
13591
  type: "button",
10875
13592
  class: "an-icon-close",
10876
13593
  "aria-label": "Close"
10877
13594
  });
10878
- closeBtn.textContent = "×";
13595
+ closeBtn.textContent = L.close;
10879
13596
  this._closeBtn = closeBtn;
10880
13597
  titleRow.append(title, closeBtn);
10881
13598
  box.appendChild(titleRow);
@@ -10883,8 +13600,8 @@
10883
13600
  const findInput = createElement("input", {
10884
13601
  type: "text",
10885
13602
  class: "an-input",
10886
- placeholder: "Find…",
10887
- "aria-label": "Search text"
13603
+ placeholder: L.findPlaceholder,
13604
+ "aria-label": L.searchAriaLabel
10888
13605
  });
10889
13606
  this._findInput = findInput;
10890
13607
  findRow.appendChild(findInput);
@@ -10896,7 +13613,7 @@
10896
13613
  "aria-label": "Case sensitive"
10897
13614
  });
10898
13615
  this._caseCheckbox = caseCheckbox;
10899
- caseLabel.append(caseCheckbox, document.createTextNode("\xA0Case sensitive"));
13616
+ caseLabel.append(caseCheckbox, document.createTextNode(L.caseSensitive));
10900
13617
  const counter = createElement("span", { class: "an-fr-counter" });
10901
13618
  this._counterEl = counter;
10902
13619
  optRow.append(caseLabel, counter);
@@ -10906,12 +13623,12 @@
10906
13623
  type: "button",
10907
13624
  class: "an-btn"
10908
13625
  });
10909
- prevBtn.textContent = "← Prev";
13626
+ prevBtn.textContent = L.prevBtn;
10910
13627
  const nextBtn = createElement("button", {
10911
13628
  type: "button",
10912
13629
  class: "an-btn an-btn-primary"
10913
13630
  });
10914
- nextBtn.textContent = "Next →";
13631
+ nextBtn.textContent = L.nextBtn;
10915
13632
  findActions.append(prevBtn, nextBtn);
10916
13633
  box.appendChild(findActions);
10917
13634
  const replaceRow = createElement("div", { class: "an-fr-replace-row" });
@@ -10919,8 +13636,8 @@
10919
13636
  const replaceInput = createElement("input", {
10920
13637
  type: "text",
10921
13638
  class: "an-input",
10922
- placeholder: "Replace with…",
10923
- "aria-label": "Replace with"
13639
+ placeholder: L.replacePlaceholder,
13640
+ "aria-label": L.replaceAriaLabel
10924
13641
  });
10925
13642
  this._replaceInput = replaceInput;
10926
13643
  replaceRow.appendChild(replaceInput);
@@ -10931,12 +13648,12 @@
10931
13648
  type: "button",
10932
13649
  class: "an-btn"
10933
13650
  });
10934
- replaceBtn.textContent = "Replace";
13651
+ replaceBtn.textContent = L.replaceBtn;
10935
13652
  const replaceAllBtn = createElement("button", {
10936
13653
  type: "button",
10937
13654
  class: "an-btn an-btn-primary"
10938
13655
  });
10939
- replaceAllBtn.textContent = "Replace All";
13656
+ replaceAllBtn.textContent = L.replaceAllBtn;
10940
13657
  replaceActions.append(replaceBtn, replaceAllBtn);
10941
13658
  box.appendChild(replaceActions);
10942
13659
  overlay.appendChild(box);
@@ -11581,6 +14298,771 @@
11581
14298
  }
11582
14299
  };
11583
14300
  //#endregion
14301
+ //#region src/js/module/AutoSaveRestore.js
14302
+ /**
14303
+ * AutoSaveRestore.js — Detects a previously auto-saved draft and offers the
14304
+ * user a banner to restore or discard it.
14305
+ *
14306
+ * Activated when both `autoSave` and `autoSaveRestore` options are true.
14307
+ * On initialize it checks localStorage for a draft that is within the
14308
+ * `autoSaveRestoreTimeout` day window. If one is found a dismissible banner
14309
+ * is prepended to the editor container.
14310
+ */
14311
+ var AutoSaveRestore = class {
14312
+ /** @param {import('../Context.js').Context} context */
14313
+ constructor(context) {
14314
+ this.context = context;
14315
+ this.options = context.options;
14316
+ /** @type {HTMLElement|null} */
14317
+ this._banner = null;
14318
+ }
14319
+ initialize() {
14320
+ if (!this.options.autoSave || !this.options.autoSaveRestore) return this;
14321
+ const key = this.options.autoSaveKey;
14322
+ const metaKey = key + ":asrmeta";
14323
+ let saved;
14324
+ let meta;
14325
+ try {
14326
+ saved = localStorage.getItem(key);
14327
+ meta = JSON.parse(localStorage.getItem(metaKey) || "{}");
14328
+ } catch (_) {
14329
+ return this;
14330
+ }
14331
+ if (!saved) return this;
14332
+ const timeout = this.options.autoSaveRestoreTimeout;
14333
+ if (timeout > 0) {
14334
+ if (Date.now() - (meta.savedAt || 0) > timeout * 864e5) {
14335
+ try {
14336
+ localStorage.removeItem(key);
14337
+ localStorage.removeItem(metaKey);
14338
+ } catch (_) {}
14339
+ return this;
14340
+ }
14341
+ }
14342
+ this._showBanner(saved, meta);
14343
+ return this;
14344
+ }
14345
+ destroy() {
14346
+ this._removeBanner();
14347
+ }
14348
+ _showBanner(draftHtml, meta) {
14349
+ const t = this.context.locale.autoSaveRestore || {};
14350
+ const label = t.found || "Draft found. Restore?";
14351
+ const restoreLabel = t.restore || "Restore";
14352
+ const discardLabel = t.discard || "Discard";
14353
+ const banner = document.createElement("div");
14354
+ banner.className = "an-asr-banner";
14355
+ banner.setAttribute("role", "alert");
14356
+ const msg = document.createElement("span");
14357
+ msg.className = "an-asr-msg";
14358
+ if (meta.savedAt) {
14359
+ const formatted = new Date(meta.savedAt).toLocaleString();
14360
+ msg.textContent = (t.foundAt || "Draft from {date}. Restore?").replace("{date}", formatted);
14361
+ } else msg.textContent = label;
14362
+ const restoreBtn = document.createElement("button");
14363
+ restoreBtn.type = "button";
14364
+ restoreBtn.className = "an-btn an-asr-btn-restore";
14365
+ restoreBtn.textContent = restoreLabel;
14366
+ restoreBtn.addEventListener("click", () => this._restore(draftHtml));
14367
+ const discardBtn = document.createElement("button");
14368
+ discardBtn.type = "button";
14369
+ discardBtn.className = "an-btn an-asr-btn-discard";
14370
+ discardBtn.textContent = discardLabel;
14371
+ discardBtn.addEventListener("click", () => this._discard());
14372
+ banner.appendChild(msg);
14373
+ banner.appendChild(restoreBtn);
14374
+ banner.appendChild(discardBtn);
14375
+ this._banner = banner;
14376
+ const container = this.context.layoutInfo.container;
14377
+ container.insertBefore(banner, container.firstChild);
14378
+ }
14379
+ _restore(draftHtml) {
14380
+ this.context.setHTML(draftHtml);
14381
+ this.context.clearHistory();
14382
+ this._removeBanner();
14383
+ if (typeof this.options.onAutoSaveRestore === "function") this.options.onAutoSaveRestore(draftHtml, this.context);
14384
+ }
14385
+ _discard() {
14386
+ const key = this.options.autoSaveKey;
14387
+ try {
14388
+ localStorage.removeItem(key);
14389
+ localStorage.removeItem(key + ":asrmeta");
14390
+ } catch (_) {}
14391
+ this._removeBanner();
14392
+ }
14393
+ _removeBanner() {
14394
+ if (this._banner && this._banner.parentNode) this._banner.parentNode.removeChild(this._banner);
14395
+ this._banner = null;
14396
+ }
14397
+ };
14398
+ //#endregion
14399
+ //#region src/js/module/MarkdownShortcuts.js
14400
+ /**
14401
+ * MarkdownShortcuts.js — Convert Markdown-style syntax typed directly in the
14402
+ * editor into rich HTML elements (input rules / auto-format).
14403
+ *
14404
+ * Activated when `markdownShortcuts: true` (the default).
14405
+ *
14406
+ * Block rules — triggered by Space or Enter at the start of a line:
14407
+ * #[#[#]]· → H1 / H2 / H3
14408
+ * >· → blockquote
14409
+ * -· or *· → unordered list item
14410
+ * 1.· → ordered list item
14411
+ * [ ]· → checklist item
14412
+ * --- → horizontal rule (on Enter)
14413
+ * ``` → code block (on Enter)
14414
+ *
14415
+ * Inline rules — triggered when the closing marker is typed:
14416
+ * **text** → <strong>
14417
+ * *text* → <em>
14418
+ * ~~text~~ → <s>
14419
+ * `code` → <code>
14420
+ */
14421
+ var MarkdownShortcuts = class {
14422
+ /** @param {import('../Context.js').Context} context */
14423
+ constructor(context) {
14424
+ this.context = context;
14425
+ this.options = context.options;
14426
+ this._disposers = [];
14427
+ }
14428
+ initialize() {
14429
+ if (!this.options.markdownShortcuts) return this;
14430
+ const editable = this.context.layoutInfo.editable;
14431
+ const d1 = on(
14432
+ editable,
14433
+ "keydown",
14434
+ /** @param {KeyboardEvent} e */
14435
+ (e) => this._onKeydown(e)
14436
+ );
14437
+ const d2 = on(editable, "input", () => this._onInput());
14438
+ this._disposers.push(d1, d2);
14439
+ return this;
14440
+ }
14441
+ destroy() {
14442
+ this._disposers.forEach((d) => d());
14443
+ this._disposers = [];
14444
+ }
14445
+ _onKeydown(e) {
14446
+ if (e.key === " ") {
14447
+ if (this._applyBlockRule()) e.preventDefault();
14448
+ } else if (e.key === "Enter") {
14449
+ if (this._applyEnterRule()) e.preventDefault();
14450
+ }
14451
+ }
14452
+ /**
14453
+ * Returns the plain text of the line the cursor is on, up to the cursor.
14454
+ * @returns {{ text: string, range: Range, lineNode: Node } | null}
14455
+ */
14456
+ _getLineContext() {
14457
+ const sel = window.getSelection();
14458
+ if (!sel || !sel.rangeCount) return null;
14459
+ const range = sel.getRangeAt(0);
14460
+ if (!range.collapsed) return null;
14461
+ const editable = this.context.layoutInfo.editable;
14462
+ if (!editable.contains(range.startContainer)) return null;
14463
+ let node = range.startContainer;
14464
+ while (node && node !== editable && !this._isBlock(node)) node = node.parentNode;
14465
+ if (!node || node === editable) node = range.startContainer;
14466
+ const tmpRange = document.createRange();
14467
+ tmpRange.setStart(node, 0);
14468
+ tmpRange.setEnd(range.startContainer, range.startOffset);
14469
+ return {
14470
+ text: tmpRange.toString(),
14471
+ range,
14472
+ lineNode: node
14473
+ };
14474
+ }
14475
+ _isBlock(node) {
14476
+ if (node.nodeType !== Node.ELEMENT_NODE) return false;
14477
+ const display = window.getComputedStyle(node).display;
14478
+ return display === "block" || display === "list-item" || display === "table-cell";
14479
+ }
14480
+ /** Applies block rule on Space key. Returns true if a rule fired. */
14481
+ _applyBlockRule() {
14482
+ const ctx = this._getLineContext();
14483
+ if (!ctx) return false;
14484
+ const { text } = ctx;
14485
+ const blockPatterns = [
14486
+ {
14487
+ re: /^(#{1,3})$/,
14488
+ handler: (m) => this._convertToHeading(m[1].length)
14489
+ },
14490
+ {
14491
+ re: /^>$/,
14492
+ handler: () => this._convertToBlockquote()
14493
+ },
14494
+ {
14495
+ re: /^[-*]$/,
14496
+ handler: () => this._convertToList("ul")
14497
+ },
14498
+ {
14499
+ re: /^1\.$/,
14500
+ handler: () => this._convertToList("ol")
14501
+ },
14502
+ {
14503
+ re: /^\[ \]$/,
14504
+ handler: () => this._convertToChecklist()
14505
+ }
14506
+ ];
14507
+ for (const { re, handler } of blockPatterns) {
14508
+ const m = text.match(re);
14509
+ if (m) {
14510
+ handler(m);
14511
+ return true;
14512
+ }
14513
+ }
14514
+ return false;
14515
+ }
14516
+ /** Applies block rule on Enter key (---, ```). Returns true if a rule fired. */
14517
+ _applyEnterRule() {
14518
+ const ctx = this._getLineContext();
14519
+ if (!ctx) return false;
14520
+ const { text } = ctx;
14521
+ if (/^-{3,}$/.test(text)) {
14522
+ this._convertToHr();
14523
+ return true;
14524
+ }
14525
+ if (/^`{3}/.test(text)) {
14526
+ this._convertToCodeBlock();
14527
+ return true;
14528
+ }
14529
+ return false;
14530
+ }
14531
+ _selectLineAndDelete() {
14532
+ const sel = window.getSelection();
14533
+ if (!sel || !sel.rangeCount) return;
14534
+ const range = sel.getRangeAt(0);
14535
+ const startRange = document.createRange();
14536
+ startRange.setStart(range.startContainer.parentNode || range.startContainer, 0);
14537
+ startRange.setEnd(range.startContainer, range.startOffset);
14538
+ startRange.deleteContents();
14539
+ }
14540
+ _convertToHeading(level) {
14541
+ this._selectLineAndDelete();
14542
+ document.execCommand("formatBlock", false, `h${level}`);
14543
+ this.context.triggerEvent("change", this.context.getHTML());
14544
+ }
14545
+ _convertToBlockquote() {
14546
+ this._selectLineAndDelete();
14547
+ document.execCommand("formatBlock", false, "blockquote");
14548
+ this.context.triggerEvent("change", this.context.getHTML());
14549
+ }
14550
+ _convertToList(type) {
14551
+ this._selectLineAndDelete();
14552
+ document.execCommand(type === "ul" ? "insertUnorderedList" : "insertOrderedList");
14553
+ this.context.triggerEvent("change", this.context.getHTML());
14554
+ }
14555
+ _convertToChecklist() {
14556
+ this._selectLineAndDelete();
14557
+ this.context.invoke("editor.checklist");
14558
+ this.context.triggerEvent("change", this.context.getHTML());
14559
+ }
14560
+ _convertToHr() {
14561
+ this._selectLineAndDelete();
14562
+ this.context.invoke("editor.insertHR");
14563
+ this.context.triggerEvent("change", this.context.getHTML());
14564
+ }
14565
+ _convertToCodeBlock() {
14566
+ this._selectLineAndDelete();
14567
+ document.execCommand("formatBlock", false, "pre");
14568
+ this.context.triggerEvent("change", this.context.getHTML());
14569
+ }
14570
+ _onInput() {
14571
+ const sel = window.getSelection();
14572
+ if (!sel || !sel.rangeCount) return;
14573
+ const range = sel.getRangeAt(0);
14574
+ if (!range.collapsed) return;
14575
+ if (!this.context.layoutInfo.editable.contains(range.startContainer)) return;
14576
+ const node = range.startContainer;
14577
+ if (node.nodeType !== Node.TEXT_NODE) return;
14578
+ const text = node.textContent;
14579
+ const offset = range.startOffset;
14580
+ const inlineRules = [
14581
+ {
14582
+ re: /\*\*(.+?)\*\*$/,
14583
+ tag: "strong"
14584
+ },
14585
+ {
14586
+ re: /(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)$/,
14587
+ tag: "em"
14588
+ },
14589
+ {
14590
+ re: /~~(.+?)~~$/,
14591
+ tag: "s"
14592
+ },
14593
+ {
14594
+ re: /`([^`]+)`$/,
14595
+ tag: "code"
14596
+ }
14597
+ ];
14598
+ const upToCursor = text.slice(0, offset);
14599
+ for (const { re, tag } of inlineRules) {
14600
+ const m = upToCursor.match(re);
14601
+ if (!m) continue;
14602
+ const matchStart = upToCursor.length - m[0].length;
14603
+ const matchEnd = offset;
14604
+ const innerText = m[1];
14605
+ const before = text.slice(0, matchStart);
14606
+ const after = text.slice(matchEnd);
14607
+ const el = document.createElement(tag);
14608
+ el.textContent = innerText;
14609
+ const beforeNode = document.createTextNode(before);
14610
+ const afterNode = document.createTextNode("​" + after);
14611
+ const parent = node.parentNode;
14612
+ parent.insertBefore(beforeNode, node);
14613
+ parent.insertBefore(el, node);
14614
+ parent.insertBefore(afterNode, node);
14615
+ parent.removeChild(node);
14616
+ const newRange = document.createRange();
14617
+ newRange.setStart(afterNode, 1);
14618
+ newRange.collapse(true);
14619
+ sel.removeAllRanges();
14620
+ sel.addRange(newRange);
14621
+ this.context.triggerEvent("change", this.context.getHTML());
14622
+ break;
14623
+ }
14624
+ }
14625
+ };
14626
+ //#endregion
14627
+ //#region src/js/module/BubbleToolbar.js
14628
+ /**
14629
+ * BubbleToolbar.js — A mini floating toolbar that appears above the current
14630
+ * text selection, allowing quick access to common formatting actions without
14631
+ * reaching for the main toolbar.
14632
+ *
14633
+ * Activated when `bubbleToolbar: true`. The set of visible buttons is
14634
+ * controlled by `bubbleToolbarItems` (array of button name strings).
14635
+ *
14636
+ * Built-in names: 'bold', 'italic', 'underline', 'strikethrough',
14637
+ * 'link', 'foreColor', 'removeFormat', 'inlineCode'.
14638
+ * Any name not found in the built-in map is silently ignored.
14639
+ */
14640
+ var _S = "stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
14641
+ var _svg = (p) => `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" ${_S} style="display:block">${p}</svg>`;
14642
+ var _ICONS = {
14643
+ bold: _svg("<path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/>"),
14644
+ italic: _svg("<line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"/><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"/><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"/>"),
14645
+ underline: _svg("<path d=\"M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3\"/><line x1=\"4\" y1=\"21\" x2=\"20\" y2=\"21\"/>"),
14646
+ strikethrough: _svg("<path d=\"M17.3 12H6.7\"/><path d=\"M10 6.5C10 5.1 11.1 4 12.5 4c1.4 0 2.5 1.1 2.5 2.5 0 .8-.4 1.5-1 2\"/><path d=\"M14 17.5C14 19 12.9 20 11.5 20 10.1 20 9 18.9 9 17.5c0-.8.4-1.5 1-2\"/>"),
14647
+ link: _svg("<path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/>"),
14648
+ foreColor: _svg("<path d=\"M4 20L12 4L20 20\"/><line x1=\"7.5\" y1=\"14\" x2=\"16.5\" y2=\"14\"/>"),
14649
+ removeFormat: _svg("<path d=\"m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21\"/><path d=\"M22 21H7\"/><path d=\"m5 11 9 9\"/>"),
14650
+ inlineCode: _svg("<path d=\"M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1\"/><path d=\"M16 3h1a2 2 0 0 1 2 2v5c0 1.1.9 2 2 2a2 2 0 0 1-2 2v5a2 2 0 0 1-2 2h-1\"/>")
14651
+ };
14652
+ var _ACTIONS = {
14653
+ bold: (ctx) => ctx.invoke("editor.bold"),
14654
+ italic: (ctx) => ctx.invoke("editor.italic"),
14655
+ underline: (ctx) => ctx.invoke("editor.underline"),
14656
+ strikethrough: (ctx) => ctx.invoke("editor.strikethrough"),
14657
+ link: (ctx) => ctx.invoke("linkDialog.show"),
14658
+ foreColor: (ctx) => {
14659
+ const color = window.prompt("Text color (hex or name):", "#000000");
14660
+ if (color) ctx.invoke("editor.foreColor", color);
14661
+ },
14662
+ removeFormat: (ctx) => ctx.invoke("editor.removeFormat"),
14663
+ inlineCode: (ctx) => ctx.invoke("editor.inlineCode")
14664
+ };
14665
+ var _ACTIVE = {
14666
+ bold: () => document.queryCommandState("bold"),
14667
+ italic: () => document.queryCommandState("italic"),
14668
+ underline: () => document.queryCommandState("underline"),
14669
+ strikethrough: () => document.queryCommandState("strikeThrough")
14670
+ };
14671
+ var BubbleToolbar = class {
14672
+ /** @param {import('../Context.js').Context} context */
14673
+ constructor(context) {
14674
+ this.context = context;
14675
+ this.options = context.options;
14676
+ /** @type {HTMLElement|null} */
14677
+ this._el = null;
14678
+ this._visible = false;
14679
+ this._rafId = null;
14680
+ this._contextMenuOpen = false;
14681
+ this._disposers = [];
14682
+ this._onSelectionChange = this._onSelectionChange.bind(this);
14683
+ this._onMousedown = this._onMousedown.bind(this);
14684
+ this._onKeydown = this._onKeydown.bind(this);
14685
+ this._onContextMenu = this._onContextMenu.bind(this);
14686
+ }
14687
+ initialize() {
14688
+ if (!this.options.bubbleToolbar) return this;
14689
+ this._build();
14690
+ const d1 = on(document, "selectionchange", this._onSelectionChange);
14691
+ const d2 = on(document, "mousedown", this._onMousedown);
14692
+ const d3 = on(document, "keydown", this._onKeydown);
14693
+ const d4 = on(document, "contextmenu", this._onContextMenu);
14694
+ const d5 = this.context.on("contextMenu:show", () => {
14695
+ this._contextMenuOpen = true;
14696
+ this._hide();
14697
+ });
14698
+ const d6 = this.context.on("contextMenu:hide", () => {
14699
+ this._contextMenuOpen = false;
14700
+ });
14701
+ this._disposers.push(d1, d2, d3, d4, d5, d6);
14702
+ return this;
14703
+ }
14704
+ destroy() {
14705
+ if (this._el && this._el.parentNode) this._el.parentNode.removeChild(this._el);
14706
+ this._el = null;
14707
+ this._disposers.forEach((d) => d());
14708
+ this._disposers = [];
14709
+ cancelAnimationFrame(this._rafId);
14710
+ }
14711
+ _build() {
14712
+ const el = document.createElement("div");
14713
+ el.className = "an-bubble-toolbar";
14714
+ el.setAttribute("role", "toolbar");
14715
+ el.setAttribute("aria-label", "Formatting");
14716
+ const items = this.options.bubbleToolbarItems || [
14717
+ "bold",
14718
+ "italic",
14719
+ "underline",
14720
+ "link",
14721
+ "foreColor",
14722
+ "removeFormat"
14723
+ ];
14724
+ for (const name of items) {
14725
+ if (!_ICONS[name] || !_ACTIONS[name]) continue;
14726
+ const btn = document.createElement("button");
14727
+ btn.type = "button";
14728
+ btn.className = "an-bubble-btn";
14729
+ btn.dataset.name = name;
14730
+ btn.setAttribute("aria-label", name);
14731
+ btn.innerHTML = _ICONS[name];
14732
+ btn.addEventListener("mousedown", (e) => {
14733
+ e.preventDefault();
14734
+ });
14735
+ btn.addEventListener("click", (e) => {
14736
+ e.preventDefault();
14737
+ e.stopPropagation();
14738
+ this.context.invoke("editor.focus");
14739
+ _ACTIONS[name](this.context);
14740
+ this.context.invoke("editor.afterCommand");
14741
+ this._syncActive();
14742
+ });
14743
+ el.appendChild(btn);
14744
+ }
14745
+ document.body.appendChild(el);
14746
+ this._el = el;
14747
+ }
14748
+ _show(rect) {
14749
+ if (!this._el) return;
14750
+ const el = this._el;
14751
+ el.style.visibility = "hidden";
14752
+ el.style.display = "flex";
14753
+ const bw = el.offsetWidth;
14754
+ const bh = el.offsetHeight;
14755
+ const gap = 8;
14756
+ let left = rect.left + rect.width / 2 - bw / 2;
14757
+ let top = rect.top - bh - gap;
14758
+ left = Math.max(8, Math.min(left, window.innerWidth - bw - 8));
14759
+ if (top < 8) top = rect.bottom + gap;
14760
+ el.style.top = `${top}px`;
14761
+ el.style.left = `${left}px`;
14762
+ el.style.visibility = "";
14763
+ this._syncActive();
14764
+ this._visible = true;
14765
+ }
14766
+ _hide() {
14767
+ if (!this._el) return;
14768
+ this._el.style.display = "none";
14769
+ this._visible = false;
14770
+ }
14771
+ _syncActive() {
14772
+ if (!this._el) return;
14773
+ this._el.querySelectorAll(".an-bubble-btn").forEach((btn) => {
14774
+ const activeFn = _ACTIVE[btn.dataset.name];
14775
+ btn.classList.toggle("an-active", !!(activeFn && activeFn()));
14776
+ });
14777
+ }
14778
+ _onSelectionChange() {
14779
+ cancelAnimationFrame(this._rafId);
14780
+ this._rafId = requestAnimationFrame(() => {
14781
+ if (this._contextMenuOpen) return;
14782
+ const sel = window.getSelection();
14783
+ if (!sel || sel.isCollapsed || !sel.rangeCount) {
14784
+ this._hide();
14785
+ return;
14786
+ }
14787
+ if (!this.context.layoutInfo.editable.contains(sel.anchorNode)) {
14788
+ this._hide();
14789
+ return;
14790
+ }
14791
+ if (this.context.options.readOnly) {
14792
+ this._hide();
14793
+ return;
14794
+ }
14795
+ const rect = sel.getRangeAt(0).getBoundingClientRect();
14796
+ if (!rect || rect.width === 0) {
14797
+ this._hide();
14798
+ return;
14799
+ }
14800
+ this._show(rect);
14801
+ });
14802
+ }
14803
+ _onMousedown(e) {
14804
+ if (!this._visible) return;
14805
+ if (this._el && this._el.contains(e.target)) return;
14806
+ if (this.context.layoutInfo.editable.contains(e.target)) return;
14807
+ this._hide();
14808
+ }
14809
+ _onKeydown(e) {
14810
+ if (e.key === "Escape" && this._visible) this._hide();
14811
+ }
14812
+ _onContextMenu() {
14813
+ this._hide();
14814
+ }
14815
+ };
14816
+ //#endregion
14817
+ //#region src/js/module/Mention.js
14818
+ /**
14819
+ * Mention.js — @mention autocomplete support.
14820
+ *
14821
+ * Activated when `mention.onSearch` option is provided.
14822
+ *
14823
+ * When the user types the trigger character (default `@`) followed by at least
14824
+ * `mention.minChars` characters, `onSearch(query, callback)` is called.
14825
+ * The callback receives an array of `{ id, label, avatar? }` items which are
14826
+ * rendered in a floating dropdown. Selecting an item inserts a non-editable
14827
+ * mention chip and fires `onInsert` (if provided) to override the default HTML.
14828
+ *
14829
+ * Options shape (passed as `mention` option object):
14830
+ * {
14831
+ * trigger: '@',
14832
+ * minChars: 1,
14833
+ * maxResults: 8,
14834
+ * debounce: 200,
14835
+ * onSearch: (query, callback) => void,
14836
+ * onInsert: (item) => string | null,
14837
+ * mentionClass: 'an-mention',
14838
+ * allowSpaces: false,
14839
+ * }
14840
+ */
14841
+ var Mention = class {
14842
+ /** @param {import('../Context.js').Context} context */
14843
+ constructor(context) {
14844
+ this.context = context;
14845
+ /** @type {HTMLElement|null} */
14846
+ this._dropdown = null;
14847
+ /** @type {string} */
14848
+ this._query = "";
14849
+ /** @type {number|null} */
14850
+ this._debounceTimer = null;
14851
+ /** @type {number} current highlighted index */
14852
+ this._activeIndex = -1;
14853
+ /** @type {Array<{id, label, avatar?}>} */
14854
+ this._items = [];
14855
+ /** @type {boolean} */
14856
+ this._open = false;
14857
+ /** Position of trigger character in the text node */
14858
+ this._triggerNode = null;
14859
+ this._triggerOffset = 0;
14860
+ /** @type {DOMRect|null} Caret rect captured synchronously during input event */
14861
+ this._caretRect = null;
14862
+ this._disposers = [];
14863
+ }
14864
+ initialize() {
14865
+ const cfg = this.context.options.mention;
14866
+ if (!cfg || typeof cfg.onSearch !== "function") return this;
14867
+ this._cfg = {
14868
+ trigger: cfg.trigger || "@",
14869
+ minChars: cfg.minChars ?? 0,
14870
+ maxResults: cfg.maxResults ?? 8,
14871
+ debounce: cfg.debounce ?? 200,
14872
+ onSearch: cfg.onSearch,
14873
+ onInsert: cfg.onInsert || null,
14874
+ mentionClass: cfg.mentionClass || "an-mention",
14875
+ allowSpaces: cfg.allowSpaces || false
14876
+ };
14877
+ this._buildDropdown();
14878
+ const editable = this.context.layoutInfo.editable;
14879
+ const d1 = on(editable, "keydown", (e) => this._onKeydown(e));
14880
+ const d2 = on(editable, "input", () => this._onInput());
14881
+ const d3 = on(document, "click", (e) => this._onDocClick(e));
14882
+ this._disposers.push(d1, d2, d3);
14883
+ return this;
14884
+ }
14885
+ destroy() {
14886
+ clearTimeout(this._debounceTimer);
14887
+ if (this._dropdown && this._dropdown.parentNode) this._dropdown.parentNode.removeChild(this._dropdown);
14888
+ this._dropdown = null;
14889
+ this._disposers.forEach((d) => d());
14890
+ this._disposers = [];
14891
+ }
14892
+ _buildDropdown() {
14893
+ const el = document.createElement("div");
14894
+ el.className = "an-mention-dropdown";
14895
+ el.setAttribute("role", "listbox");
14896
+ document.body.appendChild(el);
14897
+ this._dropdown = el;
14898
+ }
14899
+ _renderItems(items) {
14900
+ const dd = this._dropdown;
14901
+ dd.innerHTML = "";
14902
+ this._items = items.slice(0, this._cfg.maxResults);
14903
+ this._activeIndex = this._items.length > 0 ? 0 : -1;
14904
+ this._items.forEach((item, i) => {
14905
+ const li = document.createElement("div");
14906
+ li.className = "an-mention-item";
14907
+ li.setAttribute("role", "option");
14908
+ li.dataset.index = i;
14909
+ if (item.avatar) {
14910
+ const img = document.createElement("img");
14911
+ img.src = item.avatar;
14912
+ img.className = "an-mention-avatar";
14913
+ img.alt = "";
14914
+ li.appendChild(img);
14915
+ }
14916
+ const label = document.createElement("span");
14917
+ label.textContent = item.label;
14918
+ li.appendChild(label);
14919
+ li.addEventListener("mousedown", (e) => e.preventDefault());
14920
+ li.addEventListener("click", () => this._select(i));
14921
+ dd.appendChild(li);
14922
+ });
14923
+ this._highlightItem(this._activeIndex);
14924
+ }
14925
+ _highlightItem(index) {
14926
+ if (!this._dropdown) return;
14927
+ this._dropdown.querySelectorAll(".an-mention-item").forEach((el, i) => {
14928
+ el.classList.toggle("an-mention-active", i === index);
14929
+ });
14930
+ this._activeIndex = index;
14931
+ }
14932
+ _positionDropdown() {
14933
+ const dd = this._dropdown;
14934
+ const rect = this._caretRect;
14935
+ if (!rect || rect.height === 0) return;
14936
+ dd.style.visibility = "hidden";
14937
+ dd.style.display = "block";
14938
+ const ddh = dd.offsetHeight;
14939
+ const ddw = dd.offsetWidth;
14940
+ let top = rect.bottom + 4;
14941
+ let left = rect.left;
14942
+ if (rect.bottom + ddh + 8 > window.innerHeight) top = rect.top - ddh - 4;
14943
+ left = Math.max(8, Math.min(left, window.innerWidth - ddw - 8));
14944
+ dd.style.top = `${top}px`;
14945
+ dd.style.left = `${left}px`;
14946
+ dd.style.visibility = "";
14947
+ }
14948
+ _showDropdown() {
14949
+ this._open = true;
14950
+ this._positionDropdown();
14951
+ }
14952
+ _hideDropdown() {
14953
+ if (this._dropdown) this._dropdown.style.display = "none";
14954
+ this._open = false;
14955
+ this._items = [];
14956
+ this._activeIndex = -1;
14957
+ this._triggerNode = null;
14958
+ this._caretRect = null;
14959
+ this._query = "";
14960
+ }
14961
+ /**
14962
+ * Captures the caret rect synchronously during the input event.
14963
+ * Must be called while the DOM event is still live — getClientRects() on a
14964
+ * collapsed range is reliable at this point but often empty inside async callbacks.
14965
+ */
14966
+ _captureCaretRect() {
14967
+ if (this._triggerNode && this._triggerNode.isConnected) try {
14968
+ const r = document.createRange();
14969
+ const end = Math.min(this._triggerOffset + 1, this._triggerNode.textContent.length);
14970
+ r.setStart(this._triggerNode, this._triggerOffset);
14971
+ r.setEnd(this._triggerNode, end);
14972
+ const candidate = r.getBoundingClientRect();
14973
+ if (candidate.height > 0) {
14974
+ this._caretRect = candidate;
14975
+ return;
14976
+ }
14977
+ } catch (_) {}
14978
+ const sel = window.getSelection();
14979
+ if (!sel || !sel.rangeCount) return;
14980
+ const rects = sel.getRangeAt(0).getClientRects();
14981
+ if (rects.length > 0) this._caretRect = rects[rects.length - 1];
14982
+ }
14983
+ _getQueryAtCursor() {
14984
+ const sel = window.getSelection();
14985
+ if (!sel || !sel.rangeCount) return null;
14986
+ const range = sel.getRangeAt(0);
14987
+ if (!range.collapsed) return null;
14988
+ const node = range.startContainer;
14989
+ if (node.nodeType !== Node.TEXT_NODE) return null;
14990
+ const text = node.textContent.slice(0, range.startOffset);
14991
+ const trigger = this._cfg.trigger;
14992
+ const triggerIdx = text.lastIndexOf(trigger);
14993
+ if (triggerIdx === -1) return null;
14994
+ const afterTrigger = text.slice(triggerIdx + trigger.length);
14995
+ if (!this._cfg.allowSpaces && /\s/.test(afterTrigger)) return null;
14996
+ if (afterTrigger.length < this._cfg.minChars) return null;
14997
+ this._triggerNode = node;
14998
+ this._triggerOffset = triggerIdx;
14999
+ return afterTrigger;
15000
+ }
15001
+ _onInput() {
15002
+ if (!this._cfg) return;
15003
+ const query = this._getQueryAtCursor();
15004
+ if (query === null) {
15005
+ this._hideDropdown();
15006
+ return;
15007
+ }
15008
+ this._captureCaretRect();
15009
+ this._query = query;
15010
+ clearTimeout(this._debounceTimer);
15011
+ this._debounceTimer = setTimeout(() => {
15012
+ this._cfg.onSearch(this._query, (items) => {
15013
+ if (!Array.isArray(items) || items.length === 0) {
15014
+ this._hideDropdown();
15015
+ return;
15016
+ }
15017
+ this._renderItems(items);
15018
+ this._showDropdown();
15019
+ });
15020
+ }, this._cfg.debounce);
15021
+ }
15022
+ _onKeydown(e) {
15023
+ if (!this._cfg || !this._open) return;
15024
+ if (e.key === "ArrowDown") {
15025
+ e.preventDefault();
15026
+ const next = (this._activeIndex + 1) % this._items.length;
15027
+ this._highlightItem(next);
15028
+ } else if (e.key === "ArrowUp") {
15029
+ e.preventDefault();
15030
+ const prev = (this._activeIndex - 1 + this._items.length) % this._items.length;
15031
+ this._highlightItem(prev);
15032
+ } else if (e.key === "Enter" || e.key === "Tab") {
15033
+ if (this._activeIndex >= 0) {
15034
+ e.preventDefault();
15035
+ this._select(this._activeIndex);
15036
+ }
15037
+ } else if (e.key === "Escape") this._hideDropdown();
15038
+ }
15039
+ _onDocClick(e) {
15040
+ if (!this._open) return;
15041
+ if (this._dropdown && this._dropdown.contains(e.target)) return;
15042
+ this._hideDropdown();
15043
+ }
15044
+ _select(index) {
15045
+ const item = this._items[index];
15046
+ if (!item) return;
15047
+ if (this._triggerNode) {
15048
+ const node = this._triggerNode;
15049
+ node.textContent = node.textContent.slice(0, this._triggerOffset) + node.textContent.slice(this._triggerOffset + this._cfg.trigger.length + this._query.length);
15050
+ const sel = window.getSelection();
15051
+ const range = document.createRange();
15052
+ range.setStart(node, this._triggerOffset);
15053
+ range.collapse(true);
15054
+ sel.removeAllRanges();
15055
+ sel.addRange(range);
15056
+ }
15057
+ let html;
15058
+ if (typeof this._cfg.onInsert === "function") html = this._cfg.onInsert(item);
15059
+ if (!html) html = `<span class="${this._cfg.mentionClass}" data-mention-id="${item.id}" contenteditable="false">@${item.label}</span>`;
15060
+ this.context.invoke("editor.insertHTML", html + "&#8203;");
15061
+ this._hideDropdown();
15062
+ this.context.triggerEvent("change", this.context.getHTML());
15063
+ }
15064
+ };
15065
+ //#endregion
11584
15066
  //#region src/js/Context.js
11585
15067
  /**
11586
15068
  * Context.js - Central hub for the editor instance
@@ -11597,6 +15079,8 @@
11597
15079
  constructor(targetEl, userOptions = {}) {
11598
15080
  this.targetEl = targetEl;
11599
15081
  this.options = mergeDeep(defaultOptions, userOptions);
15082
+ /** @type {import('./i18n/index.js').AsnLocale} */
15083
+ this.locale = resolveLocale(this.options.lang);
11600
15084
  /** @type {{ container: HTMLElement, editable: HTMLElement, toolbar?: HTMLElement, statusbar?: HTMLElement }} */
11601
15085
  this.layoutInfo = {};
11602
15086
  /** @type {Map<string, Function[]>} */
@@ -11657,6 +15141,10 @@
11657
15141
  register("shortcutsDialog", ShortcutsDialog);
11658
15142
  register("findReplace", FindReplace);
11659
15143
  register("imageCropOverlay", ImageCropOverlay);
15144
+ register("autoSaveRestore", AutoSaveRestore);
15145
+ register("markdownShortcuts", MarkdownShortcuts);
15146
+ register("bubbleToolbar", BubbleToolbar);
15147
+ register("mention", Mention);
11660
15148
  for (const [name, ModuleClass] of _customModules) register(name, ModuleClass);
11661
15149
  }
11662
15150
  /**
@@ -11688,7 +15176,9 @@
11688
15176
  if (this.options.autoSave && this.options.autoSaveKey) {
11689
15177
  const d4 = this.on("change", () => {
11690
15178
  try {
11691
- localStorage.setItem(this.options.autoSaveKey, this.getHTML());
15179
+ const key = this.options.autoSaveKey;
15180
+ localStorage.setItem(key, this.getHTML());
15181
+ localStorage.setItem(key + ":asrmeta", JSON.stringify({ savedAt: Date.now() }));
11692
15182
  } catch (_) {}
11693
15183
  });
11694
15184
  this._disposers.push(d4);
@@ -11988,7 +15478,7 @@
11988
15478
  registerModule(name, ModuleClass) {
11989
15479
  _customModules.set(name, ModuleClass);
11990
15480
  },
11991
- version: "1.0.4"
15481
+ version: "1.0.9"
11992
15482
  };
11993
15483
  /**
11994
15484
  * @param {string|Element|NodeList|Element[]} selector