Compare commits

..

416 Commits

Author SHA1 Message Date
Excalidraw Bot 198992f02b Auto commit: Calculate translation coverage 2026-03-11 21:18:02 +00:00
Excalidraw Bot a830820d45 New translations en.json (Karakalpak) 2026-03-11 22:16:57 +01:00
Excalidraw Bot 211c2c5b4c New translations en.json (Kabyle) 2026-03-11 22:16:55 +01:00
Excalidraw Bot ebcd8535ed New translations en.json (Bengali, India) 2026-03-11 22:16:54 +01:00
Excalidraw Bot 34fd9d60d7 New translations en.json (German, Switzerland) 2026-03-11 22:16:53 +01:00
Excalidraw Bot 60e831851b New translations en.json (Occitan) 2026-03-11 22:16:52 +01:00
Excalidraw Bot 40a4146b67 New translations en.json (Norwegian Bokmal) 2026-03-11 22:16:51 +01:00
Excalidraw Bot e466771826 New translations en.json (Uzbek) 2026-03-11 22:16:50 +01:00
Excalidraw Bot 06042fa7ee New translations en.json (Sinhala) 2026-03-11 22:16:48 +01:00
Excalidraw Bot abdf12bcd0 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-11 22:16:47 +01:00
Excalidraw Bot 57a310dfb5 New translations en.json (Burmese) 2026-03-11 22:16:46 +01:00
Excalidraw Bot 06fb8d3d7c New translations en.json (Hindi) 2026-03-11 22:16:45 +01:00
Excalidraw Bot f46dd72595 New translations en.json (Azerbaijani) 2026-03-11 22:16:44 +01:00
Excalidraw Bot da36bc650d New translations en.json (Latvian) 2026-03-11 22:16:43 +01:00
Excalidraw Bot 90d458c089 New translations en.json (Kazakh) 2026-03-11 22:16:41 +01:00
Excalidraw Bot 93cf69a588 New translations en.json (Norwegian Nynorsk) 2026-03-11 22:16:40 +01:00
Excalidraw Bot 7dc916e3dc New translations en.json (Thai) 2026-03-11 22:16:39 +01:00
Excalidraw Bot 460036e324 New translations en.json (Marathi) 2026-03-11 22:16:38 +01:00
Excalidraw Bot c4a527ee17 New translations en.json (Bengali) 2026-03-11 22:16:37 +01:00
Excalidraw Bot 45d11181c9 New translations en.json (Tamil) 2026-03-11 22:16:36 +01:00
Excalidraw Bot bc9fccd61b New translations en.json (Khmer) 2026-03-11 22:16:34 +01:00
Excalidraw Bot a83cfaae6f New translations en.json (Persian) 2026-03-11 22:16:33 +01:00
Excalidraw Bot 795274f9f0 New translations en.json (Indonesian) 2026-03-11 22:16:31 +01:00
Excalidraw Bot f51ac29fbe New translations en.json (Galician) 2026-03-11 22:16:30 +01:00
Excalidraw Bot e1698dd3aa New translations en.json (Vietnamese) 2026-03-11 22:16:29 +01:00
Excalidraw Bot a39587c845 New translations en.json (Chinese Simplified) 2026-03-11 22:16:27 +01:00
Excalidraw Bot 76f004bf99 New translations en.json (Swedish) 2026-03-11 22:16:26 +01:00
Excalidraw Bot 5c0f4fdd42 New translations en.json (Slovenian) 2026-03-11 22:16:25 +01:00
Excalidraw Bot 9d157d7009 New translations en.json (Slovak) 2026-03-11 22:16:24 +01:00
Excalidraw Bot 84e5373a9a New translations en.json (Portuguese) 2026-03-11 22:16:23 +01:00
Excalidraw Bot 107023393c New translations en.json (Polish) 2026-03-11 22:16:22 +01:00
Excalidraw Bot 19f2c741a9 New translations en.json (Punjabi) 2026-03-11 22:16:20 +01:00
Excalidraw Bot a3d000ee71 New translations en.json (Lithuanian) 2026-03-11 22:16:19 +01:00
Excalidraw Bot 6ff96ef739 New translations en.json (Kurdish) 2026-03-11 22:16:18 +01:00
Excalidraw Bot e34139986c New translations en.json (Korean) 2026-03-11 22:16:17 +01:00
Excalidraw Bot 30801b718a New translations en.json (Japanese) 2026-03-11 22:16:16 +01:00
Excalidraw Bot 606b05eb17 New translations en.json (Hungarian) 2026-03-11 22:16:15 +01:00
Excalidraw Bot 625fad6c1b New translations en.json (Hebrew) 2026-03-11 22:16:13 +01:00
Excalidraw Bot 9ebafb63a6 New translations en.json (Finnish) 2026-03-11 22:16:12 +01:00
Excalidraw Bot 4d087cbbd9 New translations en.json (Basque) 2026-03-11 22:16:11 +01:00
Excalidraw Bot 539f6807e3 New translations en.json (Greek) 2026-03-11 22:16:10 +01:00
Excalidraw Bot 20b2604c68 New translations en.json (German) 2026-03-11 22:16:09 +01:00
Excalidraw Bot 0781ac6ad9 New translations en.json (Danish) 2026-03-11 22:16:07 +01:00
Excalidraw Bot 22b8282812 New translations en.json (Czech) 2026-03-11 22:16:06 +01:00
Excalidraw Bot 50500f57f8 New translations en.json (Catalan) 2026-03-11 22:16:05 +01:00
Excalidraw Bot 9a0f8167eb New translations en.json (Bulgarian) 2026-03-11 22:16:04 +01:00
Excalidraw Bot 9564931b69 New translations en.json (Arabic) 2026-03-11 22:16:03 +01:00
Excalidraw Bot 83a3ed78b4 New translations en.json (Spanish) 2026-03-11 22:16:01 +01:00
Excalidraw Bot 8d240c6f66 New translations en.json (Romanian) 2026-03-11 22:16:00 +01:00
Excalidraw Bot 132a8d3cf8 New translations en.json (Turkish) 2026-03-11 22:15:59 +01:00
Excalidraw Bot b2998238f7 New translations en.json (French) 2026-03-11 22:15:58 +01:00
Excalidraw Bot a72f4e1689 New translations en.json (Portuguese, Brazilian) 2026-03-11 22:15:57 +01:00
Excalidraw Bot dc68825d2b New translations en.json (Chinese Traditional) 2026-03-11 22:15:55 +01:00
Excalidraw Bot c49cfc3bde New translations en.json (Ukrainian) 2026-03-11 22:15:54 +01:00
Excalidraw Bot df1566a6ee New translations en.json (Dutch) 2026-03-11 22:15:53 +01:00
Excalidraw Bot e89685b931 New translations en.json (Italian) 2026-03-11 22:15:51 +01:00
Excalidraw Bot b292d2ecbc New translations en.json (Russian) 2026-03-11 22:15:50 +01:00
Excalidraw Bot 4e8d015c6d Auto commit: Calculate translation coverage 2026-03-09 12:30:29 +00:00
Excalidraw Bot f520d6839c New translations en.json (Chinese Traditional) 2026-03-09 13:30:16 +01:00
Excalidraw Bot 1dddcf1633 Auto commit: Calculate translation coverage 2026-03-09 08:53:33 +00:00
Excalidraw Bot db7d73c65e New translations en.json (Romanian) 2026-03-09 09:53:22 +01:00
Excalidraw Bot 5371a13749 Auto commit: Calculate translation coverage 2026-03-08 22:39:08 +00:00
Excalidraw Bot 73ee1552b7 New translations en.json (Karakalpak) 2026-03-08 23:38:00 +01:00
Excalidraw Bot aaa71dad68 New translations en.json (Kabyle) 2026-03-08 23:37:59 +01:00
Excalidraw Bot 365320fdc6 New translations en.json (Bengali, India) 2026-03-08 23:37:57 +01:00
Excalidraw Bot ca05af6aee New translations en.json (German, Switzerland) 2026-03-08 23:37:57 +01:00
Excalidraw Bot 3de073dfa2 New translations en.json (Occitan) 2026-03-08 23:37:55 +01:00
Excalidraw Bot 1f73461abd New translations en.json (Norwegian Bokmal) 2026-03-08 23:37:55 +01:00
Excalidraw Bot b7bc95a216 New translations en.json (Uzbek) 2026-03-08 23:37:54 +01:00
Excalidraw Bot f11580cc71 New translations en.json (Sinhala) 2026-03-08 23:37:52 +01:00
Excalidraw Bot bb5c9439c7 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-08 23:37:51 +01:00
Excalidraw Bot cec234157b New translations en.json (Burmese) 2026-03-08 23:37:51 +01:00
Excalidraw Bot 69cd706970 New translations en.json (Hindi) 2026-03-08 23:37:49 +01:00
Excalidraw Bot fa9ca4598d New translations en.json (Azerbaijani) 2026-03-08 23:37:48 +01:00
Excalidraw Bot 9c88cc2305 New translations en.json (Latvian) 2026-03-08 23:37:47 +01:00
Excalidraw Bot 8817c4ed5e New translations en.json (Kazakh) 2026-03-08 23:37:46 +01:00
Excalidraw Bot f65bdb0a90 New translations en.json (Norwegian Nynorsk) 2026-03-08 23:37:45 +01:00
Excalidraw Bot 99ce84a9b8 New translations en.json (Thai) 2026-03-08 23:37:44 +01:00
Excalidraw Bot 5b15b45bec New translations en.json (Marathi) 2026-03-08 23:37:43 +01:00
Excalidraw Bot 0573bc959b New translations en.json (Bengali) 2026-03-08 23:37:42 +01:00
Excalidraw Bot b950747454 New translations en.json (Tamil) 2026-03-08 23:37:41 +01:00
Excalidraw Bot 60609026c9 New translations en.json (Khmer) 2026-03-08 23:37:40 +01:00
Excalidraw Bot fd5affb040 New translations en.json (Persian) 2026-03-08 23:37:39 +01:00
Excalidraw Bot 5f6dd3dc91 New translations en.json (Indonesian) 2026-03-08 23:37:38 +01:00
Excalidraw Bot 62a178758b New translations en.json (Galician) 2026-03-08 23:37:37 +01:00
Excalidraw Bot c9218fdb10 New translations en.json (Vietnamese) 2026-03-08 23:37:36 +01:00
Excalidraw Bot fd13af7747 New translations en.json (Chinese Simplified) 2026-03-08 23:37:35 +01:00
Excalidraw Bot c310a50fd5 New translations en.json (Swedish) 2026-03-08 23:37:34 +01:00
Excalidraw Bot aba5b98285 New translations en.json (Slovenian) 2026-03-08 23:37:33 +01:00
Excalidraw Bot 53acd4e31b New translations en.json (Slovak) 2026-03-08 23:37:32 +01:00
Excalidraw Bot 38837620e0 New translations en.json (Portuguese) 2026-03-08 23:37:31 +01:00
Excalidraw Bot ec1c4e8ce3 New translations en.json (Polish) 2026-03-08 23:37:30 +01:00
Excalidraw Bot 27a3d5a36d New translations en.json (Punjabi) 2026-03-08 23:37:29 +01:00
Excalidraw Bot 038a44b8ef New translations en.json (Lithuanian) 2026-03-08 23:37:28 +01:00
Excalidraw Bot 56bfebb15c New translations en.json (Kurdish) 2026-03-08 23:37:27 +01:00
Excalidraw Bot 9e14480293 New translations en.json (Korean) 2026-03-08 23:37:25 +01:00
Excalidraw Bot 1b24d6def7 New translations en.json (Japanese) 2026-03-08 23:37:24 +01:00
Excalidraw Bot 5cca65323f New translations en.json (Hungarian) 2026-03-08 23:37:23 +01:00
Excalidraw Bot 32d92bcef5 New translations en.json (Hebrew) 2026-03-08 23:37:22 +01:00
Excalidraw Bot c6c7040cac New translations en.json (Finnish) 2026-03-08 23:37:21 +01:00
Excalidraw Bot c6419e54db New translations en.json (Basque) 2026-03-08 23:37:20 +01:00
Excalidraw Bot 1ce70c7022 New translations en.json (Greek) 2026-03-08 23:37:19 +01:00
Excalidraw Bot af3d94064a New translations en.json (German) 2026-03-08 23:37:18 +01:00
Excalidraw Bot 0750f61536 New translations en.json (Danish) 2026-03-08 23:37:17 +01:00
Excalidraw Bot 979830edf6 New translations en.json (Czech) 2026-03-08 23:37:16 +01:00
Excalidraw Bot 7eea5d5ec4 New translations en.json (Catalan) 2026-03-08 23:37:15 +01:00
Excalidraw Bot eef72ca4fb New translations en.json (Bulgarian) 2026-03-08 23:37:14 +01:00
Excalidraw Bot d0e25ccec2 New translations en.json (Arabic) 2026-03-08 23:37:13 +01:00
Excalidraw Bot cb616e2957 New translations en.json (Spanish) 2026-03-08 23:37:12 +01:00
Excalidraw Bot 80d466cf7a New translations en.json (Romanian) 2026-03-08 23:37:11 +01:00
Excalidraw Bot d0383d0ce3 New translations en.json (Turkish) 2026-03-08 23:37:10 +01:00
Excalidraw Bot d75222110b New translations en.json (French) 2026-03-08 23:37:09 +01:00
Excalidraw Bot e27e4e0e2d New translations en.json (Portuguese, Brazilian) 2026-03-08 23:37:07 +01:00
Excalidraw Bot 6057a40665 New translations en.json (Chinese Traditional) 2026-03-08 23:37:06 +01:00
Excalidraw Bot e29c03554c New translations en.json (Ukrainian) 2026-03-08 23:37:05 +01:00
Excalidraw Bot 3643bf82d0 New translations en.json (Dutch) 2026-03-08 23:37:04 +01:00
Excalidraw Bot 50cfcc26cc New translations en.json (Italian) 2026-03-08 23:37:03 +01:00
Excalidraw Bot d5fb4305a3 New translations en.json (Russian) 2026-03-08 23:37:02 +01:00
Excalidraw Bot 970a1aa720 Auto commit: Calculate translation coverage 2026-03-07 20:55:25 +00:00
Excalidraw Bot e3d305ddc2 New translations en.json (Italian) 2026-03-07 21:55:15 +01:00
Excalidraw Bot 04389e8408 Auto commit: Calculate translation coverage 2026-03-06 08:08:28 +00:00
Excalidraw Bot 29dbc53a56 New translations en.json (Russian) 2026-03-06 09:08:16 +01:00
Excalidraw Bot 8acc174353 Auto commit: Calculate translation coverage 2026-03-06 06:31:04 +00:00
Excalidraw Bot 6985e75394 New translations en.json (French) 2026-03-06 07:30:52 +01:00
Excalidraw Bot 0b65b2cc63 Auto commit: Calculate translation coverage 2026-03-05 20:05:20 +00:00
Excalidraw Bot f6ea4bf135 New translations en.json (Romanian) 2026-03-05 21:05:08 +01:00
Excalidraw Bot a5b37fabea Auto commit: Calculate translation coverage 2026-03-05 18:14:47 +00:00
Excalidraw Bot 43476864ad New translations en.json (Karakalpak) 2026-03-05 19:00:54 +01:00
Excalidraw Bot e29436d8db New translations en.json (Kabyle) 2026-03-05 19:00:53 +01:00
Excalidraw Bot f0b6ace0fc New translations en.json (Bengali, India) 2026-03-05 19:00:51 +01:00
Excalidraw Bot d0a5264b1b New translations en.json (German, Switzerland) 2026-03-05 19:00:50 +01:00
Excalidraw Bot 841ef74430 New translations en.json (Occitan) 2026-03-05 19:00:49 +01:00
Excalidraw Bot 3f7d7f79de New translations en.json (Norwegian Bokmal) 2026-03-05 19:00:47 +01:00
Excalidraw Bot a30d454e5f New translations en.json (Uzbek) 2026-03-05 19:00:46 +01:00
Excalidraw Bot d3471352ed New translations en.json (Sinhala) 2026-03-05 19:00:44 +01:00
Excalidraw Bot de9c8d9fc3 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-05 19:00:42 +01:00
Excalidraw Bot 1ab1db08a8 New translations en.json (Burmese) 2026-03-05 19:00:41 +01:00
Excalidraw Bot 22a72427ec New translations en.json (Hindi) 2026-03-05 19:00:40 +01:00
Excalidraw Bot 74428d8091 New translations en.json (Azerbaijani) 2026-03-05 19:00:38 +01:00
Excalidraw Bot 657929ea6f New translations en.json (Latvian) 2026-03-05 19:00:36 +01:00
Excalidraw Bot 3a0e6aa70c New translations en.json (Kazakh) 2026-03-05 19:00:35 +01:00
Excalidraw Bot 743fd2d38a New translations en.json (Norwegian Nynorsk) 2026-03-05 19:00:33 +01:00
Excalidraw Bot 3b89c89a52 New translations en.json (Thai) 2026-03-05 19:00:32 +01:00
Excalidraw Bot f0a80e9f78 New translations en.json (Marathi) 2026-03-05 19:00:30 +01:00
Excalidraw Bot 5e3dd21a10 New translations en.json (Bengali) 2026-03-05 19:00:29 +01:00
Excalidraw Bot a96134fa81 New translations en.json (Tamil) 2026-03-05 19:00:28 +01:00
Excalidraw Bot 8dd6dd666c New translations en.json (Khmer) 2026-03-05 19:00:26 +01:00
Excalidraw Bot 3a1fc51e44 New translations en.json (Persian) 2026-03-05 19:00:24 +01:00
Excalidraw Bot d83742c5eb New translations en.json (Indonesian) 2026-03-05 19:00:23 +01:00
Excalidraw Bot 83da4bd876 New translations en.json (Galician) 2026-03-05 19:00:21 +01:00
Excalidraw Bot c08a3a4d65 New translations en.json (Vietnamese) 2026-03-05 19:00:20 +01:00
Excalidraw Bot 65e20609d7 New translations en.json (Chinese Simplified) 2026-03-05 19:00:18 +01:00
Excalidraw Bot 5d62d50e4f New translations en.json (Swedish) 2026-03-05 19:00:16 +01:00
Excalidraw Bot 218acae143 New translations en.json (Slovenian) 2026-03-05 19:00:15 +01:00
Excalidraw Bot 35e618cdb6 New translations en.json (Slovak) 2026-03-05 19:00:13 +01:00
Excalidraw Bot e1f14c081c New translations en.json (Portuguese) 2026-03-05 19:00:11 +01:00
Excalidraw Bot cd3165b180 New translations en.json (Polish) 2026-03-05 19:00:10 +01:00
Excalidraw Bot 94da04d9f9 New translations en.json (Punjabi) 2026-03-05 19:00:07 +01:00
Excalidraw Bot a112d5ea41 New translations en.json (Lithuanian) 2026-03-05 19:00:05 +01:00
Excalidraw Bot 77ba23407c New translations en.json (Kurdish) 2026-03-05 19:00:03 +01:00
Excalidraw Bot 4ea2007557 New translations en.json (Korean) 2026-03-05 19:00:02 +01:00
Excalidraw Bot 42d2692b2f New translations en.json (Japanese) 2026-03-05 19:00:00 +01:00
Excalidraw Bot f7c7632ca6 New translations en.json (Hungarian) 2026-03-05 18:59:58 +01:00
Excalidraw Bot 61737f9c1b New translations en.json (Hebrew) 2026-03-05 18:59:57 +01:00
Excalidraw Bot 24d176ba2c New translations en.json (Finnish) 2026-03-05 18:59:56 +01:00
Excalidraw Bot f363fc071b New translations en.json (Basque) 2026-03-05 18:59:55 +01:00
Excalidraw Bot 7221028010 New translations en.json (Greek) 2026-03-05 18:59:53 +01:00
Excalidraw Bot fc1762d0fd New translations en.json (German) 2026-03-05 18:59:52 +01:00
Excalidraw Bot 0581fc1eaf New translations en.json (Danish) 2026-03-05 18:59:51 +01:00
Excalidraw Bot 284ac616e1 New translations en.json (Czech) 2026-03-05 18:59:49 +01:00
Excalidraw Bot 631ff71965 New translations en.json (Catalan) 2026-03-05 18:59:48 +01:00
Excalidraw Bot fb95a77055 New translations en.json (Bulgarian) 2026-03-05 18:59:47 +01:00
Excalidraw Bot 0d57e9f628 New translations en.json (Arabic) 2026-03-05 18:59:45 +01:00
Excalidraw Bot 7a24f13e81 New translations en.json (Spanish) 2026-03-05 18:59:43 +01:00
Excalidraw Bot 8f6e1d942d New translations en.json (Romanian) 2026-03-05 18:59:41 +01:00
Excalidraw Bot b21c6abbe4 New translations en.json (Turkish) 2026-03-05 18:59:40 +01:00
Excalidraw Bot 8757a9ddc4 New translations en.json (French) 2026-03-05 18:59:39 +01:00
Excalidraw Bot f7b3497dee New translations en.json (Portuguese, Brazilian) 2026-03-05 18:59:37 +01:00
Excalidraw Bot 0e17d69535 New translations en.json (Chinese Traditional) 2026-03-05 18:59:36 +01:00
Excalidraw Bot ff6ec195bd New translations en.json (Ukrainian) 2026-03-05 18:59:34 +01:00
Excalidraw Bot dd8637e125 New translations en.json (Dutch) 2026-03-05 18:59:33 +01:00
Excalidraw Bot 6d9257310c New translations en.json (Italian) 2026-03-05 18:59:31 +01:00
Excalidraw Bot 3d3f96880d New translations en.json (Russian) 2026-03-05 18:59:30 +01:00
dwelle 715399b558 Merge branch 'master' into l10n_master 2026-03-05 10:09:15 +01:00
Excalidraw Bot b818b61d8b Auto commit: Calculate translation coverage 2026-03-04 14:42:54 +00:00
Excalidraw Bot 99757b1a06 New translations en.json (Romanian) 2026-03-04 15:42:41 +01:00
Excalidraw Bot fe547a6ada New translations en.json (Russian) 2026-03-04 15:42:38 +01:00
Excalidraw Bot b6cb835cd4 Auto commit: Calculate translation coverage 2026-03-03 23:22:02 +00:00
Excalidraw Bot 917079900a New translations en.json (Karakalpak) 2026-03-04 00:20:53 +01:00
Excalidraw Bot a3304507a1 New translations en.json (Kabyle) 2026-03-04 00:20:51 +01:00
Excalidraw Bot 453d934cd9 New translations en.json (Bengali, India) 2026-03-04 00:20:50 +01:00
Excalidraw Bot cf73a91567 New translations en.json (German, Switzerland) 2026-03-04 00:20:49 +01:00
Excalidraw Bot 368ea9dd7f New translations en.json (Occitan) 2026-03-04 00:20:48 +01:00
Excalidraw Bot 0bdca3493b New translations en.json (Norwegian Bokmal) 2026-03-04 00:20:47 +01:00
Excalidraw Bot f69095d2bd New translations en.json (Uzbek) 2026-03-04 00:20:46 +01:00
Excalidraw Bot f5df6b9e4c New translations en.json (Sinhala) 2026-03-04 00:20:45 +01:00
Excalidraw Bot 0d285b3eec New translations en.json (Chinese Traditional, Hong Kong) 2026-03-04 00:20:44 +01:00
Excalidraw Bot be98b5c7cf New translations en.json (Burmese) 2026-03-04 00:20:43 +01:00
Excalidraw Bot 013cc37a47 New translations en.json (Hindi) 2026-03-04 00:20:42 +01:00
Excalidraw Bot 82f3fa6c57 New translations en.json (Azerbaijani) 2026-03-04 00:20:41 +01:00
Excalidraw Bot 669f8a2b59 New translations en.json (Latvian) 2026-03-04 00:20:40 +01:00
Excalidraw Bot 0518fa5beb New translations en.json (Kazakh) 2026-03-04 00:20:38 +01:00
Excalidraw Bot e48b742ee1 New translations en.json (Norwegian Nynorsk) 2026-03-04 00:20:37 +01:00
Excalidraw Bot d4c45dc9be New translations en.json (Thai) 2026-03-04 00:20:36 +01:00
Excalidraw Bot d728887146 New translations en.json (Marathi) 2026-03-04 00:20:35 +01:00
Excalidraw Bot 240e1b0492 New translations en.json (Bengali) 2026-03-04 00:20:34 +01:00
Excalidraw Bot 4cb28e838f New translations en.json (Tamil) 2026-03-04 00:20:33 +01:00
Excalidraw Bot 5823570530 New translations en.json (Khmer) 2026-03-04 00:20:32 +01:00
Excalidraw Bot b832b44413 New translations en.json (Persian) 2026-03-04 00:20:31 +01:00
Excalidraw Bot 5c7123e77b New translations en.json (Indonesian) 2026-03-04 00:20:30 +01:00
Excalidraw Bot 36b220a067 New translations en.json (Galician) 2026-03-04 00:20:29 +01:00
Excalidraw Bot d03c2eb963 New translations en.json (Vietnamese) 2026-03-04 00:20:27 +01:00
Excalidraw Bot e01199e6b8 New translations en.json (Chinese Simplified) 2026-03-04 00:20:26 +01:00
Excalidraw Bot 9c516f1e0a New translations en.json (Swedish) 2026-03-04 00:20:25 +01:00
Excalidraw Bot 6566456832 New translations en.json (Slovenian) 2026-03-04 00:20:24 +01:00
Excalidraw Bot 60936c48ca New translations en.json (Slovak) 2026-03-04 00:20:23 +01:00
Excalidraw Bot 43be14dc93 New translations en.json (Portuguese) 2026-03-04 00:20:22 +01:00
Excalidraw Bot 526d2c52f3 New translations en.json (Polish) 2026-03-04 00:20:21 +01:00
Excalidraw Bot fcb69c36e6 New translations en.json (Punjabi) 2026-03-04 00:20:20 +01:00
Excalidraw Bot 53a4d76e3b New translations en.json (Lithuanian) 2026-03-04 00:20:19 +01:00
Excalidraw Bot 0710d40919 New translations en.json (Kurdish) 2026-03-04 00:20:18 +01:00
Excalidraw Bot a07cd1b0e8 New translations en.json (Korean) 2026-03-04 00:20:16 +01:00
Excalidraw Bot 4a55ab6bab New translations en.json (Japanese) 2026-03-04 00:20:15 +01:00
Excalidraw Bot 6257bdbfb6 New translations en.json (Hungarian) 2026-03-04 00:20:14 +01:00
Excalidraw Bot 3f377c9d69 New translations en.json (Hebrew) 2026-03-04 00:20:13 +01:00
Excalidraw Bot 176e11bac8 New translations en.json (Finnish) 2026-03-04 00:20:12 +01:00
Excalidraw Bot 84c89018d9 New translations en.json (Basque) 2026-03-04 00:20:11 +01:00
Excalidraw Bot 6cac1c8a04 New translations en.json (Greek) 2026-03-04 00:20:10 +01:00
Excalidraw Bot 1e996e4d94 New translations en.json (German) 2026-03-04 00:20:09 +01:00
Excalidraw Bot d028157a5c New translations en.json (Danish) 2026-03-04 00:20:08 +01:00
Excalidraw Bot 2934b8c308 New translations en.json (Czech) 2026-03-04 00:20:07 +01:00
Excalidraw Bot c6f08724eb New translations en.json (Catalan) 2026-03-04 00:20:06 +01:00
Excalidraw Bot 976da91fe7 New translations en.json (Bulgarian) 2026-03-04 00:20:05 +01:00
Excalidraw Bot 0b42b49d9e New translations en.json (Arabic) 2026-03-04 00:20:04 +01:00
Excalidraw Bot 3a472aa6fc New translations en.json (Spanish) 2026-03-04 00:20:02 +01:00
Excalidraw Bot 6a716adea6 New translations en.json (Romanian) 2026-03-04 00:20:01 +01:00
Excalidraw Bot abb33bf686 New translations en.json (Turkish) 2026-03-04 00:20:00 +01:00
Excalidraw Bot 0463c18f02 New translations en.json (French) 2026-03-04 00:19:59 +01:00
Excalidraw Bot fd83e44c6f New translations en.json (Portuguese, Brazilian) 2026-03-04 00:19:58 +01:00
Excalidraw Bot 2d0d6d6c1b New translations en.json (Chinese Traditional) 2026-03-04 00:19:57 +01:00
Excalidraw Bot ab39228fc9 New translations en.json (Ukrainian) 2026-03-04 00:19:56 +01:00
Excalidraw Bot 6e9085eee7 New translations en.json (Dutch) 2026-03-04 00:19:55 +01:00
Excalidraw Bot eac4fa5b57 New translations en.json (Italian) 2026-03-04 00:19:54 +01:00
Excalidraw Bot ef40b72eb0 New translations en.json (Russian) 2026-03-04 00:19:53 +01:00
Excalidraw Bot e1f5b9f138 Auto commit: Calculate translation coverage 2026-03-02 08:15:54 +00:00
Excalidraw Bot 8bf7fae439 New translations en.json (French) 2026-03-02 09:15:44 +01:00
Excalidraw Bot 503fd4c598 Auto commit: Calculate translation coverage 2026-03-01 14:08:06 +00:00
Excalidraw Bot e99df11729 New translations en.json (Italian) 2026-03-01 15:07:54 +01:00
Excalidraw Bot 39b9224c3c Auto commit: Calculate translation coverage 2026-02-28 08:29:43 +00:00
Excalidraw Bot 38cf2fb51e New translations en.json (Russian) 2026-02-28 09:29:30 +01:00
Excalidraw Bot 333a2ee6fc Auto commit: Calculate translation coverage 2026-02-27 21:22:10 +00:00
Excalidraw Bot 728bb66eb7 New translations en.json (Thai) 2026-02-27 22:21:59 +01:00
Excalidraw Bot 49ea45aa5d Auto commit: Calculate translation coverage 2026-02-27 19:24:41 +00:00
Excalidraw Bot cf5418b128 New translations en.json (Romanian) 2026-02-27 20:24:29 +01:00
Excalidraw Bot 9e0eb0f541 New translations en.json (Chinese Traditional) 2026-02-27 20:24:28 +01:00
Excalidraw Bot d506a822ff Auto commit: Calculate translation coverage 2026-02-27 17:26:28 +00:00
Excalidraw Bot 0fb50e07c0 New translations en.json (Chinese Traditional) 2026-02-27 18:26:16 +01:00
Excalidraw Bot c2edd7c6f4 New translations en.json (Italian) 2026-02-26 22:20:43 +01:00
Excalidraw Bot bf7aafb77d Auto commit: Calculate translation coverage 2026-02-26 15:27:55 +00:00
Excalidraw Bot a3b9763dcd New translations en.json (Karakalpak) 2026-02-26 16:26:58 +01:00
Excalidraw Bot baf93453c3 New translations en.json (Kabyle) 2026-02-26 16:26:57 +01:00
Excalidraw Bot d9b9dc783e New translations en.json (Bengali, India) 2026-02-26 16:26:55 +01:00
Excalidraw Bot dd4343d48c New translations en.json (German, Switzerland) 2026-02-26 16:26:54 +01:00
Excalidraw Bot fdd021574c New translations en.json (Occitan) 2026-02-26 16:26:53 +01:00
Excalidraw Bot 652b0f60f5 New translations en.json (Norwegian Bokmal) 2026-02-26 16:26:51 +01:00
Excalidraw Bot 902c8eaf56 New translations en.json (Uzbek) 2026-02-26 16:26:50 +01:00
Excalidraw Bot 7e9a7cbd26 New translations en.json (Sinhala) 2026-02-26 16:26:48 +01:00
Excalidraw Bot 9d04681dc5 New translations en.json (Chinese Traditional, Hong Kong) 2026-02-26 16:26:47 +01:00
Excalidraw Bot b0bfc7af20 New translations en.json (Burmese) 2026-02-26 16:26:45 +01:00
Excalidraw Bot ba2828914d New translations en.json (Hindi) 2026-02-26 16:26:44 +01:00
Excalidraw Bot 791b63148b New translations en.json (Azerbaijani) 2026-02-26 16:26:42 +01:00
Excalidraw Bot 9ff150bdc2 New translations en.json (Latvian) 2026-02-26 16:26:41 +01:00
Excalidraw Bot 527b2658c1 New translations en.json (Kazakh) 2026-02-26 16:26:39 +01:00
Excalidraw Bot 7b8983c75a New translations en.json (Norwegian Nynorsk) 2026-02-26 16:26:38 +01:00
Excalidraw Bot c4067bfe2f New translations en.json (Thai) 2026-02-26 16:26:36 +01:00
Excalidraw Bot 46abd7bc96 New translations en.json (Marathi) 2026-02-26 16:26:34 +01:00
Excalidraw Bot 3ba91352ed New translations en.json (Bengali) 2026-02-26 16:26:33 +01:00
Excalidraw Bot 0e57d55451 New translations en.json (Tamil) 2026-02-26 16:26:32 +01:00
Excalidraw Bot d4e6e3cae3 New translations en.json (Khmer) 2026-02-26 16:26:30 +01:00
Excalidraw Bot c6395ae166 New translations en.json (Persian) 2026-02-26 16:26:29 +01:00
Excalidraw Bot 2d1aeee971 New translations en.json (Indonesian) 2026-02-26 16:26:28 +01:00
Excalidraw Bot c9455a0de4 New translations en.json (Galician) 2026-02-26 16:26:26 +01:00
Excalidraw Bot 9f944db928 New translations en.json (Vietnamese) 2026-02-26 16:26:25 +01:00
Excalidraw Bot 170bf35513 New translations en.json (Chinese Simplified) 2026-02-26 16:26:24 +01:00
Excalidraw Bot e6e8349a45 New translations en.json (Swedish) 2026-02-26 16:26:22 +01:00
Excalidraw Bot d632694c9b New translations en.json (Slovenian) 2026-02-26 16:26:21 +01:00
Excalidraw Bot cff4ca670d New translations en.json (Slovak) 2026-02-26 16:26:20 +01:00
Excalidraw Bot eaabc26428 New translations en.json (Portuguese) 2026-02-26 16:26:18 +01:00
Excalidraw Bot 0bcc2360a9 New translations en.json (Polish) 2026-02-26 16:26:17 +01:00
Excalidraw Bot f3da2a95ed New translations en.json (Punjabi) 2026-02-26 16:26:15 +01:00
Excalidraw Bot bec4653dcb New translations en.json (Lithuanian) 2026-02-26 16:26:14 +01:00
Excalidraw Bot a2e948d080 New translations en.json (Kurdish) 2026-02-26 16:26:12 +01:00
Excalidraw Bot 6b7da271b5 New translations en.json (Korean) 2026-02-26 16:26:11 +01:00
Excalidraw Bot 65c7c1815f New translations en.json (Japanese) 2026-02-26 16:26:10 +01:00
Excalidraw Bot 8c019fe3fd New translations en.json (Hungarian) 2026-02-26 16:26:08 +01:00
Excalidraw Bot 64466783de New translations en.json (Hebrew) 2026-02-26 16:26:07 +01:00
Excalidraw Bot ce3774e8f2 New translations en.json (Finnish) 2026-02-26 16:26:05 +01:00
Excalidraw Bot 5dd19a04c2 New translations en.json (Basque) 2026-02-26 16:26:04 +01:00
Excalidraw Bot 44769fe876 New translations en.json (Greek) 2026-02-26 16:26:02 +01:00
Excalidraw Bot 4947ec612c New translations en.json (German) 2026-02-26 16:26:01 +01:00
Excalidraw Bot 24b78a1332 New translations en.json (Danish) 2026-02-26 16:25:59 +01:00
Excalidraw Bot ee4f0a43fc New translations en.json (Czech) 2026-02-26 16:25:58 +01:00
Excalidraw Bot cfd9306c57 New translations en.json (Catalan) 2026-02-26 16:25:57 +01:00
Excalidraw Bot 8a3048d4a7 New translations en.json (Bulgarian) 2026-02-26 16:25:55 +01:00
Excalidraw Bot d7138d86ea New translations en.json (Arabic) 2026-02-26 16:25:54 +01:00
Excalidraw Bot b0644a6d3f New translations en.json (Spanish) 2026-02-26 16:25:52 +01:00
Excalidraw Bot 430adfe6f4 New translations en.json (Romanian) 2026-02-26 16:25:51 +01:00
Excalidraw Bot ceb6b47c17 New translations en.json (Turkish) 2026-02-26 16:25:49 +01:00
Excalidraw Bot 54973ba281 New translations en.json (French) 2026-02-26 16:25:48 +01:00
Excalidraw Bot 157b1911e1 New translations en.json (Portuguese, Brazilian) 2026-02-26 16:25:47 +01:00
Excalidraw Bot 482beddd67 New translations en.json (Chinese Traditional) 2026-02-26 16:25:46 +01:00
Excalidraw Bot 904828940d New translations en.json (Ukrainian) 2026-02-26 16:25:44 +01:00
Excalidraw Bot d0990c63db New translations en.json (Dutch) 2026-02-26 16:25:43 +01:00
Excalidraw Bot c86d22be7f New translations en.json (Italian) 2026-02-26 16:25:41 +01:00
Excalidraw Bot 7c03c458a1 New translations en.json (Russian) 2026-02-26 16:25:40 +01:00
Excalidraw Bot 8029e68e17 Auto commit: Calculate translation coverage 2026-02-26 11:56:56 +00:00
Excalidraw Bot c3e9430577 New translations en.json (Turkish) 2026-02-26 12:56:44 +01:00
Excalidraw Bot 9d34d4fcd4 New translations en.json (French) 2026-02-23 13:32:26 +01:00
Excalidraw Bot fe310acebd Auto commit: Calculate translation coverage 2026-02-22 23:34:27 +00:00
Excalidraw Bot d7c7236ee8 New translations en.json (Dutch) 2026-02-23 00:34:17 +01:00
Excalidraw Bot eefc3b9408 Auto commit: Calculate translation coverage 2026-02-21 05:58:59 +00:00
Excalidraw Bot cc67f9d544 New translations en.json (Chinese Traditional) 2026-02-21 06:58:50 +01:00
Excalidraw Bot e2e13bba0b Auto commit: Calculate translation coverage 2026-02-19 13:58:10 +00:00
Excalidraw Bot 041f012f72 New translations en.json (Chinese Traditional) 2026-02-19 14:57:56 +01:00
Excalidraw Bot dbd8cc8d6f Auto commit: Calculate translation coverage 2026-02-18 13:40:53 +00:00
Excalidraw Bot 9fbac79d11 New translations en.json (Chinese Traditional) 2026-02-18 14:40:42 +01:00
Excalidraw Bot ef915b7427 Auto commit: Calculate translation coverage 2026-02-15 09:04:40 +00:00
Excalidraw Bot c5b913ad9f New translations en.json (Italian) 2026-02-15 10:04:24 +01:00
Excalidraw Bot f66c943720 Auto commit: Calculate translation coverage 2026-02-14 08:25:14 +00:00
Excalidraw Bot 9c0edf1cb9 New translations en.json (Russian) 2026-02-14 09:25:02 +01:00
Excalidraw Bot 9112c61edb New translations en.json (Russian) 2026-02-14 07:54:58 +01:00
Excalidraw Bot f58a84a857 Auto commit: Calculate translation coverage 2026-02-13 19:42:14 +00:00
Excalidraw Bot 626e846d10 New translations en.json (Ukrainian) 2026-02-13 20:41:59 +01:00
Excalidraw Bot feacdcd156 Auto commit: Calculate translation coverage 2026-02-13 18:19:44 +00:00
Excalidraw Bot e1fbf09310 New translations en.json (Ukrainian) 2026-02-13 19:19:30 +01:00
Excalidraw Bot dc521a62c1 Auto commit: Calculate translation coverage 2026-02-13 01:22:27 +00:00
Excalidraw Bot f0bbb7614e New translations en.json (Portuguese, Brazilian) 2026-02-13 02:22:11 +01:00
Excalidraw Bot 62b35ab86c Auto commit: Calculate translation coverage 2026-02-09 06:58:01 +00:00
Excalidraw Bot 2ea8c308e4 New translations en.json (Romanian) 2026-02-09 07:57:50 +01:00
Excalidraw Bot 0a13e3a344 Auto commit: Calculate translation coverage 2026-02-08 23:25:09 +00:00
Excalidraw Bot 540809f2a4 New translations en.json (Karakalpak) 2026-02-09 00:23:37 +01:00
Excalidraw Bot 467d9bd08c New translations en.json (Kabyle) 2026-02-09 00:23:35 +01:00
Excalidraw Bot 13e8374bea New translations en.json (Bengali, India) 2026-02-09 00:23:34 +01:00
Excalidraw Bot b96dd6d332 New translations en.json (German, Switzerland) 2026-02-09 00:23:33 +01:00
Excalidraw Bot 32d77172cc New translations en.json (Occitan) 2026-02-09 00:23:32 +01:00
Excalidraw Bot 9e23421d53 New translations en.json (Norwegian Bokmal) 2026-02-09 00:23:31 +01:00
Excalidraw Bot c3d91aac44 New translations en.json (Uzbek) 2026-02-09 00:23:30 +01:00
Excalidraw Bot 99088550be New translations en.json (Sinhala) 2026-02-09 00:23:28 +01:00
Excalidraw Bot b9a171a2ef New translations en.json (Chinese Traditional, Hong Kong) 2026-02-09 00:23:27 +01:00
Excalidraw Bot 521d727b5b New translations en.json (Burmese) 2026-02-09 00:23:26 +01:00
Excalidraw Bot b7d610fbbe New translations en.json (Hindi) 2026-02-09 00:23:25 +01:00
Excalidraw Bot 1f33be6403 New translations en.json (Azerbaijani) 2026-02-09 00:23:23 +01:00
Excalidraw Bot 7ca28ae504 New translations en.json (Latvian) 2026-02-09 00:23:22 +01:00
Excalidraw Bot d648d1147e New translations en.json (Kazakh) 2026-02-09 00:23:21 +01:00
Excalidraw Bot 869739b170 New translations en.json (Norwegian Nynorsk) 2026-02-09 00:23:20 +01:00
Excalidraw Bot ca09cc2830 New translations en.json (Thai) 2026-02-09 00:23:19 +01:00
Excalidraw Bot 5d1e8448bc New translations en.json (Marathi) 2026-02-09 00:23:18 +01:00
Excalidraw Bot 2076b3643a New translations en.json (Bengali) 2026-02-09 00:23:17 +01:00
Excalidraw Bot cbe5581782 New translations en.json (Tamil) 2026-02-09 00:23:15 +01:00
Excalidraw Bot be9fcec967 New translations en.json (Khmer) 2026-02-09 00:23:14 +01:00
Excalidraw Bot 41f8203889 New translations en.json (Persian) 2026-02-09 00:23:13 +01:00
Excalidraw Bot 60be58033f New translations en.json (Indonesian) 2026-02-09 00:23:12 +01:00
Excalidraw Bot 7010f1af88 New translations en.json (Portuguese, Brazilian) 2026-02-09 00:23:10 +01:00
Excalidraw Bot edd1510183 New translations en.json (Galician) 2026-02-09 00:23:09 +01:00
Excalidraw Bot 5926023ab1 New translations en.json (Chinese Traditional) 2026-02-09 00:23:08 +01:00
Excalidraw Bot 7adc3d163f New translations en.json (Chinese Simplified) 2026-02-09 00:23:07 +01:00
Excalidraw Bot 124ecebca6 New translations en.json (Ukrainian) 2026-02-09 00:23:05 +01:00
Excalidraw Bot 8d8fdc2985 New translations en.json (Turkish) 2026-02-09 00:23:04 +01:00
Excalidraw Bot 0691617028 New translations en.json (Swedish) 2026-02-09 00:23:03 +01:00
Excalidraw Bot 3f181f7fa7 New translations en.json (Slovenian) 2026-02-09 00:23:02 +01:00
Excalidraw Bot 00f2600cbb New translations en.json (Slovak) 2026-02-09 00:23:01 +01:00
Excalidraw Bot 4274935c62 New translations en.json (Portuguese) 2026-02-09 00:23:00 +01:00
Excalidraw Bot 6209445644 New translations en.json (Polish) 2026-02-09 00:22:59 +01:00
Excalidraw Bot d9636c4101 New translations en.json (Punjabi) 2026-02-09 00:22:58 +01:00
Excalidraw Bot fc1c3a3985 New translations en.json (Dutch) 2026-02-09 00:22:56 +01:00
Excalidraw Bot 2c7737ed9b New translations en.json (Lithuanian) 2026-02-09 00:22:55 +01:00
Excalidraw Bot c4dcd3a5d2 New translations en.json (Kurdish) 2026-02-09 00:22:54 +01:00
Excalidraw Bot f0a37029bc New translations en.json (Korean) 2026-02-09 00:22:53 +01:00
Excalidraw Bot 2567526103 New translations en.json (Japanese) 2026-02-09 00:22:52 +01:00
Excalidraw Bot a10f7c10ae New translations en.json (Italian) 2026-02-09 00:22:51 +01:00
Excalidraw Bot 60dc788254 New translations en.json (Hungarian) 2026-02-09 00:22:49 +01:00
Excalidraw Bot 34ab5746b4 New translations en.json (Hebrew) 2026-02-09 00:22:48 +01:00
Excalidraw Bot 6c7b8f4bf4 New translations en.json (Finnish) 2026-02-09 00:22:47 +01:00
Excalidraw Bot 7a79d0306c New translations en.json (Basque) 2026-02-09 00:22:46 +01:00
Excalidraw Bot d70108dcfa New translations en.json (Greek) 2026-02-09 00:22:45 +01:00
Excalidraw Bot c2fc95116a New translations en.json (German) 2026-02-09 00:22:44 +01:00
Excalidraw Bot 947a11bac2 New translations en.json (Danish) 2026-02-09 00:22:42 +01:00
Excalidraw Bot 87336c115e New translations en.json (Czech) 2026-02-09 00:22:41 +01:00
Excalidraw Bot 6041c34aeb New translations en.json (Catalan) 2026-02-09 00:22:40 +01:00
Excalidraw Bot 184d9ca6e8 New translations en.json (Bulgarian) 2026-02-09 00:22:39 +01:00
Excalidraw Bot 3a844186a7 New translations en.json (Arabic) 2026-02-09 00:22:38 +01:00
Excalidraw Bot cc262ce5d5 New translations en.json (Spanish) 2026-02-09 00:22:37 +01:00
Excalidraw Bot 802b01562c New translations en.json (French) 2026-02-09 00:22:36 +01:00
Excalidraw Bot 6f2b43ff0c New translations en.json (Romanian) 2026-02-09 00:22:35 +01:00
Excalidraw Bot 461f327887 New translations en.json (Russian) 2026-02-09 00:22:33 +01:00
Excalidraw Bot a33a643a2f New translations en.json (Vietnamese) 2026-02-09 00:22:32 +01:00
Excalidraw Bot 91f7b38a39 Auto commit: Calculate translation coverage 2026-02-08 21:46:20 +00:00
Excalidraw Bot 998a296ca2 New translations en.json (Persian) 2026-02-08 22:45:54 +01:00
Excalidraw Bot 0afec1bf5c Auto commit: Calculate translation coverage 2026-02-08 16:00:00 +00:00
Excalidraw Bot 2436e8498a New translations en.json (Japanese) 2026-02-08 16:59:48 +01:00
Excalidraw Bot 521362a532 Auto commit: Calculate translation coverage 2026-02-08 15:03:07 +00:00
Excalidraw Bot ae5a2dee54 New translations en.json (Japanese) 2026-02-08 16:02:55 +01:00
Excalidraw Bot bf62bce4cb Auto commit: Calculate translation coverage 2026-02-08 13:52:29 +00:00
Excalidraw Bot 92c5acb960 New translations en.json (Japanese) 2026-02-08 14:52:19 +01:00
Excalidraw Bot d4ce7b067a New translations en.json (Japanese) 2026-02-08 13:56:44 +01:00
Excalidraw Bot 214e68ce03 Auto commit: Calculate translation coverage 2026-02-08 10:53:56 +00:00
Excalidraw Bot 19cb9ab30e New translations en.json (Italian) 2026-02-08 11:53:43 +01:00
Excalidraw Bot 3d097669f8 Auto commit: Calculate translation coverage 2026-02-05 18:24:29 +00:00
Excalidraw Bot ede77bd6ac New translations en.json (Chinese Simplified) 2026-02-05 19:24:17 +01:00
Excalidraw Bot 05dde8f0d1 Auto commit: Calculate translation coverage 2026-02-04 07:52:55 +00:00
Excalidraw Bot d11453ae15 New translations en.json (French) 2026-02-04 08:52:41 +01:00
Excalidraw Bot 2d3577bb43 Auto commit: Calculate translation coverage 2026-02-03 06:40:22 +00:00
Excalidraw Bot 4258aeac53 New translations en.json (Japanese) 2026-02-03 07:40:12 +01:00
Excalidraw Bot 4f772d77a4 New translations en.json (Japanese) 2026-02-03 06:35:52 +01:00
Excalidraw Bot 6f57935419 Auto commit: Calculate translation coverage 2026-02-02 12:15:38 +00:00
Excalidraw Bot 61c69915da New translations en.json (Japanese) 2026-02-02 13:15:26 +01:00
130 changed files with 2430 additions and 4428 deletions
+1 -22
View File
@@ -39,26 +39,5 @@
"allowReferrer": true "allowReferrer": true
} }
] ]
}, }
"overrides": [
{
"files": ["packages/excalidraw/**/*.{ts,tsx}"],
"excludedFiles": ["packages/excalidraw/**/*.test.{ts,tsx}", "packages/excalidraw/**/*.test.*.{ts,tsx}"],
"rules": {
"@typescript-eslint/no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["@excalidraw/excalidraw"],
"message": "Do not import from the barrel 'index.tsx' files. Use direct relative imports to the specific module instead.",
"allowTypeImports": true
}
],
"paths": [".", "..", "../..", "../../..", "../../../..", "../../../../..", "../index", "../../index", "../../../index", "../../../../index"]
}
]
}
}
]
} }
+5 -5
View File
@@ -3,14 +3,14 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@excalidraw/excalidraw": "*",
"browser-fs-access": "0.38.0",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0" "react-dom": "19.0.0",
"@excalidraw/excalidraw": "*",
"browser-fs-access": "0.29.1"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5", "vite": "5.0.12",
"vite": "5.0.12" "typescript": "^5"
}, },
"scripts": { "scripts": {
"start": "vite", "start": "vite",
+36
View File
@@ -4,6 +4,8 @@ import { unstable_batchedUpdates } from "react-dom";
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">; type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
const INPUT_CHANGE_INTERVAL_MS = 500;
export type ResolvablePromise<T> = Promise<T> & { export type ResolvablePromise<T> = Promise<T> & {
resolve: [T] extends [undefined] ? (value?: T) => void : (value: T) => void; resolve: [T] extends [undefined] ? (value?: T) => void : (value: T) => void;
reject: (error: Error) => void; reject: (error: Error) => void;
@@ -52,6 +54,40 @@ export const fileOpen = <M extends boolean | undefined = false>(opts: {
extensions, extensions,
mimeTypes, mimeTypes,
multiple: opts.multiple ?? false, multiple: opts.multiple ?? false,
legacySetup: (resolve, reject, input) => {
const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
const focusHandler = () => {
checkForFile();
document.addEventListener("keyup", scheduleRejection);
document.addEventListener("pointerup", scheduleRejection);
scheduleRejection();
};
const checkForFile = () => {
// this hack might not work when expecting multiple files
if (input.files?.length) {
const ret = opts.multiple ? [...input.files] : input.files[0];
resolve(ret as RetType);
}
};
requestAnimationFrame(() => {
window.addEventListener("focus", focusHandler);
});
const interval = window.setInterval(() => {
checkForFile();
}, INPUT_CHANGE_INTERVAL_MS);
return (rejectPromise) => {
clearInterval(interval);
scheduleRejection.cancel();
window.removeEventListener("focus", focusHandler);
document.removeEventListener("keyup", scheduleRejection);
document.removeEventListener("pointerup", scheduleRejection);
if (rejectPromise) {
// so that something is shown in console if we need to debug this
console.warn("Opening the file was canceled (legacy-fs).");
rejectPromise(new Error("Request Aborted"));
}
};
},
}) as Promise<RetType>; }) as Promise<RetType>;
}; };
+20 -92
View File
@@ -5,8 +5,6 @@ import {
CaptureUpdateAction, CaptureUpdateAction,
reconcileElements, reconcileElements,
useEditorInterface, useEditorInterface,
ExcalidrawAPIProvider,
useExcalidrawAPI,
} from "@excalidraw/excalidraw"; } from "@excalidraw/excalidraw";
import { trackEvent } from "@excalidraw/excalidraw/analytics"; import { trackEvent } from "@excalidraw/excalidraw/analytics";
import { getDefaultAppState } from "@excalidraw/excalidraw/appState"; import { getDefaultAppState } from "@excalidraw/excalidraw/appState";
@@ -36,6 +34,7 @@ import {
import polyfill from "@excalidraw/excalidraw/polyfill"; import polyfill from "@excalidraw/excalidraw/polyfill";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { loadFromBlob } from "@excalidraw/excalidraw/data/blob"; import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
import { t } from "@excalidraw/excalidraw/i18n"; import { t } from "@excalidraw/excalidraw/i18n";
import { import {
@@ -75,7 +74,6 @@ import type {
BinaryFiles, BinaryFiles,
ExcalidrawInitialDataState, ExcalidrawInitialDataState,
UIAppState, UIAppState,
ExcalidrawProps,
} from "@excalidraw/excalidraw/types"; } from "@excalidraw/excalidraw/types";
import type { ResolutionType } from "@excalidraw/common/utility-types"; import type { ResolutionType } from "@excalidraw/common/utility-types";
import type { ResolvablePromise } from "@excalidraw/common/utils"; import type { ResolvablePromise } from "@excalidraw/common/utils";
@@ -116,7 +114,6 @@ import {
} from "./data"; } from "./data";
import { updateStaleImageStatuses } from "./data/FileManager"; import { updateStaleImageStatuses } from "./data/FileManager";
import { FileStatusStore } from "./data/fileStatusStore";
import { import {
importFromLocalStorage, importFromLocalStorage,
importUsernameFromLocalStorage, importUsernameFromLocalStorage,
@@ -372,8 +369,6 @@ const initializeScene = async (opts: {
}; };
const ExcalidrawWrapper = () => { const ExcalidrawWrapper = () => {
const excalidrawAPI = useExcalidrawAPI();
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
const isCollabDisabled = isRunningInIframe(); const isCollabDisabled = isRunningInIframe();
@@ -404,6 +399,9 @@ const ExcalidrawWrapper = () => {
}, VERSION_TIMEOUT); }, VERSION_TIMEOUT);
}, []); }, []);
const [excalidrawAPI, excalidrawRefCallback] =
useCallbackRefState<ExcalidrawImperativeAPI>();
const [, setShareDialogState] = useAtom(shareDialogStateAtom); const [, setShareDialogState] = useAtom(shareDialogStateAtom);
const [collabAPI] = useAtom(collabAPIAtom); const [collabAPI] = useAtom(collabAPIAtom);
const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => { const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => {
@@ -435,15 +433,18 @@ const ExcalidrawWrapper = () => {
} }
}, [excalidrawAPI]); }, [excalidrawAPI]);
// --------------------------------------------------------------------------- useEffect(() => {
// Hoisted loadImages if (!excalidrawAPI || (!isCollabDisabled && !collabAPI)) {
// --------------------------------------------------------------------------- return;
const loadImages = useCallback( }
(data: ResolutionType<typeof initializeScene>, isInitialLoad = false) => {
if (!data.scene || !excalidrawAPI) { const loadImages = (
data: ResolutionType<typeof initializeScene>,
isInitialLoad = false,
) => {
if (!data.scene) {
return; return;
} }
if (collabAPI?.isCollaborating()) { if (collabAPI?.isCollaborating()) {
if (data.scene.elements) { if (data.scene.elements) {
collabAPI collabAPI
@@ -470,12 +471,6 @@ const ExcalidrawWrapper = () => {
}, [] as FileId[]) || []; }, [] as FileId[]) || [];
if (data.isExternalScene) { if (data.isExternalScene) {
if (fileIds.length) {
// Direct Firebase call (not through FileManager), so track manually
FileStatusStore.updateStatuses(
fileIds.map((id) => [id, "loading"]),
);
}
loadFilesFromFirebase( loadFilesFromFirebase(
`${FIREBASE_STORAGE_PREFIXES.shareLinkFiles}/${data.id}`, `${FIREBASE_STORAGE_PREFIXES.shareLinkFiles}/${data.id}`,
data.key, data.key,
@@ -487,18 +482,12 @@ const ExcalidrawWrapper = () => {
erroredFiles, erroredFiles,
elements: excalidrawAPI.getSceneElementsIncludingDeleted(), elements: excalidrawAPI.getSceneElementsIncludingDeleted(),
}); });
FileStatusStore.updateStatuses([
...loadedFiles.map((f) => [f.id, "loaded"] as [FileId, "loaded"]),
...[...erroredFiles.keys()].map(
(id) => [id, "error"] as [FileId, "error"],
),
]);
}); });
} else if (isInitialLoad) { } else if (isInitialLoad) {
if (fileIds.length) { if (fileIds.length) {
LocalData.fileStorage LocalData.fileStorage
.getFiles(fileIds) .getFiles(fileIds)
.then(async ({ loadedFiles, erroredFiles }) => { .then(({ loadedFiles, erroredFiles }) => {
if (loadedFiles.length) { if (loadedFiles.length) {
excalidrawAPI.addFiles(loadedFiles); excalidrawAPI.addFiles(loadedFiles);
} }
@@ -511,19 +500,10 @@ const ExcalidrawWrapper = () => {
} }
// on fresh load, clear unused files from IDB (from previous // on fresh load, clear unused files from IDB (from previous
// session) // session)
LocalData.fileStorage.clearObsoleteFiles({ LocalData.fileStorage.clearObsoleteFiles({ currentFileIds: fileIds });
currentFileIds: fileIds,
});
} }
} }
}, };
[collabAPI, excalidrawAPI],
);
useEffect(() => {
if (!excalidrawAPI || (!isCollabDisabled && !collabAPI)) {
return;
}
initializeScene({ collabAPI, excalidrawAPI }).then(async (data) => { initializeScene({ collabAPI, excalidrawAPI }).then(async (data) => {
loadImages(data, /* isInitialLoad */ true); loadImages(data, /* isInitialLoad */ true);
@@ -648,7 +628,7 @@ const ExcalidrawWrapper = () => {
false, false,
); );
}; };
}, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode, loadImages]); }, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode]);
useEffect(() => { useEffect(() => {
const unloadHandler = (event: BeforeUnloadEvent) => { const unloadHandler = (event: BeforeUnloadEvent) => {
@@ -793,56 +773,6 @@ const ExcalidrawWrapper = () => {
[setShareDialogState], [setShareDialogState],
); );
// ---------------------------------------------------------------------------
// onExport — intercepts file save to wait for pending image loads
// ---------------------------------------------------------------------------
const onExport: Required<ExcalidrawProps>["onExport"] = useCallback(
async function* () {
let snapshot = FileStatusStore.getSnapshot();
const { pending, total } = FileStatusStore.getPendingCount(
snapshot.value,
);
if (pending === 0) {
return;
}
// Yield initial progress
yield {
type: "progress",
progress: (total - pending) / total,
message: `Loading images (${total - pending}/${total})...`,
};
// Wait for all pending images to finish
while (true) {
snapshot = await FileStatusStore.pull(snapshot.version);
const { pending: nowPending, total: nowTotal } =
FileStatusStore.getPendingCount(snapshot.value);
yield {
type: "progress",
progress: (nowTotal - nowPending) / nowTotal,
message: `Loading images (${nowTotal - nowPending}/${nowTotal})...`,
};
if (nowPending === 0) {
await new Promise((r) => setTimeout(r, 500));
yield {
type: "progress",
message: `Preparing export...`,
};
return;
}
}
},
[],
);
// const onExport = () => {
// return new Promise((r) => setTimeout(r, 2500));
// // console.log("onExport");
// };
// browsers generally prevent infinite self-embedding, there are // browsers generally prevent infinite self-embedding, there are
// cases where it still happens, and while we disallow self-embedding // cases where it still happens, and while we disallow self-embedding
// by not whitelisting our own origin, this serves as an additional guard // by not whitelisting our own origin, this serves as an additional guard
@@ -909,8 +839,8 @@ const ExcalidrawWrapper = () => {
})} })}
> >
<Excalidraw <Excalidraw
excalidrawAPI={excalidrawRefCallback}
onChange={onChange} onChange={onChange}
onExport={onExport}
initialData={initialStatePromiseRef.current.promise} initialData={initialStatePromiseRef.current.promise}
isCollaborating={isCollaborating} isCollaborating={isCollaborating}
onPointerUpdate={collabAPI?.onPointerUpdate} onPointerUpdate={collabAPI?.onPointerUpdate}
@@ -1276,9 +1206,7 @@ const ExcalidrawApp = () => {
return ( return (
<TopErrorBoundary> <TopErrorBoundary>
<Provider store={appJotaiStore}> <Provider store={appJotaiStore}>
<ExcalidrawAPIProvider> <ExcalidrawWrapper />
<ExcalidrawWrapper />
</ExcalidrawAPIProvider>
</Provider> </Provider>
</TopErrorBoundary> </TopErrorBoundary>
); );
-2
View File
@@ -72,7 +72,6 @@ import {
FileManager, FileManager,
updateStaleImageStatuses, updateStaleImageStatuses,
} from "../data/FileManager"; } from "../data/FileManager";
import { FileStatusStore } from "../data/fileStatusStore";
import { LocalData } from "../data/LocalData"; import { LocalData } from "../data/LocalData";
import { import {
isSavedToFirebase, isSavedToFirebase,
@@ -150,7 +149,6 @@ class Collab extends PureComponent<CollabProps, CollabState> {
}; };
this.portal = new Portal(this); this.portal = new Portal(this);
this.fileManager = new FileManager({ this.fileManager = new FileManager({
onFileStatusChange: FileStatusStore.updateStatuses.bind(FileStatusStore),
getFiles: async (fileIds) => { getFiles: async (fileIds) => {
const { roomId, roomKey } = this.portal; const { roomId, roomKey } = this.portal;
if (!roomId || !roomKey) { if (!roomId || !roomKey) {
@@ -414,6 +414,7 @@ export const debugRenderer = throttleRAF(
) => { ) => {
_debugRenderer(canvas, appState, elements, scale); _debugRenderer(canvas, appState, elements, scale);
}, },
{ trailing: true },
); );
export const loadSavedDebugState = () => { export const loadSavedDebugState = () => {
-22
View File
@@ -40,12 +40,10 @@ export class FileManager {
private _getFiles; private _getFiles;
private _saveFiles; private _saveFiles;
private _onFileStatusChange;
constructor({ constructor({
getFiles, getFiles,
saveFiles, saveFiles,
onFileStatusChange,
}: { }: {
getFiles: (fileIds: FileId[]) => Promise<{ getFiles: (fileIds: FileId[]) => Promise<{
loadedFiles: BinaryFileData[]; loadedFiles: BinaryFileData[];
@@ -55,13 +53,9 @@ export class FileManager {
savedFiles: Map<FileId, BinaryFileData>; savedFiles: Map<FileId, BinaryFileData>;
erroredFiles: Map<FileId, BinaryFileData>; erroredFiles: Map<FileId, BinaryFileData>;
}>; }>;
onFileStatusChange?: (
updates: Array<[FileId, "loading" | "loaded" | "error"]>,
) => void;
}) { }) {
this._getFiles = getFiles; this._getFiles = getFiles;
this._saveFiles = saveFiles; this._saveFiles = saveFiles;
this._onFileStatusChange = onFileStatusChange;
} }
/** /**
@@ -152,8 +146,6 @@ export class FileManager {
this.fetchingFiles.set(id, true); this.fetchingFiles.set(id, true);
} }
this._onFileStatusChange?.(ids.map((id) => [id, "loading"]));
try { try {
const { loadedFiles, erroredFiles } = await this._getFiles(ids); const { loadedFiles, erroredFiles } = await this._getFiles(ids);
@@ -164,13 +156,6 @@ export class FileManager {
this.erroredFiles_fetch.set(fileId, true); this.erroredFiles_fetch.set(fileId, true);
} }
this._onFileStatusChange?.([
...loadedFiles.map((f) => [f.id, "loaded"] as [FileId, "loaded"]),
...[...erroredFiles.keys()].map(
(id) => [id, "error"] as [FileId, "error"],
),
]);
return { loadedFiles, erroredFiles }; return { loadedFiles, erroredFiles };
} finally { } finally {
for (const id of ids) { for (const id of ids) {
@@ -210,13 +195,6 @@ export class FileManager {
}; };
reset() { reset() {
if (this._onFileStatusChange && this.fetchingFiles.size) {
this._onFileStatusChange(
[...this.fetchingFiles.keys()].map(
(id) => [id, "error"] as [FileId, "error"],
),
);
}
this.fetchingFiles.clear(); this.fetchingFiles.clear();
this.savingFiles.clear(); this.savingFiles.clear();
this.savedFiles.clear(); this.savedFiles.clear();
-2
View File
@@ -42,7 +42,6 @@ import type { MaybePromise } from "@excalidraw/common/utility-types";
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants"; import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
import { FileManager } from "./FileManager"; import { FileManager } from "./FileManager";
import { FileStatusStore } from "./fileStatusStore";
import { Locker } from "./Locker"; import { Locker } from "./Locker";
import { updateBrowserStateVersion } from "./tabSync"; import { updateBrowserStateVersion } from "./tabSync";
@@ -167,7 +166,6 @@ export class LocalData {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static fileStorage = new LocalFileManager({ static fileStorage = new LocalFileManager({
onFileStatusChange: FileStatusStore.updateStatuses.bind(FileStatusStore),
getFiles(ids) { getFiles(ids) {
return getMany(ids, filesStore).then( return getMany(ids, filesStore).then(
async (filesData: (BinaryFileData | undefined)[]) => { async (filesData: (BinaryFileData | undefined)[]) => {
-48
View File
@@ -1,48 +0,0 @@
import { VersionedSnapshotStore } from "@excalidraw/common";
import type { FileId } from "@excalidraw/element/types";
export type FileLoadingStatus = "loading" | "loaded" | "error";
export class FileStatusStore {
private static store = new VersionedSnapshotStore<
Map<FileId, FileLoadingStatus>
>(new Map());
static getSnapshot() {
return this.store.getSnapshot();
}
static pull(sinceVersion?: number) {
return this.store.pull(sinceVersion);
}
static updateStatuses(updates: Array<[FileId, FileLoadingStatus]>) {
if (!updates.length) {
return;
}
this.store.update((prev) => {
let changed = false;
const next = new Map(prev);
for (const [id, status] of updates) {
if (next.get(id) !== status) {
next.set(id, status);
changed = true;
}
}
return changed ? next : prev;
});
}
static getPendingCount(statuses: Map<FileId, FileLoadingStatus>) {
let pending = 0;
let total = 0;
for (const status of statuses.values()) {
total++;
if (status === "loading") {
pending++;
}
}
return { pending, total };
}
}
+1 -10
View File
@@ -106,10 +106,6 @@ export default defineConfig(({ mode }) => {
if (id.includes("@excalidraw/mermaid-to-excalidraw")) { if (id.includes("@excalidraw/mermaid-to-excalidraw")) {
return "mermaid-to-excalidraw"; return "mermaid-to-excalidraw";
} }
if (id.includes("@codemirror/") || id.includes("@lezer/")) {
return "codemirror.chunk";
}
}, },
}, },
}, },
@@ -154,11 +150,6 @@ export default defineConfig(({ mode }) => {
"**/locales/**", "**/locales/**",
"service-worker.js", "service-worker.js",
"**/*.chunk-*.js", "**/*.chunk-*.js",
// CodeMirrorEditor can't be assigned a `.chunk` name via
// manualChunks because Rollup would hoist shared deps (React)
// via a static import from the main bundle, defeating lazy
// loading. So we exclude it by name instead.
"**/CodeMirrorEditor-*.js",
], ],
runtimeCaching: [ runtimeCaching: [
{ {
@@ -198,7 +189,7 @@ export default defineConfig(({ mode }) => {
}, },
}, },
{ {
urlPattern: new RegExp("(.chunk-.+|CodeMirrorEditor-.+)\\.js"), urlPattern: new RegExp(".chunk-.+.js"),
handler: "CacheFirst", handler: "CacheFirst",
options: { options: {
cacheName: "chunk", cacheName: "chunk",
-74
View File
@@ -1,74 +0,0 @@
import { AppEventBus } from "./appEventBus";
type TestEvents = {
initialize: [api: number];
pointerUp: [pointerId: string];
viewState: [zoom: number];
};
const behavior = {
initialize: { cardinality: "once", replay: "last" },
pointerUp: { cardinality: "many", replay: "none" },
viewState: { cardinality: "many", replay: "last" },
} as const;
const flushMicrotasks = async () => Promise.resolve();
describe("AppEventBus", () => {
it("replays once events to late callback and Promise subscribers", async () => {
const bus = new AppEventBus<TestEvents, typeof behavior>(behavior);
bus.emit("initialize", 42);
const calls: number[] = [];
bus.on("initialize", (value) => {
calls.push(value);
});
expect(calls).toEqual([]);
await flushMicrotasks();
expect(calls).toEqual([42]);
await expect(bus.on("initialize")).resolves.toBe(42);
});
it("does not replay stream events to late subscribers", async () => {
const bus = new AppEventBus<TestEvents, typeof behavior>(behavior);
bus.emit("pointerUp", "first");
const calls: string[] = [];
bus.on("pointerUp", (pointerId) => {
calls.push(pointerId);
});
await flushMicrotasks();
expect(calls).toEqual([]);
bus.emit("pointerUp", "second");
expect(calls).toEqual(["second"]);
});
it("replays replay-last stream events and stays subscribed", async () => {
const bus = new AppEventBus<TestEvents, typeof behavior>(behavior);
bus.emit("viewState", 1);
const calls: number[] = [];
bus.on("viewState", (zoom) => {
calls.push(zoom);
});
await flushMicrotasks();
expect(calls).toEqual([1]);
bus.emit("viewState", 2);
expect(calls).toEqual([1, 2]);
});
it("throws when emitting a once event twice", () => {
const bus = new AppEventBus<TestEvents, typeof behavior>(behavior);
bus.emit("initialize", 1);
expect(() => {
bus.emit("initialize", 2);
}).toThrow('Event "initialize" can only be emitted once');
});
});
-136
View File
@@ -1,136 +0,0 @@
import type { UnsubscribeCallback } from "@excalidraw/excalidraw/types";
import { Emitter } from "./emitter";
import { isProdEnv } from "./utils";
export type AppEventPayloadMap = Record<string, unknown[]>;
export type AppEventBehavior = {
cardinality: "once" | "many";
replay: "none" | "last";
};
export type AppEventBehaviorMap<Events extends AppEventPayloadMap> = {
[K in keyof Events]: AppEventBehavior;
};
type AwaitableAppEventKeys<
Events extends AppEventPayloadMap,
Behavior extends AppEventBehaviorMap<Events>,
> = {
[K in keyof Events]: Behavior[K]["cardinality"] extends "once"
? Behavior[K]["replay"] extends "last"
? K
: never
: never;
}[keyof Events];
type AppEventPromiseValue<Args extends any[]> = Args extends [infer Only]
? Only
: Args;
export class AppEventBus<
Events extends AppEventPayloadMap,
Behavior extends AppEventBehaviorMap<Events>,
> {
private readonly emitters = new Map<keyof Events, Emitter<any>>();
private readonly lastPayload = new Map<keyof Events, any[]>();
private readonly emittedOnce = new Set<keyof Events>();
constructor(private readonly behavior: Behavior) {}
private getEmitter<K extends keyof Events>(name: K): Emitter<Events[K]> {
let emitter = this.emitters.get(name);
if (!emitter) {
emitter = new Emitter<any>();
this.emitters.set(name, emitter);
}
return emitter as Emitter<Events[K]>;
}
private toPromiseValue<Args extends any[]>(
args: Args,
): AppEventPromiseValue<Args> {
return (args.length === 1 ? args[0] : args) as AppEventPromiseValue<Args>;
}
public on<K extends keyof Events>(
name: K,
callback: (...args: Events[K]) => void,
): UnsubscribeCallback;
public on<K extends AwaitableAppEventKeys<Events, Behavior>>(
name: K,
): Promise<AppEventPromiseValue<Events[K]>>;
public on<K extends keyof Events>(
name: K,
callback?: (...args: Events[K]) => void,
): UnsubscribeCallback | Promise<AppEventPromiseValue<Events[K]>> {
const eventBehavior = this.behavior[name];
const cachedPayload = this.lastPayload.get(name) as Events[K] | undefined;
if (callback) {
if (eventBehavior.replay === "last" && cachedPayload) {
queueMicrotask(() => callback(...cachedPayload));
if (eventBehavior.cardinality === "once") {
return () => {};
}
}
return this.getEmitter(name).on(callback);
}
if (
eventBehavior.cardinality !== "once" ||
eventBehavior.replay !== "last"
) {
throw new Error(`Event "${String(name)}" requires a callback`);
}
if (cachedPayload) {
return Promise.resolve(this.toPromiseValue(cachedPayload));
}
return new Promise<AppEventPromiseValue<Events[K]>>((resolve) => {
this.getEmitter(name).once((...args: Events[K]) => {
resolve(this.toPromiseValue(args));
});
});
}
public emit<K extends keyof Events>(name: K, ...args: Events[K]) {
const eventBehavior = this.behavior[name];
if (!isProdEnv()) {
if (eventBehavior.cardinality === "once") {
if (this.emittedOnce.has(name)) {
throw new Error(`Event "${String(name)}" can only be emitted once`);
}
this.emittedOnce.add(name);
}
}
if (eventBehavior.replay === "last") {
this.lastPayload.set(name, args);
}
try {
this.getEmitter(name).trigger(...args);
} finally {
if (eventBehavior.cardinality === "once") {
this.getEmitter(name).clear();
}
}
}
public clear() {
this.lastPayload.clear();
this.emittedOnce.clear();
for (const emitter of this.emitters.values()) {
emitter.clear();
}
this.emitters.clear();
}
}
-2
View File
@@ -11,7 +11,5 @@ export * from "./random";
export * from "./url"; export * from "./url";
export * from "./utils"; export * from "./utils";
export * from "./emitter"; export * from "./emitter";
export * from "./appEventBus";
export * from "./editorInterface"; export * from "./editorInterface";
export * from "./versionedSnapshotStore";
export { Debug } from "../debug"; export { Debug } from "../debug";
-89
View File
@@ -3,12 +3,6 @@ import {
mapFind, mapFind,
reduceToCommonValue, reduceToCommonValue,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { vi } from "vitest";
// Import directly to avoid the @excalidraw/common throttleRAF mock from setupTests.ts.
import { throttleRAF } from "./utils";
type RafCallback = FrameRequestCallback;
describe("@excalidraw/common/utils", () => { describe("@excalidraw/common/utils", () => {
describe("isTransparent()", () => { describe("isTransparent()", () => {
@@ -85,87 +79,4 @@ describe("@excalidraw/common/utils", () => {
expect(mapFind([1, 2], () => null)).toBe(undefined); expect(mapFind([1, 2], () => null)).toBe(undefined);
}); });
}); });
describe("throttleRAF()", () => {
let frameCallbacks: Map<number, RafCallback>;
let nextFrameId: number;
const runScheduledFrame = (timestamp = 16) => {
const callbacks = [...frameCallbacks.values()];
frameCallbacks.clear();
callbacks.forEach((callback) => callback(timestamp));
};
beforeEach(() => {
frameCallbacks = new Map();
nextFrameId = 0;
vi.spyOn(window, "requestAnimationFrame").mockImplementation(
(callback) => {
const frameId = ++nextFrameId;
frameCallbacks.set(frameId, callback);
return frameId;
},
);
vi.spyOn(window, "cancelAnimationFrame").mockImplementation((frameId) => {
frameCallbacks.delete(frameId);
});
});
afterEach(() => {
vi.restoreAllMocks();
});
it("should invoke the callback with the last args from the same frame", () => {
const fn = vi.fn();
const throttled = throttleRAF(fn);
throttled("first", 1);
throttled("second", 2);
throttled("last", 3);
expect(fn).not.toHaveBeenCalled();
expect(window.requestAnimationFrame).toHaveBeenCalledTimes(1);
runScheduledFrame();
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenCalledWith("last", 3);
});
it("should flush the pending callback immediately", () => {
const fn = vi.fn();
const throttled = throttleRAF(fn);
throttled("first");
throttled("last");
throttled.flush();
expect(window.cancelAnimationFrame).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenCalledWith("last");
runScheduledFrame();
expect(fn).toHaveBeenCalledTimes(1);
});
it("should cancel the pending callback", () => {
const fn = vi.fn();
const throttled = throttleRAF(fn);
throttled("first");
throttled("last");
throttled.cancel();
expect(window.cancelAnimationFrame).toHaveBeenCalledTimes(1);
runScheduledFrame();
expect(fn).not.toHaveBeenCalled();
});
});
}); });
+23 -13
View File
@@ -88,8 +88,7 @@ export const isWritableElement = (
(target.type === "text" || (target.type === "text" ||
target.type === "number" || target.type === "number" ||
target.type === "password" || target.type === "password" ||
target.type === "search")) || target.type === "search"));
(target instanceof HTMLElement && target.closest(".cm-editor") !== null);
export const getFontFamilyString = ({ export const getFontFamilyString = ({
fontFamily, fontFamily,
@@ -151,27 +150,38 @@ export const debounce = <T extends any[]>(
return ret; return ret;
}; };
// throttle callback to execute once per animation frame using the latest args // throttle callback to execute once per animation frame
export const throttleRAF = <T extends any[]>(fn: (...args: T) => void) => { export const throttleRAF = <T extends any[]>(
fn: (...args: T) => void,
opts?: { trailing?: boolean },
) => {
let timerId: number | null = null; let timerId: number | null = null;
let lastArgs: T | null = null; let lastArgs: T | null = null;
let lastArgsTrailing: T | null = null;
const scheduleFunc = () => { const scheduleFunc = (args: T) => {
timerId = window.requestAnimationFrame(() => { timerId = window.requestAnimationFrame(() => {
timerId = null; timerId = null;
const args = lastArgs; fn(...args);
lastArgs = null; lastArgs = null;
if (lastArgsTrailing) {
if (args) { lastArgs = lastArgsTrailing;
fn(...args); lastArgsTrailing = null;
scheduleFunc(lastArgs);
} }
}); });
}; };
const ret = (...args: T) => { const ret = (...args: T) => {
if (isTestEnv()) {
fn(...args);
return;
}
lastArgs = args; lastArgs = args;
if (timerId === null) { if (timerId === null) {
scheduleFunc(); scheduleFunc(lastArgs);
} else if (opts?.trailing) {
lastArgsTrailing = args;
} }
}; };
ret.flush = () => { ret.flush = () => {
@@ -180,12 +190,12 @@ export const throttleRAF = <T extends any[]>(fn: (...args: T) => void) => {
timerId = null; timerId = null;
} }
if (lastArgs) { if (lastArgs) {
fn(...lastArgs); fn(...(lastArgsTrailing || lastArgs));
lastArgs = null; lastArgs = lastArgsTrailing = null;
} }
}; };
ret.cancel = () => { ret.cancel = () => {
lastArgs = null; lastArgs = lastArgsTrailing = null;
if (timerId !== null) { if (timerId !== null) {
cancelAnimationFrame(timerId); cancelAnimationFrame(timerId);
timerId = null; timerId = null;
@@ -1,70 +0,0 @@
export type VersionedSnapshot<T> = Readonly<{
version: number;
value: T;
}>;
export class VersionedSnapshotStore<T> {
private version = 0;
private value: T;
private readonly waiters = new Set<
(snapshot: VersionedSnapshot<T>) => void
>();
private readonly subscribers = new Set<
(snapshot: VersionedSnapshot<T>) => void
>();
constructor(
initialValue: T,
private readonly isEqual: (prev: T, next: T) => boolean = Object.is,
) {
this.value = initialValue;
}
public getSnapshot(): VersionedSnapshot<T> {
return { version: this.version, value: this.value };
}
public set(nextValue: T): boolean {
if (this.isEqual(this.value, nextValue)) {
return false;
}
this.value = nextValue;
this.version += 1;
const snapshot = this.getSnapshot();
for (const subscriber of this.subscribers) {
subscriber(snapshot);
}
for (const waiter of this.waiters) {
waiter(snapshot);
}
this.waiters.clear();
return true;
}
public update(updater: (prev: T) => T): boolean {
return this.set(updater(this.value));
}
public subscribe(
subscriber: (snapshot: VersionedSnapshot<T>) => void,
): () => void {
this.subscribers.add(subscriber);
return () => {
this.subscribers.delete(subscriber);
};
}
public pull(sinceVersion = -1): Promise<VersionedSnapshot<T>> {
if (this.version !== sinceVersion) {
return Promise.resolve(this.getSnapshot());
}
return new Promise((resolve) => {
this.waiters.add(resolve);
});
}
}
-86
View File
@@ -11,92 +11,6 @@ The change should be grouped under one of the below section and must contain PR
Please add the latest change on the top under the correct section. Please add the latest change on the top under the correct section.
--> -->
## Unreleased
## Excalidraw API
### Breaking changes
- Renamed the `excalidrawAPI` prop to `onExcalidrawAPI`.
### Features
- Added `onMount` and `onInitialize` props. `onMount` receives `{ excalidrawAPI, container }` once the editor root is mounted, and `onInitialize` fires once the initial scene has loaded.
```tsx
<Excalidraw
onMount={({ excalidrawAPI, container }) => {
console.log(container);
excalidrawAPI.scrollToContent();
}}
onInitialize={(api) => {
api.refresh();
}}
/>
```
- Same events are also accessible imperatively through `api.onEvent(...)`.
```tsx
<Excalidraw
onExcalidrawAPI={(api) => {
api.onEvent("editor:mount", ({ excalidrawAPI, container }) => {
excalidrawAPI.scrollToContent();
console.log(container);
});
api.onEvent("editor:initialize").then((readyApi) => {
readyApi.scrollToContent();
});
}}
/>
```
Note that in future releases, most, if not all, `excalidrawAPI.on*` subscriptions will be removed in favor of `excalidrawAPI.onEvent(name)`.
- Exported `ExcalidrawAPIProvider`, `useExcalidrawAPI`, and `useAppStateValue` from the package entrypoint. The imperative API also now exposes `onStateChange`.
```tsx
<ExcalidrawAPIProvider>
<Excalidraw />
<Logger />
</ExcalidrawAPIProvider>;
function Logger() {
const api = useExcalidrawAPI();
useAppStateValue("viewModeEnabled", (viewModeEnabled) => {
console.log("view mode changed:", viewModeEnabled);
});
React.useEffect(() => {
if (api) {
console.log("editor instance id:", api.id);
}
}, [api]);
return null;
}
```
- Added `onExport` so host apps can delay JSON export until async work completes. The handler receives the export data plus an `AbortSignal`, and may return a `Promise` or an async generator that yields progress updates for the built-in toast UI.
```tsx
<Excalidraw
onExport={async function* (_type, { files }, { signal }) {
yield { type: "progress", message: "Waiting for images..." };
await waitForImagesToLoad(files, signal);
if (signal.aborted) {
return;
}
yield { type: "progress", message: "Export ready", progress: 1 };
}}
/>
```
## Excalidraw Library ## Excalidraw Library
## 0.18.0 (2025-03-11) ## 0.18.0 (2025-03-11)
@@ -30,7 +30,7 @@ import { getSelectedElements, isSomeElementSelected } from "../scene";
import { TrashIcon } from "../components/icons"; import { TrashIcon } from "../components/icons";
import { ToolButton } from "../components/ToolButton"; import { ToolButton } from "../components/ToolButton";
import { useStylesPanelMode } from "../components/App"; import { useStylesPanelMode } from "..";
import { register } from "./register"; import { register } from "./register";
@@ -27,7 +27,7 @@ import { t } from "../i18n";
import { isSomeElementSelected } from "../scene"; import { isSomeElementSelected } from "../scene";
import { getShortcutKey } from "../shortcut"; import { getShortcutKey } from "../shortcut";
import { useStylesPanelMode } from "../components/App"; import { useStylesPanelMode } from "..";
import { register } from "./register"; import { register } from "./register";
+37 -219
View File
@@ -9,20 +9,18 @@ import { getNonDeletedElements } from "@excalidraw/element";
import { CaptureUpdateAction } from "@excalidraw/element"; import { CaptureUpdateAction } from "@excalidraw/element";
import type { ExcalidrawElement, Theme } from "@excalidraw/element/types"; import type { Theme } from "@excalidraw/element/types";
import { useEditorInterface } from "../components/App"; import { useEditorInterface } from "../components/App";
import { CheckboxItem } from "../components/CheckboxItem"; import { CheckboxItem } from "../components/CheckboxItem";
import { DarkModeToggle } from "../components/DarkModeToggle"; import { DarkModeToggle } from "../components/DarkModeToggle";
import { ProjectName } from "../components/ProjectName"; import { ProjectName } from "../components/ProjectName";
import { Toast } from "../components/Toast";
import { ToolButton } from "../components/ToolButton"; import { ToolButton } from "../components/ToolButton";
import { Tooltip } from "../components/Tooltip"; import { Tooltip } from "../components/Tooltip";
import { ExportIcon, questionCircle, saveAs } from "../components/icons"; import { ExportIcon, questionCircle, saveAs } from "../components/icons";
import { loadFromJSON, saveAsJSON } from "../data"; import { loadFromJSON, saveAsJSON } from "../data";
import { isImageFileHandle } from "../data/blob"; import { isImageFileHandle } from "../data/blob";
import { nativeFileSystemSupported } from "../data/filesystem"; import { nativeFileSystemSupported } from "../data/filesystem";
import { resaveAsImageWithScene } from "../data/resave"; import { resaveAsImageWithScene } from "../data/resave";
import { t } from "../i18n"; import { t } from "../i18n";
@@ -33,15 +31,7 @@ import "../components/ToolIcon.scss";
import { register } from "./register"; import { register } from "./register";
import type { JSONExportData } from "../data/json"; import type { AppState } from "../types";
import type {
AppClassProperties,
AppState,
BinaryFiles,
ExcalidrawProps,
OnExportProgress,
} from "../types";
export const actionChangeProjectName = register<AppState["name"]>({ export const actionChangeProjectName = register<AppState["name"]>({
name: "changeProjectName", name: "changeProjectName",
@@ -160,143 +150,6 @@ export const actionChangeExportEmbedScene = register<
), ),
}); });
// ---------------------------------------------------------------------------
// onExport interception helpers
// ---------------------------------------------------------------------------
let onExportInProgress = false;
const onProgressToast = (
app: AppClassProperties,
progress: {
message?: OnExportProgress["message"];
progress?: number | null;
},
) => {
const message = progress.message ?? t("progressDialog.defaultMessage");
app.setAppState({
toast: {
message:
progress.progress != null ? (
<>
{message}
<Toast.ProgressBar progress={progress.progress} />
</>
) : (
message
),
duration: Infinity,
},
});
};
/** awaits host app's onExport result, and renders progress to the UI */
async function handleOnExportResult(
onExportResult: ReturnType<NonNullable<ExcalidrawProps["onExport"]>>,
opts: {
signal: AbortSignal;
app: AppClassProperties;
},
): Promise<void> {
if (opts.app.state.isLoading) {
onProgressToast(opts.app, { progress: null });
await opts.app.onStateChange({ predicate: (state) => !state.isLoading });
}
if (
onExportResult != null &&
typeof onExportResult === "object" &&
Symbol.asyncIterator in onExportResult
) {
for await (const value of onExportResult) {
if (opts.signal.aborted) {
onExportResult.return();
return;
}
if (value.type === "progress") {
onProgressToast(opts.app, {
message: value.message,
progress: value.progress ?? null,
});
} else if (value.type === "done") {
return;
}
}
// Generator completed without explicit "done" message
return;
}
if (onExportResult instanceof Promise) {
onProgressToast(opts.app, { progress: null });
await onExportResult;
}
}
function prepareDataForJSONExport(
elements: readonly ExcalidrawElement[],
appState: AppState,
files: BinaryFiles,
app: AppClassProperties,
): { abortController: AbortController; data: Promise<JSONExportData> } {
const abortController = new AbortController();
const signal = abortController.signal;
const dataPromise = new Promise<JSONExportData>(async (resolve) => {
try {
if (app.props.onExport) {
await handleOnExportResult(
app.props.onExport(
"json",
{
elements,
appState,
files,
},
{
signal,
},
),
{
app,
signal,
},
);
}
} catch (error: any) {
if (error?.name === "AbortError") {
// if abort error, assume it's a reaction on the signal being aborted
console.warn(
`onExport() aborted by host app (signal aborted: ${signal.aborted})`,
);
} else {
// non-abort error
//
console.error("Error during props.onExport() handling", error);
}
// either way, we currently don't allow host apps to cancel save actions
// so we resolve to orig data
}
resolve({
elements,
appState,
// return latest files in case they finished loading during onExport
files: app.files,
});
});
return {
abortController,
data: dataPromise,
};
}
// ---------------------------------------------------------------------------
// Save actions
// ---------------------------------------------------------------------------
export const actionSaveToActiveFile = register({ export const actionSaveToActiveFile = register({
name: "saveToActiveFile", name: "saveToActiveFile",
label: "buttons.save", label: "buttons.save",
@@ -310,62 +163,42 @@ export const actionSaveToActiveFile = register({
); );
}, },
perform: async (elements, appState, value, app) => { perform: async (elements, appState, value, app) => {
if (onExportInProgress) { const fileHandleExists = !!appState.fileHandle;
return false;
}
onExportInProgress = true;
const previousFileHandle = appState.fileHandle;
const filename = app.getName();
const { abortController, data: exportedDataPromise } =
prepareDataForJSONExport(elements, appState, app.files, app);
try { try {
const { fileHandle } = isImageFileHandle(previousFileHandle) const { fileHandle } = isImageFileHandle(appState.fileHandle)
? await resaveAsImageWithScene( ? await resaveAsImageWithScene(
exportedDataPromise, elements,
previousFileHandle, appState,
filename, app.files,
app.getName(),
) )
: await saveAsJSON({ : await saveAsJSON(elements, appState, app.files, app.getName());
data: exportedDataPromise,
filename,
fileHandle: previousFileHandle,
});
return { return {
captureUpdate: CaptureUpdateAction.NEVER, captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: { appState: {
...appState,
fileHandle, fileHandle,
toast: { toast: fileHandleExists
message: ? {
previousFileHandle && fileHandle?.name message: fileHandle?.name
? t("toast.fileSavedToFilename").replace( ? t("toast.fileSavedToFilename").replace(
"{filename}", "{filename}",
`"${fileHandle.name}"`, `"${fileHandle.name}"`,
) )
: t("toast.fileSaved"), : t("toast.fileSaved"),
duration: 1500, }
}, : null,
}, },
}; };
} catch (error: any) { } catch (error: any) {
abortController.abort();
if (error?.name !== "AbortError") { if (error?.name !== "AbortError") {
console.error(error); console.error(error);
} else { } else {
console.warn(error); console.warn(error);
} }
return { return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
captureUpdate: CaptureUpdateAction.NEVER,
appState: {
toast: null,
},
};
} finally {
onExportInProgress = false;
} }
}, },
keyTest: (event) => keyTest: (event) =>
@@ -379,50 +212,36 @@ export const actionSaveFileToDisk = register({
viewMode: true, viewMode: true,
trackEvent: { category: "export" }, trackEvent: { category: "export" },
perform: async (elements, appState, value, app) => { perform: async (elements, appState, value, app) => {
if (onExportInProgress) {
return false;
}
onExportInProgress = true;
const { abortController, data: exportedDataPromise } =
prepareDataForJSONExport(elements, appState, app.files, app);
try { try {
const { fileHandle: savedFileHandle } = await saveAsJSON({ const { fileHandle } = await saveAsJSON(
data: exportedDataPromise, elements,
filename: app.getName(), {
fileHandle: null, ...appState,
}); fileHandle: null,
},
app.files,
app.getName(),
);
return { return {
captureUpdate: CaptureUpdateAction.NEVER, captureUpdate: CaptureUpdateAction.EVENTUALLY,
appState: { appState: {
...appState,
openDialog: null, openDialog: null,
fileHandle: savedFileHandle, fileHandle,
toast: { message: t("toast.fileSaved") }, toast: { message: t("toast.fileSaved") },
}, },
}; };
} catch (error: any) { } catch (error: any) {
abortController.abort();
if (error?.name !== "AbortError") { if (error?.name !== "AbortError") {
console.error(error); console.error(error);
} else { } else {
console.warn(error); console.warn(error);
} }
return { return { captureUpdate: CaptureUpdateAction.EVENTUALLY };
captureUpdate: CaptureUpdateAction.NEVER,
appState: {
toast: null,
},
};
} finally {
onExportInProgress = false;
} }
}, },
keyTest: (event) => keyTest: (event) =>
event.key.toLowerCase() === KEYS.S && event.key === KEYS.S && event.shiftKey && event[KEYS.CTRL_OR_CMD],
event.shiftKey &&
event[KEYS.CTRL_OR_CMD],
PanelComponent: ({ updateData }) => ( PanelComponent: ({ updateData }) => (
<ToolButton <ToolButton
type="button" type="button"
@@ -481,8 +300,7 @@ export const actionExportWithDarkMode = register<
name: "exportWithDarkMode", name: "exportWithDarkMode",
label: "imageExportDialog.label.darkMode", label: "imageExportDialog.label.darkMode",
trackEvent: { category: "export", action: "toggleTheme" }, trackEvent: { category: "export", action: "toggleTheme" },
perform: (_elements, appState, value, app) => { perform: (_elements, appState, value) => {
app.sessionExportThemeOverride = value ? THEME.DARK : THEME.LIGHT;
return { return {
appState: { ...appState, exportWithDarkMode: value }, appState: { ...appState, exportWithDarkMode: value },
captureUpdate: CaptureUpdateAction.EVENTUALLY, captureUpdate: CaptureUpdateAction.EVENTUALLY,
@@ -18,7 +18,7 @@ import { HistoryChangedEvent } from "../history";
import { useEmitter } from "../hooks/useEmitter"; import { useEmitter } from "../hooks/useEmitter";
import { t } from "../i18n"; import { t } from "../i18n";
import { useStylesPanelMode } from "../components/App"; import { useStylesPanelMode } from "..";
import type { History } from "../history"; import type { History } from "../history";
import type { AppClassProperties, AppState } from "../types"; import type { AppClassProperties, AppState } from "../types";
+4 -2
View File
@@ -33,6 +33,8 @@ import {
normalizeFile, normalizeFile,
} from "./data/blob"; } from "./data/blob";
import type { FileSystemHandle } from "./data/filesystem";
import type { BinaryFiles } from "./types"; import type { BinaryFiles } from "./types";
type ElementsClipboard = { type ElementsClipboard = {
@@ -367,7 +369,7 @@ type AllowedParsedDataTransferItem =
type: ValueOf<typeof IMAGE_MIME_TYPES>; type: ValueOf<typeof IMAGE_MIME_TYPES>;
kind: "file"; kind: "file";
file: File; file: File;
fileHandle: FileSystemFileHandle | null; fileHandle: FileSystemHandle | null;
} }
| { type: ValueOf<typeof STRING_MIME_TYPES>; kind: "string"; value: string }; | { type: ValueOf<typeof STRING_MIME_TYPES>; kind: "string"; value: string };
@@ -376,7 +378,7 @@ type ParsedDataTransferItem =
type: string; type: string;
kind: "file"; kind: "file";
file: File; file: File;
fileHandle: FileSystemFileHandle | null; fileHandle: FileSystemHandle | null;
} }
| { type: string; kind: "string"; value: string }; | { type: string; kind: "string"; value: string };
File diff suppressed because it is too large Load Diff
@@ -1,208 +0,0 @@
import type { AppState, UnsubscribeCallback } from "../types";
type StateChangeSelector =
| keyof AppState
| (keyof AppState)[]
| ((appState: AppState) => unknown);
type StateChangePredicateOptions = {
predicate: (appState: AppState) => boolean;
callback?: (appState: AppState) => void;
once?: boolean;
};
type StateChangeArg = StateChangeSelector | StateChangePredicateOptions;
type StateChangeListener = {
predicate: (appState: AppState, prevState: AppState) => boolean;
getValue: (appState: AppState) => unknown;
callback: (value: any, appState: AppState) => void;
once: boolean;
};
type NormalizedStateChange = {
predicate: StateChangeListener["predicate"];
getValue: StateChangeListener["getValue"];
callback?: StateChangeListener["callback"];
once: boolean;
matchesImmediately: boolean;
};
export type OnStateChange = {
<K extends keyof AppState>(
prop: K,
callback: (value: AppState[K], appState: AppState) => void,
opts?: { once: boolean },
): UnsubscribeCallback;
<K extends keyof AppState>(prop: K): Promise<AppState[K]>;
(
prop: (keyof AppState)[],
callback: (appState: AppState, appState2: AppState) => void,
opts?: { once: boolean },
): UnsubscribeCallback;
(prop: (keyof AppState)[]): Promise<AppState>;
<T>(
prop: (appState: AppState) => T,
callback: (value: T, appState: AppState) => void,
opts?: { once: boolean },
): UnsubscribeCallback;
<T>(prop: (appState: AppState) => T): Promise<T>;
(opts: {
predicate: (appState: AppState) => boolean;
callback: (appState: AppState) => void;
once?: boolean;
}): UnsubscribeCallback;
(opts: { predicate: (appState: AppState) => boolean }): Promise<AppState>;
(
selector: StateChangeSelector,
callback: (value: any, appState: AppState) => void,
): any;
};
export class AppStateObserver {
private listeners: StateChangeListener[] = [];
constructor(private readonly getState: () => AppState) {}
private isStateChangePredicateOptions(
propOrOpts: StateChangeArg,
): propOrOpts is StateChangePredicateOptions {
return (
typeof propOrOpts === "object" &&
!Array.isArray(propOrOpts) &&
"predicate" in propOrOpts
);
}
private subscribe(listener: StateChangeListener): UnsubscribeCallback {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(
(existingListener) => existingListener !== listener,
);
};
}
private normalize(
propOrOpts: StateChangeArg,
callback?: (value: any, appState: AppState) => void,
opts?: { once: boolean },
): NormalizedStateChange {
let predicate: StateChangeListener["predicate"];
let getValue: StateChangeListener["getValue"];
let normalizedCallback = callback;
let once = opts?.once ?? false;
let matchesImmediately = false;
if (this.isStateChangePredicateOptions(propOrOpts)) {
const {
predicate: predicateFn,
callback: callbackFromOpts,
once: onceFromOpts,
} = propOrOpts;
predicate = predicateFn;
getValue = (appState: AppState) => appState;
normalizedCallback = callbackFromOpts
? (_value: AppState, appState: AppState) => callbackFromOpts(appState)
: undefined;
once = onceFromOpts ?? false;
matchesImmediately = predicateFn(this.getState());
} else if (typeof propOrOpts === "function") {
const selector = propOrOpts;
predicate = (appState: AppState, prevState: AppState) =>
selector(appState) !== selector(prevState);
getValue = (appState: AppState) => selector(appState);
} else if (Array.isArray(propOrOpts)) {
const keys = propOrOpts;
predicate = (appState: AppState, prevState: AppState) =>
keys.some((key) => appState[key] !== prevState[key]);
getValue = (appState: AppState) => appState;
} else {
const key = propOrOpts;
predicate = (appState: AppState, prevState: AppState) =>
appState[key] !== prevState[key];
getValue = (appState: AppState) => appState[key];
}
return {
predicate,
getValue,
callback: normalizedCallback,
once,
matchesImmediately,
};
}
public onStateChange: OnStateChange = ((
propOrOpts: StateChangeArg,
callback?: any,
opts?: { once: boolean },
) => {
const {
predicate,
getValue,
callback: stateChangeCallback,
once,
matchesImmediately,
} = this.normalize(propOrOpts, callback, opts);
if (stateChangeCallback) {
if (matchesImmediately) {
queueMicrotask(() => {
const state = this.getState();
stateChangeCallback(getValue(state), state);
});
if (once) {
return () => {};
}
}
return this.subscribe({
predicate,
getValue,
callback: stateChangeCallback,
once,
});
}
if (matchesImmediately) {
return Promise.resolve(getValue(this.getState()));
}
return new Promise<any>((resolve) => {
this.subscribe({
predicate,
getValue,
callback: (value) => resolve(value),
once: true,
});
});
}) as OnStateChange;
public flush(prevState: AppState) {
if (!this.listeners.length) {
return;
}
const state = this.getState();
const listenersToKeep: StateChangeListener[] = [];
for (const listener of this.listeners) {
if (listener.predicate(state, prevState)) {
listener.callback(listener.getValue(state), state);
if (!listener.once) {
listenersToKeep.push(listener);
}
} else {
listenersToKeep.push(listener);
}
}
this.listeners = listenersToKeep;
}
public clear() {
this.listeners = [];
}
}
@@ -10,10 +10,11 @@ import {
isWritableElement, isWritableElement,
} from "@excalidraw/common"; } from "@excalidraw/common";
import type { MarkRequired } from "@excalidraw/common/utility-types"; import { actionToggleShapeSwitch } from "@excalidraw/excalidraw/actions/actionToggleShapeSwitch";
import { actionToggleShapeSwitch } from "../../actions/actionToggleShapeSwitch"; import { getShortcutKey } from "@excalidraw/excalidraw/shortcut";
import { getShortcutKey } from "../../shortcut";
import type { MarkRequired } from "@excalidraw/common/utility-types";
import { import {
actionClearCanvas, actionClearCanvas,
@@ -1,9 +1,7 @@
import { type ReactNode, useEffect, useMemo, useRef, useState } from "react"; import { type ReactNode, useEffect, useMemo, useRef, useState } from "react";
import { import {
bumpVersion,
getLinearElementSubType, getLinearElementSubType,
mutateElement,
updateElbowArrowPoints, updateElbowArrowPoints,
} from "@excalidraw/element"; } from "@excalidraw/element";
@@ -39,8 +37,6 @@ import {
isProdEnv, isProdEnv,
mapFind, mapFind,
reduceToCommonValue, reduceToCommonValue,
ROUNDNESS,
sceneCoordsToViewportCoords,
updateActiveTool, updateActiveTool,
} from "@excalidraw/common"; } from "@excalidraw/common";
@@ -75,6 +71,12 @@ import type {
import type { Scene } from "@excalidraw/element"; import type { Scene } from "@excalidraw/element";
import {
bumpVersion,
mutateElement,
ROUNDNESS,
sceneCoordsToViewportCoords,
} from "..";
import { trackEvent } from "../analytics"; import { trackEvent } from "../analytics";
import { atom } from "../editor-jotai"; import { atom } from "../editor-jotai";
@@ -59,7 +59,6 @@ type ImageExportModalProps = {
actionManager: ActionManager; actionManager: ActionManager;
onExportImage: AppClassProperties["onExportImage"]; onExportImage: AppClassProperties["onExportImage"];
name: string; name: string;
exportWithDarkMode: boolean;
}; };
const ImageExportModal = ({ const ImageExportModal = ({
@@ -69,7 +68,6 @@ const ImageExportModal = ({
actionManager, actionManager,
onExportImage, onExportImage,
name, name,
exportWithDarkMode,
}: ImageExportModalProps) => { }: ImageExportModalProps) => {
const hasSelection = isSomeElementSelected( const hasSelection = isSomeElementSelected(
elementsSnapshot, elementsSnapshot,
@@ -81,13 +79,15 @@ const ImageExportModal = ({
const [exportWithBackground, setExportWithBackground] = useState( const [exportWithBackground, setExportWithBackground] = useState(
appStateSnapshot.exportBackground, appStateSnapshot.exportBackground,
); );
const [exportDarkMode, setExportDarkMode] = useState(
appStateSnapshot.exportWithDarkMode,
);
const [embedScene, setEmbedScene] = useState( const [embedScene, setEmbedScene] = useState(
appStateSnapshot.exportEmbedScene, appStateSnapshot.exportEmbedScene,
); );
const [exportScale, setExportScale] = useState(appStateSnapshot.exportScale); const [exportScale, setExportScale] = useState(appStateSnapshot.exportScale);
const previewRef = useRef<HTMLDivElement>(null); const previewRef = useRef<HTMLDivElement>(null);
const previewRenderRequestIdRef = useRef(0);
const [renderError, setRenderError] = useState<Error | null>(null); const [renderError, setRenderError] = useState<Error | null>(null);
const { onCopy, copyStatus, resetCopyStatus } = useCopyStatus(); const { onCopy, copyStatus, resetCopyStatus } = useCopyStatus();
@@ -99,7 +99,7 @@ const ImageExportModal = ({
}, [ }, [
projectName, projectName,
exportWithBackground, exportWithBackground,
exportWithDarkMode, exportDarkMode,
exportScale, exportScale,
embedScene, embedScene,
resetCopyStatus, resetCopyStatus,
@@ -122,18 +122,13 @@ const ImageExportModal = ({
return; return;
} }
const requestId = ++previewRenderRequestIdRef.current;
const isStaleRequest = () => {
return requestId !== previewRenderRequestIdRef.current;
};
exportToCanvas({ exportToCanvas({
elements: exportedElements, elements: exportedElements,
appState: { appState: {
...appStateSnapshot, ...appStateSnapshot,
name: projectName, name: projectName,
exportBackground: exportWithBackground, exportBackground: exportWithBackground,
exportWithDarkMode, exportWithDarkMode: exportDarkMode,
exportScale, exportScale,
exportEmbedScene: embedScene, exportEmbedScene: embedScene,
}, },
@@ -142,41 +137,25 @@ const ImageExportModal = ({
maxWidthOrHeight: Math.max(maxWidth, maxHeight), maxWidthOrHeight: Math.max(maxWidth, maxHeight),
exportingFrame, exportingFrame,
}) })
.then(async (canvas) => { .then((canvas) => {
if (isStaleRequest()) {
return;
}
// If converting to blob fails, there's some problem that will likely
// prevent preview and export (e.g. canvas too big).
try {
await canvasToBlob(canvas);
} catch (error: any) {
if (error.name === "CANVAS_POSSIBLY_TOO_BIG") {
throw new Error(t("canvasError.canvasTooBig"));
}
throw error;
}
if (isStaleRequest()) {
return;
}
setRenderError(null); setRenderError(null);
previewNode.replaceChildren(canvas); // if converting to blob fails, there's some problem that will
// likely prevent preview and export (e.g. canvas too big)
return canvasToBlob(canvas)
.then(() => {
previewNode.replaceChildren(canvas);
})
.catch((e) => {
if (e.name === "CANVAS_POSSIBLY_TOO_BIG") {
throw new Error(t("canvasError.canvasTooBig"));
}
throw e;
});
}) })
.catch((error) => { .catch((error) => {
if (isStaleRequest()) {
return;
}
console.error(error); console.error(error);
setRenderError(error); setRenderError(error);
}); });
return () => {
previewRenderRequestIdRef.current += 1;
};
}, [ }, [
appStateSnapshot, appStateSnapshot,
files, files,
@@ -184,7 +163,7 @@ const ImageExportModal = ({
exportingFrame, exportingFrame,
projectName, projectName,
exportWithBackground, exportWithBackground,
exportWithDarkMode, exportDarkMode,
exportScale, exportScale,
embedScene, embedScene,
]); ]);
@@ -254,8 +233,9 @@ const ImageExportModal = ({
> >
<Switch <Switch
name="exportDarkModeSwitch" name="exportDarkModeSwitch"
checked={exportWithDarkMode} checked={exportDarkMode}
onChange={(checked) => { onChange={(checked) => {
setExportDarkMode(checked);
actionManager.executeAction( actionManager.executeAction(
actionExportWithDarkMode, actionExportWithDarkMode,
"ui", "ui",
@@ -419,7 +399,6 @@ export const ImageExportDialog = ({
actionManager={actionManager} actionManager={actionManager}
onExportImage={onExportImage} onExportImage={onExportImage}
name={name} name={name}
exportWithDarkMode={appState.exportWithDarkMode}
/> />
</Dialog> </Dialog>
); );
+12 -25
View File
@@ -60,7 +60,6 @@ import { ImageExportDialog } from "./ImageExportDialog";
import { Island } from "./Island"; import { Island } from "./Island";
import { JSONExportDialog } from "./JSONExportDialog"; import { JSONExportDialog } from "./JSONExportDialog";
import { LaserPointerButton } from "./LaserPointerButton"; import { LaserPointerButton } from "./LaserPointerButton";
import { Toast } from "./Toast";
import "./LayerUI.scss"; import "./LayerUI.scss";
import "./Toolbar.scss"; import "./Toolbar.scss";
@@ -606,30 +605,18 @@ const LayerUI = ({
showExitZenModeBtn={showExitZenModeBtn} showExitZenModeBtn={showExitZenModeBtn}
renderWelcomeScreen={renderWelcomeScreen} renderWelcomeScreen={renderWelcomeScreen}
/> />
{(appState.toast || appState.scrolledOutside) && ( {appState.scrolledOutside && (
<div className="floating-status-stack"> <button
{appState.toast && ( type="button"
<Toast className="scroll-back-to-content"
message={appState.toast.message} onClick={() => {
onClose={() => setAppState({ toast: null })} setAppState((appState) => ({
duration={appState.toast.duration} ...calculateScrollCenter(elements, appState),
closable={appState.toast.closable} }));
/> }}
)} >
{!appState.toast && appState.scrolledOutside && ( {t("buttons.scrollBackToContent")}
<button </button>
type="button"
className="scroll-back-to-content"
onClick={() => {
setAppState((appState) => ({
...calculateScrollCenter(elements, appState),
}));
}}
>
{t("buttons.scrollBackToContent")}
</button>
)}
</div>
)} )}
</div> </div>
{renderSidebars()} {renderSidebars()}
@@ -472,9 +472,9 @@ export const MobileToolBar = ({
onSelect={() => app.onMagicframeToolSelect()} onSelect={() => app.onMagicframeToolSelect()}
icon={MagicIcon} icon={MagicIcon}
data-testid="toolbar-magicframe" data-testid="toolbar-magicframe"
badge={<DropdownMenu.Item.Badge>AI</DropdownMenu.Item.Badge>}
> >
{t("toolBar.magicframe")} {t("toolBar.magicframe")}
<DropdownMenu.Item.Badge>AI</DropdownMenu.Item.Badge>
</DropdownMenu.Item> </DropdownMenu.Item>
</> </>
)} )}
@@ -11,7 +11,7 @@ import { rateLimitsAtom } from "../TTDContext";
import { ChatHistoryMenu } from "./ChatHistoryMenu"; import { ChatHistoryMenu } from "./ChatHistoryMenu";
import { ChatInterface } from "./ChatInterface"; import { ChatInterface } from ".";
import type { TTDPanelAction } from "../TTDDialogPanel"; import type { TTDPanelAction } from "../TTDDialogPanel";
@@ -1,239 +0,0 @@
import { useEffect, useRef } from "react";
import {
Decoration,
EditorView,
keymap,
lineNumbers,
placeholder as cmPlaceholder,
drawSelection,
} from "@codemirror/view";
import { Compartment, EditorState, type Extension } from "@codemirror/state";
import {
defaultKeymap,
history,
historyKeymap,
redo,
} from "@codemirror/commands";
import { syntaxHighlighting, HighlightStyle } from "@codemirror/language";
import { tags } from "@lezer/highlight";
import type { Theme } from "@excalidraw/element/types";
import { mermaidLite } from "./mermaid-lang-lite";
export interface CodeMirrorEditorProps {
value: string;
onChange: (value: string) => void;
onKeyboardSubmit?: () => void;
placeholder?: string;
theme: Theme;
errorLine?: number | null;
}
// ---- Dark theme ----
const darkTheme = EditorView.theme(
{
"&": {
backgroundColor: "#1e1e1e",
color: "#d4d4d4",
},
".cm-content": { caretColor: "#fff" },
".cm-cursor": { borderLeftColor: "#fff" },
".cm-gutters": {
backgroundColor: "#1e1e1e",
color: "#858585",
border: "none",
},
".cm-activeLineGutter": { backgroundColor: "#2a2a2a" },
".cm-activeLine": { backgroundColor: "#2a2a2a" },
".cm-errorLine": { backgroundColor: "rgba(255, 0, 0, 0.15)" },
},
{ dark: true },
);
const darkHighlight = HighlightStyle.define([
{ tag: tags.keyword, color: "#569cd6" },
{ tag: tags.string, color: "#ce9178" },
{ tag: tags.comment, color: "#6a9955" },
{ tag: tags.number, color: "#b5cea8" },
{ tag: tags.operator, color: "#d4d4d4" },
{ tag: tags.punctuation, color: "#d4d4d4" },
{ tag: tags.variableName, color: "#9cdcfe" },
{ tag: tags.bracket, color: "#ffd700" },
]);
// ---- Light theme ----
const lightTheme = EditorView.theme({
"&": {
backgroundColor: "#ffffff",
color: "#1e1e1e",
},
".cm-content": { caretColor: "#000" },
".cm-cursor": { borderLeftColor: "#000" },
".cm-gutters": {
backgroundColor: "#fff",
color: "#999",
border: "none",
},
".cm-activeLineGutter": { backgroundColor: "#e8e8e8" },
".cm-activeLine": { backgroundColor: "#e8e8e8" },
".cm-errorLine": { backgroundColor: "rgba(255, 0, 0, 0.1)" },
});
const lightHighlight = HighlightStyle.define([
{ tag: tags.keyword, color: "#0000ff" },
{ tag: tags.string, color: "#a31515" },
{ tag: tags.comment, color: "#008000" },
{ tag: tags.number, color: "#098658" },
{ tag: tags.operator, color: "#1e1e1e" },
{ tag: tags.punctuation, color: "#1e1e1e" },
{ tag: tags.variableName, color: "#001080" },
{ tag: tags.bracket, color: "#af00db" },
]);
// ---- Error line decoration ----
const errorLineDeco = Decoration.line({ class: "cm-errorLine" });
const getErrorLineExtension = (
errorLine: number | null | undefined,
doc: { line(n: number): { from: number }; lines: number },
): Extension => {
if (!errorLine || errorLine < 1 || errorLine > doc.lines) {
return EditorView.decorations.of(Decoration.none);
}
const line = doc.line(errorLine);
return EditorView.decorations.of(
Decoration.set([errorLineDeco.range(line.from)]),
);
};
// ---- Helpers ----
const getThemeExtensions = (theme: Theme) => {
if (theme === "dark") {
return [darkTheme, syntaxHighlighting(darkHighlight)];
}
return [lightTheme, syntaxHighlighting(lightHighlight)];
};
const CodeMirrorEditor = ({
value,
onChange,
onKeyboardSubmit,
placeholder,
theme,
errorLine,
}: CodeMirrorEditorProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const viewRef = useRef<EditorView | null>(null);
const onChangeRef = useRef(onChange);
const onKeyboardSubmitRef = useRef(onKeyboardSubmit);
const themeCompartmentRef = useRef(new Compartment());
const errorLineCompartmentRef = useRef(new Compartment());
onChangeRef.current = onChange;
onKeyboardSubmitRef.current = onKeyboardSubmit;
useEffect(() => {
if (!containerRef.current) {
return;
}
const themeCompartment = themeCompartmentRef.current;
const view = new EditorView({
state: EditorState.create({
doc: value,
extensions: [
keymap.of([
{
key: "Mod-Enter",
run: () => {
onKeyboardSubmitRef.current?.();
return true;
},
},
// historyKeymap binds Mod-Shift-z only on Mac; add it for all platforms
{ key: "Mod-Shift-z", run: redo, preventDefault: true },
]),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
onChangeRef.current(update.state.doc.toString());
}
}),
history(),
keymap.of([...defaultKeymap, ...historyKeymap]),
lineNumbers(),
EditorView.lineWrapping,
themeCompartment.of(getThemeExtensions(theme)),
errorLineCompartmentRef.current.of([]),
mermaidLite(),
drawSelection({ drawRangeCursor: true }),
...(placeholder ? [cmPlaceholder(placeholder)] : []),
],
}),
parent: containerRef.current,
});
viewRef.current = view;
view.focus();
return () => {
view.destroy();
viewRef.current = null;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Swap theme dynamically via compartment
useEffect(() => {
const view = viewRef.current;
if (!view) {
return;
}
view.dispatch({
effects: themeCompartmentRef.current.reconfigure(
getThemeExtensions(theme),
),
});
}, [theme]);
// Update error line highlight
useEffect(() => {
const view = viewRef.current;
if (!view) {
return;
}
view.dispatch({
effects: errorLineCompartmentRef.current.reconfigure(
getErrorLineExtension(errorLine, view.state.doc),
),
});
}, [errorLine]);
// Sync external value changes into EditorView
useEffect(() => {
const view = viewRef.current;
if (!view) {
return;
}
const currentDoc = view.state.doc.toString();
if (value !== currentDoc) {
view.dispatch({
changes: { from: 0, to: currentDoc.length, insert: value },
});
}
}, [value]);
return (
<div
ref={containerRef}
className="ttd-dialog-input ttd-dialog-input--codemirror"
/>
);
};
export default CodeMirrorEditor;
@@ -17,11 +17,6 @@ import { TTDDialogOutput } from "./TTDDialogOutput";
import { TTDDialogPanel } from "./TTDDialogPanel"; import { TTDDialogPanel } from "./TTDDialogPanel";
import { TTDDialogPanels } from "./TTDDialogPanels"; import { TTDDialogPanels } from "./TTDDialogPanels";
import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut"; import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut";
import {
getMermaidErrorLineNumber,
isMermaidAutoFixableError,
} from "./utils/mermaidError";
import { getMermaidAutoFixCandidates } from "./utils/mermaidAutoFix";
import { import {
convertMermaidToExcalidraw, convertMermaidToExcalidraw,
insertToEditor, insertToEditor,
@@ -38,27 +33,6 @@ const MERMAID_EXAMPLE =
"flowchart TD\n A[Christmas] -->|Get money| B(Go shopping)\n B --> C{Let me think}\n C -->|One| D[Laptop]\n C -->|Two| E[iPhone]\n C -->|Three| F[Car]"; "flowchart TD\n A[Christmas] -->|Get money| B(Go shopping)\n B --> C{Let me think}\n C -->|One| D[Laptop]\n C -->|Two| E[iPhone]\n C -->|Three| F[Car]";
const debouncedSaveMermaidDefinition = debounce(saveMermaidDataToStorage, 300); const debouncedSaveMermaidDefinition = debounce(saveMermaidDataToStorage, 300);
const AUTO_FIX_DEBOUNCE_MS = 500;
const AUTO_FIX_MAX_DEPTH = 4;
const AUTO_FIX_MAX_CANDIDATES = 30;
const getErrorMessage = (error: unknown): string => {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string") {
return error;
}
if (
error &&
typeof error === "object" &&
"message" in error &&
typeof (error as { message?: unknown }).message === "string"
) {
return (error as { message: string }).message;
}
return "";
};
const MermaidToExcalidraw = ({ const MermaidToExcalidraw = ({
mermaidToExcalidrawLib, mermaidToExcalidrawLib,
@@ -72,16 +46,8 @@ const MermaidToExcalidraw = ({
EditorLocalStorage.get<string>(EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW) || EditorLocalStorage.get<string>(EDITOR_LS_KEYS.MERMAID_TO_EXCALIDRAW) ||
MERMAID_EXAMPLE, MERMAID_EXAMPLE,
); );
const deferredText = useDeferredValue(text); const deferredText = useDeferredValue(text.trim());
const [error, setError] = useState<Error | null>(null); const [error, setError] = useState<Error | null>(null);
const [autoFixCandidate, setAutoFixCandidate] = useState<string | null>(null);
const errorLine = (() => {
if (!error?.message) {
return null;
}
return getMermaidErrorLineNumber(error.message, deferredText);
})();
const canvasRef = useRef<HTMLDivElement>(null); const canvasRef = useRef<HTMLDivElement>(null);
const data = useRef<{ const data = useRef<{
@@ -95,7 +61,7 @@ const MermaidToExcalidraw = ({
useEffect(() => { useEffect(() => {
const doRender = async () => { const doRender = async () => {
try { try {
if (!deferredText.trim()) { if (!deferredText) {
resetPreview({ canvasRef, setError }); resetPreview({ canvasRef, setError });
return; return;
} }
@@ -132,88 +98,6 @@ const MermaidToExcalidraw = ({
[], [],
); );
useEffect(() => {
const errorMessage = error?.message ?? "";
const sourceText = deferredText;
const shouldTryAutoFix =
isActive &&
isMermaidAutoFixableError(errorMessage) &&
!!sourceText.trim() &&
mermaidToExcalidrawLib.loaded;
if (!shouldTryAutoFix) {
setAutoFixCandidate(null);
return;
}
const candidates = getMermaidAutoFixCandidates(sourceText, errorMessage);
if (!candidates.length) {
setAutoFixCandidate(null);
return;
}
let cancelled = false;
const timer = setTimeout(async () => {
try {
const api = await mermaidToExcalidrawLib.api;
const seen = new Set<string>([sourceText]);
const queue = candidates.map((candidate) => ({
text: candidate,
depth: 1,
}));
let triedCandidates = 0;
while (queue.length > 0 && triedCandidates < AUTO_FIX_MAX_CANDIDATES) {
const current = queue.shift();
if (!current || seen.has(current.text)) {
continue;
}
seen.add(current.text);
triedCandidates += 1;
try {
await api.parseMermaidToExcalidraw(current.text);
if (!cancelled) {
setAutoFixCandidate(current.text);
}
return;
} catch (candidateError) {
if (current.depth >= AUTO_FIX_MAX_DEPTH) {
continue;
}
const nextErrorMessage = getErrorMessage(candidateError);
if (!nextErrorMessage) {
continue;
}
const nextCandidates = getMermaidAutoFixCandidates(
current.text,
nextErrorMessage,
);
for (const nextCandidate of nextCandidates) {
if (!seen.has(nextCandidate)) {
queue.push({
text: nextCandidate,
depth: current.depth + 1,
});
}
}
}
}
} catch {
// ignore auto-fix probe errors
}
if (!cancelled) {
setAutoFixCandidate(null);
}
}, AUTO_FIX_DEBOUNCE_MS);
return () => {
cancelled = true;
clearTimeout(timer);
};
}, [deferredText, error?.message, isActive, mermaidToExcalidrawLib]);
const onInsertToEditor = () => { const onInsertToEditor = () => {
insertToEditor({ insertToEditor({
app, app,
@@ -223,13 +107,6 @@ const MermaidToExcalidraw = ({
}); });
}; };
const onApplyAutoFix = () => {
if (!autoFixCandidate) {
return;
}
setText(autoFixCandidate);
};
return ( return (
<> <>
<div className="ttd-dialog-desc"> <div className="ttd-dialog-desc">
@@ -253,8 +130,7 @@ const MermaidToExcalidraw = ({
<TTDDialogInput <TTDDialogInput
input={text} input={text}
placeholder={t("mermaid.inputPlaceholder")} placeholder={t("mermaid.inputPlaceholder")}
onChange={(value) => setText(value)} onChange={(event) => setText(event.target.value)}
errorLine={errorLine}
onKeyboardSubmit={() => { onKeyboardSubmit={() => {
onInsertToEditor(); onInsertToEditor();
}} }}
@@ -277,9 +153,6 @@ const MermaidToExcalidraw = ({
canvasRef={canvasRef} canvasRef={canvasRef}
loaded={mermaidToExcalidrawLib.loaded} loaded={mermaidToExcalidrawLib.loaded}
error={error} error={error}
sourceText={text}
autoFixAvailable={!!autoFixCandidate}
onApplyAutoFix={onApplyAutoFix}
/> />
</TTDDialogPanel> </TTDDialogPanel>
</TTDDialogPanels> </TTDDialogPanels>
@@ -219,49 +219,6 @@ $fullScreenModalBreakpoint: 600px;
} }
} }
.ttd-dialog-input--loading {
display: flex;
align-items: center;
justify-content: center;
}
.ttd-dialog-input--codemirror {
padding: 0;
overflow: hidden;
// Override height:100% from .ttd-dialog-input use flex sizing
// so the editor fills remaining space without overflowing the panel
height: 0;
flex: 1 1 0;
min-height: 0;
.cm-editor {
height: 100%;
font-family: monospace;
&.cm-focused {
outline: none;
}
}
.cm-scroller {
padding: 0.85rem 0;
overflow: auto;
}
.cm-gutters {
padding-left: 0.25rem;
}
.cm-content {
padding: 0;
}
.cm-placeholder {
color: var(--color-gray-40);
font-style: italic;
}
}
.ttd-dialog-output-wrapper { .ttd-dialog-output-wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -374,55 +331,14 @@ $fullScreenModalBreakpoint: 600px;
margin-top: 0.25rem; margin-top: 0.25rem;
} }
.ttd-dialog-output-error-summary {
width: 100%;
max-width: 640px;
color: var(--color-gray-50);
font-size: 0.9rem;
text-align: left;
&__headline {
font-weight: 600;
color: var(--color-gray-60);
}
&__label {
margin-top: 0.35rem;
font-weight: 500;
}
&__causes {
margin: 0.35rem 0 0;
padding-left: 2rem;
}
}
.ttd-dialog-output-error-message { .ttd-dialog-output-error-message {
text-align: left; text-align: left;
font-weight: 400; font-weight: 400;
color: var(--color-gray-50); color: var(--color-gray-50);
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
max-width: 640px; max-width: 100%;
width: 100%;
font-family: monospace; font-family: monospace;
&__caret {
color: var(--color-danger);
}
}
.ttd-dialog-output-error-autofix-slot {
align-self: flex-start;
margin-top: 0.35rem;
min-height: 2.5rem;
display: flex;
align-items: flex-start;
}
.ttd-dialog-output-error-autofix {
margin-top: 0;
white-space: nowrap;
} }
} }
@@ -1,84 +1,28 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef } from "react";
import { EVENT, KEYS } from "@excalidraw/common"; import { EVENT, KEYS } from "@excalidraw/common";
import Spinner from "../Spinner"; import type { ChangeEventHandler } from "react";
import { useUIAppState } from "../../context/ui-appState";
import type { ComponentType } from "react";
import type { CodeMirrorEditorProps } from "./CodeMirrorEditor";
interface TTDDialogInputProps { interface TTDDialogInputProps {
input: string; input: string;
placeholder: string; placeholder: string;
onChange: (value: string) => void; onChange: ChangeEventHandler<HTMLTextAreaElement>;
onKeyboardSubmit?: () => void; onKeyboardSubmit?: () => void;
errorLine?: number | null;
} }
type EditorState =
| { type: "loading" }
| { type: "ready"; component: ComponentType<CodeMirrorEditorProps> }
| { type: "fallback" };
const SPINNER_DELAY_MS = 300;
export const TTDDialogInput = ({ export const TTDDialogInput = ({
input, input,
placeholder, placeholder,
onChange, onChange,
onKeyboardSubmit, onKeyboardSubmit,
errorLine,
}: TTDDialogInputProps) => { }: TTDDialogInputProps) => {
const ref = useRef<HTMLTextAreaElement>(null); const ref = useRef<HTMLTextAreaElement>(null);
const callbackRef = useRef(onKeyboardSubmit); const callbackRef = useRef(onKeyboardSubmit);
callbackRef.current = onKeyboardSubmit; callbackRef.current = onKeyboardSubmit;
const [editorState, setEditorState] = useState<EditorState>({
type: "loading",
});
const [showSpinner, setShowSpinner] = useState(false);
const { theme } = useUIAppState();
// Lazy-load CodeMirror editor
useEffect(() => { useEffect(() => {
let cancelled = false;
const spinnerTimer = setTimeout(() => {
if (!cancelled) {
setShowSpinner(true);
}
}, SPINNER_DELAY_MS);
import("./CodeMirrorEditor")
.then((mod) => {
if (!cancelled) {
setEditorState({ type: "ready", component: mod.default });
}
})
.catch(() => {
if (!cancelled) {
setEditorState({ type: "fallback" });
}
})
.finally(() => {
clearTimeout(spinnerTimer);
});
return () => {
cancelled = true;
clearTimeout(spinnerTimer);
};
}, []);
// Keyboard shortcut + focus for textarea fallback
useEffect(() => {
if (editorState.type !== "fallback") {
return;
}
if (!callbackRef.current) { if (!callbackRef.current) {
return; return;
} }
@@ -96,42 +40,15 @@ export const TTDDialogInput = ({
textarea.removeEventListener(EVENT.KEYDOWN, handleKeyDown); textarea.removeEventListener(EVENT.KEYDOWN, handleKeyDown);
}; };
} }
}, [editorState.type]); }, []);
if (editorState.type === "ready") { return (
const CodeMirrorEditor = editorState.component; <textarea
return ( className="ttd-dialog-input"
<CodeMirrorEditor onChange={onChange}
value={input} value={input}
onChange={onChange} placeholder={placeholder}
onKeyboardSubmit={onKeyboardSubmit} ref={ref}
placeholder={placeholder} />
theme={theme} );
errorLine={errorLine}
/>
);
}
if (editorState.type === "fallback") {
return (
<textarea
className="ttd-dialog-input"
onChange={(e) => onChange(e.target.value)}
value={input}
placeholder={placeholder}
ref={ref}
/>
);
}
// Loading state
if (showSpinner) {
return (
<div className="ttd-dialog-input ttd-dialog-input--loading">
<Spinner />
</div>
);
}
return null;
}; };
@@ -1,24 +1,14 @@
import clsx from "clsx"; import clsx from "clsx";
import { Button } from "../Button";
import Spinner from "../Spinner"; import Spinner from "../Spinner";
import { t } from "../../i18n"; import { t } from "../../i18n";
import { alertTriangleIcon } from "../icons"; import { alertTriangleIcon } from "../icons";
import {
formatMermaidParseErrorMessage,
getMermaidSyntaxErrorGuidance,
isMermaidCaretLine,
} from "./utils/mermaidError";
interface TTDDialogOutputProps { interface TTDDialogOutputProps {
error: Error | null; error: Error | null;
canvasRef: React.RefObject<HTMLDivElement | null>; canvasRef: React.RefObject<HTMLDivElement | null>;
loaded: boolean; loaded: boolean;
hideErrorDetails?: boolean; hideErrorDetails?: boolean;
sourceText?: string;
autoFixAvailable?: boolean;
onApplyAutoFix?: () => void;
} }
export const TTDDialogOutput = ({ export const TTDDialogOutput = ({
@@ -26,24 +16,7 @@ export const TTDDialogOutput = ({
canvasRef, canvasRef,
loaded, loaded,
hideErrorDetails, hideErrorDetails,
sourceText,
autoFixAvailable,
onApplyAutoFix,
}: TTDDialogOutputProps) => { }: TTDDialogOutputProps) => {
const errorMessage = error
? hideErrorDetails
? t("chat.errors.mermaidParseError")
: formatMermaidParseErrorMessage(error.message)
: null;
const syntaxGuidance =
error && !hideErrorDetails
? getMermaidSyntaxErrorGuidance(error.message, sourceText)
: null;
const showAutoFixButton =
!!autoFixAvailable && !!onApplyAutoFix && !hideErrorDetails;
const errorMessageLines = errorMessage?.split(/\r?\n/) ?? [];
return ( return (
<div <div
className={`ttd-dialog-output-wrapper ${ className={`ttd-dialog-output-wrapper ${
@@ -60,48 +33,14 @@ export const TTDDialogOutput = ({
<div className="ttd-dialog-output-error-icon"> <div className="ttd-dialog-output-error-icon">
{alertTriangleIcon} {alertTriangleIcon}
</div> </div>
{syntaxGuidance && ( <div className="ttd-dialog-output-error-title">
<div className="ttd-dialog-output-error-summary"> {t("ttd.error")}
<div className="ttd-dialog-output-error-summary__headline"> </div>
{syntaxGuidance.summary} <div className="ttd-dialog-output-error-message">
</div> {hideErrorDetails
<div className="ttd-dialog-output-error-summary__label"> ? t("chat.errors.mermaidParseError")
Likely causes: : error.message}
</div>
<ul className="ttd-dialog-output-error-summary__causes">
{syntaxGuidance.likelyCauses.map((cause) => (
<li key={cause}>{cause}</li>
))}
</ul>
</div>
)}
<div className="ttd-dialog-output-error-message">
{errorMessageLines.map((line, index) => (
<span
key={`error-line-${index}`}
className={
isMermaidCaretLine(line)
? "ttd-dialog-output-error-message__caret"
: undefined
}
>
{line}
{index < errorMessageLines.length - 1 ? "\n" : ""}
</span>
))}
</div> </div>
{!hideErrorDetails && (
<div className="ttd-dialog-output-error-autofix-slot">
{showAutoFixButton ? (
<Button
className="ttd-dialog-panel-button ttd-dialog-output-error-autofix"
onSelect={onApplyAutoFix}
>
{t("mermaid.autoFixAvailable")}
</Button>
) : null}
</div>
)}
</div> </div>
</div> </div>
)} )}
@@ -1,4 +1,4 @@
import { getShortcutKey } from "../../shortcut"; import { getShortcutKey } from "@excalidraw/excalidraw/shortcut";
export const TTDDialogSubmitShortcut = () => { export const TTDDialogSubmitShortcut = () => {
return ( return (
@@ -1,89 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { convertMermaidToExcalidraw } from "./common";
type ConvertMermaidArgs = Parameters<typeof convertMermaidToExcalidraw>[0];
type ParseMermaidToExcalidraw = Awaited<
ConvertMermaidArgs["mermaidToExcalidrawLib"]["api"]
>["parseMermaidToExcalidraw"];
const createConvertArgs = (
mermaidDefinition: string,
parseMermaidToExcalidraw: ParseMermaidToExcalidraw,
): ConvertMermaidArgs => {
const parent = document.createElement("div");
const canvas = document.createElement("div");
parent.appendChild(canvas);
return {
canvasRef: { current: canvas },
mermaidToExcalidrawLib: {
loaded: true,
api: Promise.resolve({ parseMermaidToExcalidraw }),
},
mermaidDefinition,
setError: vi.fn(),
data: {
current: {
elements: [],
files: null,
},
},
theme: "light",
};
};
describe("convertMermaidToExcalidraw", () => {
it("returns the original parse error when quote-normalized fallback also fails", async () => {
const originalError = new Error("Parse error on line 9: ...");
const fallbackError = new Error("Parse error on line 6: ...");
const parseMermaidToExcalidraw = vi
.fn<ParseMermaidToExcalidraw>()
.mockRejectedValueOnce(originalError)
.mockRejectedValueOnce(fallbackError);
const mermaidDefinition =
'graph TD\nA["One"]\nB["Two"]x\nC["Three"]\nD["Four"]';
const result = await convertMermaidToExcalidraw(
createConvertArgs(mermaidDefinition, parseMermaidToExcalidraw),
);
expect(parseMermaidToExcalidraw).toHaveBeenCalledTimes(2);
expect(parseMermaidToExcalidraw).toHaveBeenNthCalledWith(
1,
mermaidDefinition,
);
expect(parseMermaidToExcalidraw).toHaveBeenNthCalledWith(
2,
mermaidDefinition.replace(/"/g, "'"),
);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toBe(originalError);
}
});
it("does not retry quote normalization when the input has no double quotes", async () => {
const originalError = new Error("Parse error on line 9: ...");
const parseMermaidToExcalidraw = vi
.fn<ParseMermaidToExcalidraw>()
.mockRejectedValueOnce(originalError);
const mermaidDefinition = "graph TD\nA[One]\nB[Two]x";
const result = await convertMermaidToExcalidraw(
createConvertArgs(mermaidDefinition, parseMermaidToExcalidraw),
);
expect(parseMermaidToExcalidraw).toHaveBeenCalledTimes(1);
expect(parseMermaidToExcalidraw).toHaveBeenCalledWith(mermaidDefinition);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toBe(originalError);
}
});
});
@@ -1,12 +1,4 @@
import { import { DEFAULT_EXPORT_PADDING, EDITOR_LS_KEYS } from "@excalidraw/common";
DEFAULT_EXPORT_PADDING,
EDITOR_LS_KEYS,
THEME,
} from "@excalidraw/common";
import { convertToExcalidrawElements } from "@excalidraw/element";
import { exportToCanvas } from "@excalidraw/utils";
import type { import type {
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
@@ -14,6 +6,11 @@ import type {
} from "@excalidraw/element/types"; } from "@excalidraw/element/types";
import { EditorLocalStorage } from "../../data/EditorLocalStorage"; import { EditorLocalStorage } from "../../data/EditorLocalStorage";
import {
convertToExcalidrawElements,
exportToCanvas,
THEME,
} from "../../index";
import type { MermaidToExcalidrawLibProps } from "./types"; import type { MermaidToExcalidrawLibProps } from "./types";
@@ -75,23 +72,15 @@ export const convertMermaidToExcalidraw = async ({
const api = await mermaidToExcalidrawLib.api; const api = await mermaidToExcalidrawLib.api;
try { try {
ret = await api.parseMermaidToExcalidraw(mermaidDefinition);
} catch (err: unknown) {
const originalParseError = err as Error;
if (!mermaidDefinition.includes('"')) {
return { success: false, error: originalParseError };
}
try { try {
ret = await api.parseMermaidToExcalidraw(mermaidDefinition);
} catch (err: unknown) {
ret = await api.parseMermaidToExcalidraw( ret = await api.parseMermaidToExcalidraw(
mermaidDefinition.replace(/"/g, "'"), mermaidDefinition.replace(/"/g, "'"),
); );
} catch {
// Keep the original error so line/column references stay aligned with
// the user's unmodified input.
return { success: false, error: originalParseError };
} }
} catch (err: unknown) {
return { success: false, error: err as Error };
} }
const { elements, files } = ret; const { elements, files } = ret;
@@ -1,82 +0,0 @@
import { StreamLanguage } from "@codemirror/language";
const mermaidStreamParser = StreamLanguage.define({
token(stream) {
// Comments: %%...
if (stream.match(/^%%.*$/)) {
return "comment";
}
// Strings
if (stream.match(/^"(?:[^"\\]|\\.)*"/)) {
return "string";
}
// Diagram type keywords (at start of line or after whitespace)
if (
stream.match(
/^(flowchart|graph|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|mindmap|journey|gitGraph|timeline|quadrantChart|sankey|xychart)\b/i,
)
) {
return "keyword";
}
// Direction keywords
if (stream.match(/^(TB|TD|BT|RL|LR)\b/)) {
return "keyword";
}
// Keywords
if (
stream.match(
/^(subgraph|end|participant|actor|loop|alt|else|opt|par|critical|break|rect|note|over|activate|deactivate|title|section|class|style|linkStyle|classDef|click)\b/i,
)
) {
return "keyword";
}
// Arrows: -->, ---, -.->, ===>, etc.
if (stream.match(/^[-.=<>|ox]+>/)) {
return "operator";
}
if (stream.match(/^<[-.=<>|ox]+/)) {
return "operator";
}
if (stream.match(/^--+|\.\.+|==+/)) {
return "operator";
}
// Labels in brackets/parens: [text], (text), {text}, ((text)), etc.
if (stream.match(/^[[\](){}|<>]/)) {
return "bracket";
}
// Node IDs (alphanumeric)
if (stream.match(/^[A-Za-z_][A-Za-z0-9_]*/)) {
return "variableName";
}
// Numbers
if (stream.match(/^\d+(\.\d+)?/)) {
return "number";
}
// Punctuation
if (stream.match(/^[,:;]/)) {
return "punctuation";
}
// Skip whitespace
if (stream.eatSpace()) {
return null;
}
// Skip any other character
stream.next();
return null;
},
});
export function mermaidLite() {
return mermaidStreamParser;
}
@@ -1,6 +1,9 @@
import { RequestError } from "../../../errors"; import { RequestError } from "@excalidraw/excalidraw/errors";
import type { LLMMessage, TTTDDialog } from "../types"; import type {
LLMMessage,
TTTDDialog,
} from "@excalidraw/excalidraw/components/TTDDialog/types";
interface RateLimitInfo { interface RateLimitInfo {
rateLimit?: number; rateLimit?: number;
@@ -1,116 +0,0 @@
import { describe, expect, it } from "vitest";
import { getMermaidAutoFixCandidates } from "./mermaidAutoFix";
describe("getMermaidAutoFixCandidates", () => {
it("suggests removing trailing token after a closed label shape", () => {
const sourceText = `graph TD
L3_TCP["TCP (Transmission Control Protocol)"]x
L3_UDP["UDP (User Datagram Protocol)"]`;
const errorMessage = `Parse error on line 2:
...ission Control Protocol)"]x
-----------------------------^
Expecting 'SEMI', got 'NODE_STRING'`;
const candidates = getMermaidAutoFixCandidates(sourceText, errorMessage);
expect(candidates).toContain(`graph TD
L3_TCP["TCP (Transmission Control Protocol)"]
L3_UDP["UDP (User Datagram Protocol)"]`);
});
it("suggests appending missing end statements", () => {
const sourceText = `graph TD
subgraph A
A1[Start]`;
const errorMessage = `Parse error on line 3:
... A1[Start]
-------------^
Expecting 'end'`;
const candidates = getMermaidAutoFixCandidates(sourceText, errorMessage);
expect(candidates).toContain(`graph TD
subgraph A
A1[Start]
end`);
});
it("returns empty list for non-parse errors", () => {
expect(
getMermaidAutoFixCandidates("graph TD\nA-->B", "Network error"),
).toEqual([]);
});
it("extracts line index from lexical error format too", () => {
const sourceText = `graph TD
subgraph Layers["X"]x
direction TB`;
const errorMessage = `Lexical error on line 2. Unrecognized text.
... subgraph Layers["X"]x direction
-----------------------^`;
const candidates = getMermaidAutoFixCandidates(sourceText, errorMessage);
expect(candidates).toContain(`graph TD
subgraph Layers["X"]
direction TB`);
});
it("removes extra > after edge label", () => {
const sourceText = `flowchart TD
A["User Input"] -->|text|> B["Tokenization"]
A["User Input"] -->|text|> B["Tokenization"]`;
const errorMessage = `Parse error on line 2:
...A["User Input"] -->|text|> B["Tokenization"]
---------------------------^
Expecting 'NODE_STRING', got 'GT'`;
const candidates = getMermaidAutoFixCandidates(sourceText, errorMessage);
expect(candidates).toContain(`flowchart TD
A["User Input"] -->|text| B["Tokenization"]
A["User Input"] -->|text| B["Tokenization"]`);
});
it("suggests removing the last invalid deactivate for participant errors", () => {
const sourceText = `sequenceDiagram
participant QAEngineer as QA
activate QA
QA->>QA: Verifies Fix
deactivate QA
QA->>QA: Verifies Again
deactivate QA`;
const errorMessage = "Trying to inactivate an inactive participant (QA)";
const candidates = getMermaidAutoFixCandidates(sourceText, errorMessage);
expect(candidates).toContain(`sequenceDiagram
participant QAEngineer as QA
activate QA
QA->>QA: Verifies Fix
deactivate QA
QA->>QA: Verifies Again`);
});
it("adds a fallback candidate that removes all invalid deactivations", () => {
const sourceText = `sequenceDiagram
participant QAEngineer as QA
deactivate QA
QA->>QA: Verifies Fix
deactivate QA`;
const errorMessage = "Trying to inactivate an inactive participant (QA)";
const candidates = getMermaidAutoFixCandidates(sourceText, errorMessage);
expect(candidates).toContain(`sequenceDiagram
participant QAEngineer as QA
QA->>QA: Verifies Fix`);
});
});
@@ -1,175 +0,0 @@
import {
getMermaidErrorLineNumber,
getMermaidInactiveParticipant,
isMermaidAutoFixableError,
isMermaidParseSyntaxError,
} from "./mermaidError";
const getErrorLineIndex = (message: string, sourceText: string) => {
const lineNumber = getMermaidErrorLineNumber(message, sourceText);
if (lineNumber == null) {
return null;
}
return lineNumber - 1;
};
const replaceLineAt = (
lines: string[],
index: number,
transform: (line: string) => string,
) => {
if (index < 0 || index >= lines.length) {
return null;
}
const nextLine = transform(lines[index]);
if (nextLine === lines[index]) {
return null;
}
const nextLines = [...lines];
nextLines[index] = nextLine;
return nextLines.join("\n");
};
const stripTrailingTokenAfterShape = (line: string) => {
const alphaTailMatch = line.match(
/^(.*(?:\[[^\]]*]|\([^)]*\)|\{[^}]*}|"(?:[^"]*)"|'(?:[^']*)'))([A-Za-z]+)\s*$/,
);
if (alphaTailMatch) {
return alphaTailMatch[1];
}
const punctuationTailMatch = line.match(
/^(.*(?:\[[^\]]*]|\([^)]*\)|\{[^}]*}|"(?:[^"]*)"|'(?:[^']*)'))([,;:])\s*$/,
);
if (punctuationTailMatch) {
return punctuationTailMatch[1];
}
return line;
};
const removeExtraArrowheadAfterEdgeLabel = (line: string) => {
// Common typo in generated Mermaid: `-->|label|> Target` (extra `>`).
// Convert it to `-->|label| Target`.
return line.replace(/(\|[^|\n]+\|)\s*>\s*(?=[A-Za-z0-9_("[{'`])/g, "$1 ");
};
const escapeRegExp = (value: string) =>
value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const removeLastDeactivateForParticipant = (
sourceText: string,
participant: string,
) => {
const pattern = new RegExp(
`^\\s*deactivate\\s+${escapeRegExp(participant)}(?:\\s+%%.*)?\\s*$`,
);
const lines = sourceText.split(/\r?\n/);
for (let index = lines.length - 1; index >= 0; index--) {
if (pattern.test(lines[index])) {
return lines.filter((_, lineIndex) => lineIndex !== index).join("\n");
}
}
return null;
};
const removeAllDeactivateForParticipant = (
sourceText: string,
participant: string,
) => {
const pattern = new RegExp(
`^\\s*deactivate\\s+${escapeRegExp(participant)}(?:\\s+%%.*)?\\s*$`,
);
const lines = sourceText.split(/\r?\n/);
let removedAny = false;
const remainingLines = lines.filter((line) => {
if (!pattern.test(line)) {
return true;
}
removedAny = true;
return false;
});
return removedAny ? remainingLines.join("\n") : null;
};
const appendMissingEnds = (sourceText: string) => {
const subgraphCount = (sourceText.match(/^\s*subgraph\b/gm) || []).length;
const endCount = (sourceText.match(/^\s*end\s*$/gm) || []).length;
const missingCount = subgraphCount - endCount;
if (missingCount <= 0) {
return null;
}
const endings = Array.from({ length: missingCount }, () => "end").join("\n");
return `${sourceText.trimEnd()}\n${endings}`;
};
const normalizeSmartQuotes = (sourceText: string) =>
sourceText.replace(/[“”]/g, '"').replace(/[‘’]/g, "'");
export const getMermaidAutoFixCandidates = (
sourceText: string,
errorMessage: string,
) => {
if (!isMermaidAutoFixableError(errorMessage) || !sourceText.trim()) {
return [];
}
const candidates: string[] = [];
const seen = new Set<string>();
const addCandidate = (candidate: string | null) => {
if (!candidate || candidate === sourceText || seen.has(candidate)) {
return;
}
seen.add(candidate);
candidates.push(candidate);
};
const inactiveParticipant = getMermaidInactiveParticipant(errorMessage);
if (inactiveParticipant) {
addCandidate(
removeLastDeactivateForParticipant(sourceText, inactiveParticipant),
);
// Fallback for repeated invalid inactivations in one diagram.
addCandidate(
removeAllDeactivateForParticipant(sourceText, inactiveParticipant),
);
}
if (isMermaidParseSyntaxError(errorMessage)) {
const lines = sourceText.split(/\r?\n/);
const errorLineIndex = getErrorLineIndex(errorMessage, sourceText);
const lineIndexesToTry =
errorLineIndex == null
? []
: [errorLineIndex, errorLineIndex - 1, errorLineIndex + 1];
for (const lineIndex of lineIndexesToTry) {
addCandidate(
replaceLineAt(lines, lineIndex, (line) =>
stripTrailingTokenAfterShape(line),
),
);
addCandidate(
replaceLineAt(lines, lineIndex, (line) =>
removeExtraArrowheadAfterEdgeLabel(line),
),
);
}
// Also try full-text replacement so repeated occurrences on other lines
// are fixed together in a single candidate.
addCandidate(removeExtraArrowheadAfterEdgeLabel(sourceText));
addCandidate(appendMissingEnds(sourceText));
const normalizedQuotes = normalizeSmartQuotes(sourceText);
addCandidate(normalizedQuotes === sourceText ? null : normalizedQuotes);
}
return candidates;
};
@@ -1,155 +0,0 @@
import { describe, expect, it } from "vitest";
import {
formatMermaidParseErrorMessage,
getMermaidErrorLineNumber,
getMermaidInactiveParticipant,
getMermaidSyntaxErrorGuidance,
isMermaidAutoFixableError,
isMermaidParseSyntaxError,
isMermaidCaretLine,
} from "./mermaidError";
describe("formatMermaidParseErrorMessage", () => {
it("strips the noisy Expecting clause from Mermaid parse errors", () => {
const message = `Parse error on line 6:
... Control Protocol)"]x L3_UDP
----------------------^
Expecting 'SEMI', 'NEWLINE', 'SPACE', got 'NODE_STRING'`;
expect(formatMermaidParseErrorMessage(message)).toBe(`Parse error on line 6:
... Control Protocol)"]x L3_UDP
----------------------^`);
});
it("keeps Mermaid parse errors unchanged when no Expecting clause exists", () => {
const message = `Parse error on line 3:
... some snippet
----^`;
expect(formatMermaidParseErrorMessage(message)).toBe(message);
});
it("does not modify non-Mermaid parse messages", () => {
const message =
"Unexpected token while parsing JSON. Expecting value at position 10.";
expect(formatMermaidParseErrorMessage(message)).toBe(message);
});
});
describe("isMermaidCaretLine", () => {
it("returns true for Mermaid caret lines", () => {
expect(isMermaidCaretLine("-----------------------^")).toBe(true);
});
it("returns false for regular lines", () => {
expect(isMermaidCaretLine(`... Control Protocol)"]x`)).toBe(false);
});
});
describe("isMermaidParseSyntaxError", () => {
it("returns true for Mermaid parser syntax errors", () => {
expect(isMermaidParseSyntaxError("Parse error on line 6: ...")).toBe(true);
});
it("returns true for Mermaid lexical syntax errors", () => {
expect(
isMermaidParseSyntaxError("Lexical error on line 2. Unrecognized text."),
).toBe(true);
});
it("returns false for non-parse errors", () => {
expect(isMermaidParseSyntaxError("Network error")).toBe(false);
});
});
describe("isMermaidAutoFixableError", () => {
it("returns true for Mermaid parser syntax errors", () => {
expect(isMermaidAutoFixableError("Parse error on line 6: ...")).toBe(true);
});
it("returns true for inactive participant runtime errors", () => {
expect(
isMermaidAutoFixableError(
"Trying to inactivate an inactive participant (QA)",
),
).toBe(true);
});
it("returns false for non-fixable errors", () => {
expect(isMermaidAutoFixableError("Network error")).toBe(false);
});
});
describe("getMermaidInactiveParticipant", () => {
it("extracts the participant id from inactive participant errors", () => {
expect(
getMermaidInactiveParticipant(
"Trying to inactivate an inactive participant (QA)",
),
).toBe("QA");
});
it("returns null for unrelated errors", () => {
expect(
getMermaidInactiveParticipant("Parse error on line 3: ..."),
).toBeNull();
});
});
describe("getMermaidErrorLineNumber", () => {
it("extracts line number from parse error format", () => {
expect(getMermaidErrorLineNumber("Parse error on line 6: ...")).toBe(6);
});
it("extracts line number from lexical error format", () => {
expect(
getMermaidErrorLineNumber("Lexical error on line 2. Unrecognized text."),
).toBe(2);
});
it("returns null for messages without Mermaid line details", () => {
expect(getMermaidErrorLineNumber("Network error")).toBeNull();
});
it("infers line from inactive participant errors when source text is provided", () => {
const sourceText = `sequenceDiagram
participant QA
deactivate QA
QA->>QA: Verifies Fix
deactivate QA`;
expect(
getMermaidErrorLineNumber(
"Trying to inactivate an inactive participant (QA)",
sourceText,
),
).toBe(5);
});
});
describe("getMermaidSyntaxErrorGuidance", () => {
it("returns summary and likely causes for Mermaid parse errors", () => {
const message = `Parse error on line 6:
... Control Protocol)"]x
----------------------^`;
const source = `graph TD
subgraph Layers["X"]
L3_TCP["TCP (Transmission Control Protocol)"]x`;
expect(getMermaidSyntaxErrorGuidance(message, source)).toEqual({
summary: "Syntax error near line 6.",
likelyCauses: expect.arrayContaining([
"A block is missing an `end` statement.",
]),
});
});
it("returns null for non-parse errors", () => {
expect(
getMermaidSyntaxErrorGuidance("Network error", "graph TD"),
).toBeNull();
});
});
@@ -1,133 +0,0 @@
const MERMAID_SYNTAX_ERROR_LINE = /(?:Parse|Lexical) error on line (\d+)[.:]/i;
const MERMAID_INACTIVE_PARTICIPANT_ERROR =
/Trying to inactivate an inactive participant \((.+)\)/i;
const MERMAID_CARET_LINE = /^\s*-+\^\s*$/;
export const isMermaidParseSyntaxError = (message: string) =>
MERMAID_SYNTAX_ERROR_LINE.test(message);
export const isMermaidAutoFixableError = (message: string) =>
isMermaidParseSyntaxError(message) ||
MERMAID_INACTIVE_PARTICIPANT_ERROR.test(message);
export const isMermaidCaretLine = (line: string) =>
MERMAID_CARET_LINE.test(line);
export const getMermaidInactiveParticipant = (
message: string,
): string | null => {
const match = message.match(MERMAID_INACTIVE_PARTICIPANT_ERROR);
if (!match?.[1]) {
return null;
}
return match[1].trim();
};
const escapeRegExp = (value: string) =>
value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const getInactiveParticipantLineNumber = (
message: string,
sourceText: string,
): number | null => {
const participant = getMermaidInactiveParticipant(message);
if (!participant) {
return null;
}
const deactivatePattern = new RegExp(
`^\\s*deactivate\\s+${escapeRegExp(participant)}(?:\\s+%%.*)?\\s*$`,
);
const lines = sourceText.split(/\r?\n/);
for (let index = lines.length - 1; index >= 0; index--) {
if (deactivatePattern.test(lines[index])) {
return index + 1;
}
}
return null;
};
export const getMermaidErrorLineNumber = (
message: string,
sourceText?: string,
): number | null => {
const match = message.match(MERMAID_SYNTAX_ERROR_LINE);
if (!match) {
if (!sourceText) {
return null;
}
return getInactiveParticipantLineNumber(message, sourceText);
}
return Number.parseInt(match[1], 10);
};
const countMatches = (text: string, re: RegExp) =>
(text.match(re) || []).length;
export const getMermaidSyntaxErrorGuidance = (
message: string,
sourceText?: string,
): { summary: string; likelyCauses: string[] } | null => {
if (!isMermaidParseSyntaxError(message)) {
return null;
}
const errorLine = getMermaidErrorLineNumber(message, sourceText);
const summary = errorLine
? `Syntax error near line ${errorLine}.`
: "Syntax error in Mermaid diagram.";
const likelyCauses: string[] = [];
if (sourceText) {
const openBrackets = countMatches(sourceText, /\[/g);
const closeBrackets = countMatches(sourceText, /\]/g);
if (openBrackets !== closeBrackets) {
likelyCauses.push("Unbalanced square brackets in a node label.");
}
const openParens = countMatches(sourceText, /\(/g);
const closeParens = countMatches(sourceText, /\)/g);
if (openParens !== closeParens) {
likelyCauses.push("Unbalanced parentheses in a node shape.");
}
const openBraces = countMatches(sourceText, /\{/g);
const closeBraces = countMatches(sourceText, /\}/g);
if (openBraces !== closeBraces) {
likelyCauses.push("Unbalanced braces in a decision node.");
}
const subgraphCount = countMatches(sourceText, /^\s*subgraph\b/gm);
const endCount = countMatches(sourceText, /^\s*end\s*$/gm);
if (subgraphCount > endCount) {
likelyCauses.push("A block is missing an `end` statement.");
}
}
if (/got 'NODE_STRING'/.test(message) || /got 'PS'/.test(message)) {
likelyCauses.push(
"An extra character/token may appear after a node or label definition.",
);
}
if (likelyCauses.length === 0) {
likelyCauses.push(
"A node or edge line is malformed (missing/extra delimiters).",
);
likelyCauses.push("A block (`subgraph`, `class`, etc.) may be incomplete.");
}
return {
summary,
likelyCauses: [...new Set(likelyCauses)],
};
};
export const formatMermaidParseErrorMessage = (message: string) => {
if (!isMermaidParseSyntaxError(message)) {
return message;
}
return message.replace(/\n\s*Expecting[\s\S]*$/, "").trimEnd();
};
+15 -31
View File
@@ -1,51 +1,35 @@
@use "../css/variables.module" as *;
.excalidraw { .excalidraw {
.Toast { .Toast {
$closeButtonSize: 1.2rem; $closeButtonSize: 1.2rem;
$closeButtonPadding: 0.4rem; $closeButtonPadding: 0.4rem;
animation: Toast-fade-in 0.5s; animation: fade-in 0.5s;
min-width: 220px; background-color: var(--button-gray-1);
max-width: min(360px, calc(100vw - 32px)); border-radius: 4px;
border-radius: var(--border-radius-lg); bottom: 10px;
border: 1px solid var(--default-border-color);
background-color: var(--island-bg-color);
color: var(--text-primary-color);
padding: 0.5rem 0.75rem;
box-shadow: 0 0 0 1px var(--color-surface-lowest);
box-sizing: border-box; box-sizing: border-box;
cursor: default; cursor: default;
pointer-events: none; left: 50%;
margin-left: -150px;
padding: 4px 0;
position: absolute;
text-align: center;
width: 300px;
z-index: 999999;
.Toast__message { .Toast__message {
font-family: var(--ui-font);
font-size: 0.75rem;
line-height: 1.25rem;
text-align: center;
padding: 0 $closeButtonSize + ($closeButtonPadding); padding: 0 $closeButtonSize + ($closeButtonPadding);
color: var(--popup-text-color);
white-space: pre-wrap; white-space: pre-wrap;
} }
.Toast__progress-bar {
margin-top: 0.35rem;
width: 100%;
height: 4px;
border-radius: 999px;
background-color: var(--button-gray-2);
overflow: hidden;
}
.Toast__progress-bar-fill {
height: 100%;
border-radius: inherit;
background-color: var(--color-primary);
}
.close { .close {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
padding: $closeButtonPadding; padding: $closeButtonPadding;
pointer-events: auto;
.ToolIcon__icon { .ToolIcon__icon {
width: $closeButtonSize; width: $closeButtonSize;
@@ -54,7 +38,7 @@
} }
} }
@keyframes Toast-fade-in { @keyframes fade-in {
from { from {
opacity: 0; opacity: 0;
} }
+4 -18
View File
@@ -5,22 +5,11 @@ import { ToolButton } from "./ToolButton";
import "./Toast.scss"; import "./Toast.scss";
import type { CSSProperties, ReactNode } from "react"; import type { CSSProperties } from "react";
const DEFAULT_TOAST_TIMEOUT = 5000; const DEFAULT_TOAST_TIMEOUT = 5000;
const ProgressBar = ({ progress }: { progress: number }) => ( export const Toast = ({
<div className="Toast__progress-bar">
<div
className="Toast__progress-bar-fill"
style={{
width: `${Math.min(5, Math.round(progress * 100))}%`,
}}
/>
</div>
);
const ToastComponent = ({
message, message,
onClose, onClose,
closable = false, closable = false,
@@ -28,7 +17,7 @@ const ToastComponent = ({
duration = DEFAULT_TOAST_TIMEOUT, duration = DEFAULT_TOAST_TIMEOUT,
style, style,
}: { }: {
message: ReactNode; message: string;
onClose: () => void; onClose: () => void;
closable?: boolean; closable?: boolean;
duration?: number; duration?: number;
@@ -58,12 +47,11 @@ const ToastComponent = ({
return ( return (
<div <div
className="Toast" className="Toast"
role="status"
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
style={style} style={style}
> >
<div className="Toast__message">{message}</div> <p className="Toast__message">{message}</p>
{closable && ( {closable && (
<ToolButton <ToolButton
icon={CloseIcon} icon={CloseIcon}
@@ -76,5 +64,3 @@ const ToastComponent = ({
</div> </div>
); );
}; };
export const Toast = Object.assign(ToastComponent, { ProgressBar });
@@ -6,6 +6,7 @@ import {
sceneCoordsToViewportCoords, sceneCoordsToViewportCoords,
type EditorInterface, type EditorInterface,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { AnimationController } from "@excalidraw/excalidraw/renderer/animation";
import type { import type {
InteractiveCanvasRenderConfig, InteractiveCanvasRenderConfig,
@@ -23,8 +24,6 @@ import type {
import { t } from "../../i18n"; import { t } from "../../i18n";
import { renderInteractiveScene } from "../../renderer/interactiveScene"; import { renderInteractiveScene } from "../../renderer/interactiveScene";
import { AnimationController } from "../../renderer/animation";
import type { import type {
AppClassProperties, AppClassProperties,
AppState, AppState,
@@ -469,6 +469,7 @@ const PreferencesToggleMidpointSnappingItem = () => {
return ( return (
<DropdownMenuItemCheckbox <DropdownMenuItemCheckbox
checked={appState.isMidpointSnappingEnabled} checked={appState.isMidpointSnappingEnabled}
disabled={appState.bindingPreference === "disabled"}
onSelect={(event) => { onSelect={(event) => {
actionManager.executeAction(actionToggleMidpointSnapping); actionManager.executeAction(actionToggleMidpointSnapping);
event.preventDefault(); event.preventDefault();
-20
View File
@@ -500,26 +500,6 @@ body.excalidraw-cursor-resize * {
} }
} }
.floating-status-stack {
position: absolute;
left: 50%;
bottom: 30px;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
pointer-events: none;
.scroll-back-to-content {
position: static;
left: auto;
bottom: auto;
transform: none;
pointer-events: var(--ui-pointerEvents);
}
}
.help-icon { .help-icon {
@include outlineButtonStyles; @include outlineButtonStyles;
@include filledButtonOnCanvas; @include filledButtonOnCanvas;
+9 -10
View File
@@ -27,6 +27,7 @@ import {
import type { AppState, DataURL, LibraryItem } from "../types"; import type { AppState, DataURL, LibraryItem } from "../types";
import type { FileSystemHandle } from "browser-fs-access";
import type { ImportedLibraryData } from "./types"; import type { ImportedLibraryData } from "./types";
const parseFileContents = async (blob: Blob | File): Promise<string> => { const parseFileContents = async (blob: Blob | File): Promise<string> => {
@@ -103,7 +104,7 @@ export const getMimeType = (blob: Blob | string): string => {
return ""; return "";
}; };
export const getFileHandleType = (handle: FileSystemFileHandle | null) => { export const getFileHandleType = (handle: FileSystemHandle | null) => {
if (!handle) { if (!handle) {
return null; return null;
} }
@@ -117,9 +118,7 @@ export const isImageFileHandleType = (
return type === "png" || type === "svg"; return type === "png" || type === "svg";
}; };
export const isImageFileHandle = ( export const isImageFileHandle = (handle: FileSystemHandle | null) => {
handle: FileSystemFileHandle | null,
): handle is FileSystemFileHandle => {
const type = getFileHandleType(handle); const type = getFileHandleType(handle);
return type === "png" || type === "svg"; return type === "png" || type === "svg";
}; };
@@ -140,8 +139,8 @@ export const loadSceneOrLibraryFromBlob = async (
/** @see restore.localAppState */ /** @see restore.localAppState */
localAppState: AppState | null, localAppState: AppState | null,
localElements: readonly ExcalidrawElement[] | null, localElements: readonly ExcalidrawElement[] | null,
/** FileSystemFileHandle. Defaults to `blob.handle` if defined, otherwise null. */ /** FileSystemHandle. Defaults to `blob.handle` if defined, otherwise null. */
fileHandle?: FileSystemFileHandle | null, fileHandle?: FileSystemHandle | null,
) => { ) => {
const contents = await parseFileContents(blob); const contents = await parseFileContents(blob);
let data; let data;
@@ -199,8 +198,8 @@ export const loadFromBlob = async (
/** @see restore.localAppState */ /** @see restore.localAppState */
localAppState: AppState | null, localAppState: AppState | null,
localElements: readonly ExcalidrawElement[] | null, localElements: readonly ExcalidrawElement[] | null,
/** FileSystemFileHandle. Defaults to `blob.handle` if defined, otherwise null. */ /** FileSystemHandle. Defaults to `blob.handle` if defined, otherwise null. */
fileHandle?: FileSystemFileHandle | null, fileHandle?: FileSystemHandle | null,
) => { ) => {
const ret = await loadSceneOrLibraryFromBlob( const ret = await loadSceneOrLibraryFromBlob(
blob, blob,
@@ -393,7 +392,7 @@ export const ImageURLToFile = async (
export const getFileHandle = async ( export const getFileHandle = async (
event: DragEvent | React.DragEvent | DataTransferItem, event: DragEvent | React.DragEvent | DataTransferItem,
): Promise<FileSystemFileHandle | null> => { ): Promise<FileSystemHandle | null> => {
if (nativeFileSystemSupported) { if (nativeFileSystemSupported) {
try { try {
const dataTransferItem = const dataTransferItem =
@@ -401,7 +400,7 @@ export const getFileHandle = async (
? event ? event
: (event as DragEvent).dataTransfer?.items?.[0]; : (event as DragEvent).dataTransfer?.items?.[0];
const handle: FileSystemFileHandle | null = const handle: FileSystemHandle | null =
(await (dataTransferItem as any).getAsFileSystemHandle()) || null; (await (dataTransferItem as any).getAsFileSystemHandle()) || null;
return handle; return handle;
+44 -4
View File
@@ -4,12 +4,18 @@ import {
supported as nativeFileSystemSupported, supported as nativeFileSystemSupported,
} from "browser-fs-access"; } from "browser-fs-access";
import { MIME_TYPES } from "@excalidraw/common"; import { EVENT, MIME_TYPES, debounce } from "@excalidraw/common";
import { AbortError } from "../errors";
import { normalizeFile } from "./blob"; import { normalizeFile } from "./blob";
import type { FileSystemHandle } from "browser-fs-access";
type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">; type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
const INPUT_CHANGE_INTERVAL_MS = 5000;
export const fileOpen = async <M extends boolean | undefined = false>(opts: { export const fileOpen = async <M extends boolean | undefined = false>(opts: {
extensions?: FILE_EXTENSION[]; extensions?: FILE_EXTENSION[];
description: string; description: string;
@@ -36,6 +42,40 @@ export const fileOpen = async <M extends boolean | undefined = false>(opts: {
extensions, extensions,
mimeTypes, mimeTypes,
multiple: opts.multiple ?? false, multiple: opts.multiple ?? false,
legacySetup: (resolve, reject, input) => {
const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
const focusHandler = () => {
checkForFile();
document.addEventListener(EVENT.KEYUP, scheduleRejection);
document.addEventListener(EVENT.POINTER_UP, scheduleRejection);
scheduleRejection();
};
const checkForFile = () => {
// this hack might not work when expecting multiple files
if (input.files?.length) {
const ret = opts.multiple ? [...input.files] : input.files[0];
resolve(ret as RetType);
}
};
requestAnimationFrame(() => {
window.addEventListener(EVENT.FOCUS, focusHandler);
});
const interval = window.setInterval(() => {
checkForFile();
}, INPUT_CHANGE_INTERVAL_MS);
return (rejectPromise) => {
clearInterval(interval);
scheduleRejection.cancel();
window.removeEventListener(EVENT.FOCUS, focusHandler);
document.removeEventListener(EVENT.KEYUP, scheduleRejection);
document.removeEventListener(EVENT.POINTER_UP, scheduleRejection);
if (rejectPromise) {
// so that something is shown in console if we need to debug this
console.warn("Opening the file was canceled (legacy-fs).");
rejectPromise(new AbortError());
}
};
},
}); });
if (Array.isArray(files)) { if (Array.isArray(files)) {
@@ -55,8 +95,8 @@ export const fileSave = (
extension: FILE_EXTENSION; extension: FILE_EXTENSION;
mimeTypes?: string[]; mimeTypes?: string[];
description: string; description: string;
/** existing FileSystemFileHandle */ /** existing FileSystemHandle */
fileHandle?: FileSystemFileHandle | null; fileHandle?: FileSystemHandle | null;
}, },
) => { ) => {
return _fileSave( return _fileSave(
@@ -68,8 +108,8 @@ export const fileSave = (
mimeTypes: opts.mimeTypes, mimeTypes: opts.mimeTypes,
}, },
opts.fileHandle, opts.fileHandle,
false,
); );
}; };
export { nativeFileSystemSupported }; export { nativeFileSystemSupported };
export type { FileSystemHandle };
+3 -1
View File
@@ -33,6 +33,8 @@ import { canvasToBlob } from "./blob";
import { fileSave } from "./filesystem"; import { fileSave } from "./filesystem";
import { serializeAsJSON } from "./json"; import { serializeAsJSON } from "./json";
import type { FileSystemHandle } from "./filesystem";
import type { ExportType } from "../scene/types"; import type { ExportType } from "../scene/types";
import type { AppState, BinaryFiles } from "../types"; import type { AppState, BinaryFiles } from "../types";
@@ -108,7 +110,7 @@ export const exportCanvas = async (
viewBackgroundColor: string; viewBackgroundColor: string;
/** filename, if applicable */ /** filename, if applicable */
name?: string; name?: string;
fileHandle?: FileSystemFileHandle | null; fileHandle?: FileSystemHandle | null;
exportingFrame: ExcalidrawFrameLikeElement | null; exportingFrame: ExcalidrawFrameLikeElement | null;
}, },
) => { ) => {
+18 -27
View File
@@ -1,13 +1,12 @@
import { import {
DEFAULT_FILENAME,
EXPORT_DATA_TYPES, EXPORT_DATA_TYPES,
getExportSource, getExportSource,
MIME_TYPES, MIME_TYPES,
VERSIONS, VERSIONS,
} from "@excalidraw/common"; } from "@excalidraw/common";
import type { ExcalidrawElement, NonDeleted } from "@excalidraw/element/types"; import type { ExcalidrawElement } from "@excalidraw/element/types";
import type { MaybePromise } from "@excalidraw/common/utility-types";
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState"; import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
@@ -22,12 +21,6 @@ import type {
ImportedLibraryData, ImportedLibraryData,
} from "./types"; } from "./types";
export type JSONExportData = {
elements: readonly NonDeleted<ExcalidrawElement>[];
appState: AppState;
files: BinaryFiles;
};
/** /**
* Strips out files which are only referenced by deleted elements * Strips out files which are only referenced by deleted elements
*/ */
@@ -74,29 +67,27 @@ export const serializeAsJSON = (
return JSON.stringify(data, null, 2); return JSON.stringify(data, null, 2);
}; };
export const saveAsJSON = async ({ export const saveAsJSON = async (
data, elements: readonly ExcalidrawElement[],
filename, appState: AppState,
fileHandle, files: BinaryFiles,
}: { /** filename */
data: MaybePromise<JSONExportData>; name: string = appState.name || DEFAULT_FILENAME,
filename: string; ) => {
fileHandle: AppState["fileHandle"]; const serialized = serializeAsJSON(elements, appState, files, "local");
}) => { const blob = new Blob([serialized], {
const blob = Promise.resolve(data).then(({ elements, appState, files }) => { type: MIME_TYPES.excalidraw,
const serialized = serializeAsJSON(elements, appState, files, "local");
return new Blob([serialized], {
type: MIME_TYPES.excalidraw,
});
}); });
const savedFileHandle = await fileSave(blob, { const fileHandle = await fileSave(blob, {
name: filename, name,
extension: "excalidraw", extension: "excalidraw",
description: "Excalidraw file", description: "Excalidraw file",
fileHandle: isImageFileHandle(fileHandle) ? null : fileHandle, fileHandle: isImageFileHandle(appState.fileHandle)
? null
: appState.fileHandle,
}); });
return { fileHandle: savedFileHandle }; return { fileHandle };
}; };
export const loadFromJSON = async ( export const loadFromJSON = async (
+8 -21
View File
@@ -1,39 +1,26 @@
import type { MaybePromise } from "@excalidraw/common/utility-types";
import type { ExcalidrawElement } from "@excalidraw/element/types"; import type { ExcalidrawElement } from "@excalidraw/element/types";
import { getFileHandleType, isImageFileHandleType } from "./blob"; import { getFileHandleType, isImageFileHandleType } from "./blob";
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { exportCanvas, prepareElementsForExport } from "."; import { exportCanvas, prepareElementsForExport } from ".";
import type { AppState, BinaryFiles } from "../types"; import type { AppState, BinaryFiles } from "../types";
export const resaveAsImageWithScene = async ( export const resaveAsImageWithScene = async (
data: MaybePromise<{ elements: readonly ExcalidrawElement[],
elements: readonly ExcalidrawElement[]; appState: AppState,
appState: AppState; files: BinaryFiles,
files: BinaryFiles; name: string,
}>,
fileHandle: FileSystemFileHandle,
filename: string,
) => { ) => {
const { exportBackground, viewBackgroundColor, fileHandle } = appState;
const fileHandleType = getFileHandleType(fileHandle); const fileHandleType = getFileHandleType(fileHandle);
if (Math.random() < 1) { if (!fileHandle || !isImageFileHandleType(fileHandleType)) {
throw new Error("OLALALALA");
}
if (!isImageFileHandleType(fileHandleType)) {
throw new Error( throw new Error(
"fileHandle should exist and should be of type svg or png when resaving", "fileHandle should exist and should be of type svg or png when resaving",
); );
} }
let { elements, appState, files } = await data;
const { exportBackground, viewBackgroundColor } = appState;
appState = { appState = {
...appState, ...appState,
exportEmbedScene: true, exportEmbedScene: true,
@@ -48,7 +35,7 @@ export const resaveAsImageWithScene = async (
await exportCanvas(fileHandleType, exportedElements, appState, files, { await exportCanvas(fileHandleType, exportedElements, appState, files, {
exportBackground, exportBackground,
viewBackgroundColor, viewBackgroundColor,
name: filename, name,
fileHandle, fileHandle,
exportingFrame, exportingFrame,
}); });
+1 -1
View File
@@ -52,7 +52,7 @@ declare module "png-chunks-extract" {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
interface Blob { interface Blob {
handle?: FileSystemFileHandle; handle?: import("browser-fs-acces").FileSystemHandle;
name?: string; name?: string;
} }
@@ -1,110 +0,0 @@
import { useEffect, useRef, useState } from "react";
import { useExcalidrawAPI } from "../components/App";
import { getDefaultAppState } from "../appState";
import type { AppState } from "../types";
/**
* Subscribes to specific appState changes. The component re-renders
* only when the specified prop(s) change not on every appState update.
*
* Works both inside and outside the <Excalidraw> tree, as long as
* ExcalidrawAPIContext.Provider is an ancestor (automatically provided
* inside <Excalidraw>, or manually by the host app).
*
* Returns the narrowed value depending on prop form:
* - `keyof AppState` `AppState[K]`
* - `(keyof AppState)[]` whole `AppState`
* - selector function selector's return type `T`
*
* Calls the optional callback with the latest value on every change (not called
* on initial render).
*
* If excalidrawAPI is not ready yet (host apps), hook is rerendered with latest
* value once available.
*/
export function useAppStateValue<K extends keyof AppState>(
prop: K,
callback?: (value: AppState[K], appState: AppState) => void,
_internal?: boolean,
): AppState[K];
export function useAppStateValue(
props: (keyof AppState)[],
callback?: (props: AppState, appState: AppState) => void,
_internal?: boolean,
): AppState;
export function useAppStateValue<T>(
selector: (appState: AppState) => T,
callback?: (value: T, appState: AppState) => void,
_internal?: boolean,
): T;
export function useAppStateValue(
selector:
| keyof AppState
| (keyof AppState)[]
| ((appState: AppState) => unknown),
callback?: (value: any, appState: AppState) => void,
_internal: boolean = true,
): unknown {
const api = useExcalidrawAPI();
const getLatestValue = () => {
let appState = api?.getAppState();
if (!appState) {
if (!_internal) {
return undefined;
}
console.warn(
"useAppStateValue: excalidrawAPI not defined yet for internal component in which case it should always be defined. Are you sure you're rendering inside of <Excalidraw/> component tree?",
);
// fall back in case there's a bug so we don't break the app
// (internal components using this internal useAppStateValue expect
// non-undefined values on init)
appState = Object.assign(
{ width: 0, height: 0, offsetLeft: 0, offsetTop: 0 },
getDefaultAppState(),
);
}
if (typeof selector === "function") {
return selector(appState);
}
if (Array.isArray(selector)) {
return appState;
}
return appState[selector];
};
const [value, setValue] = useState<unknown>(getLatestValue);
const stateRef = useRef({
selector,
callback,
isInitialized: !!api,
latestValue: value,
});
stateRef.current.selector = selector;
stateRef.current.callback = callback;
if (!stateRef.current.isInitialized && api) {
stateRef.current.isInitialized = true;
stateRef.current.latestValue = getLatestValue();
}
useEffect(() => {
if (!api) {
return;
}
return api.onStateChange(
stateRef.current.selector,
(newValue: any, state: AppState) => {
stateRef.current.latestValue = newValue;
stateRef.current.callback?.(newValue, state);
setValue(newValue);
},
);
}, [api]);
return stateRef.current.latestValue;
}
+6 -91
View File
@@ -1,18 +1,14 @@
import React, { useCallback, useContext, useEffect, useState } from "react"; import React, { useEffect } from "react";
import { DEFAULT_UI_OPTIONS, isShallowEqual } from "@excalidraw/common"; import { DEFAULT_UI_OPTIONS, isShallowEqual } from "@excalidraw/common";
import App, { import App from "./components/App";
ExcalidrawAPIContext,
ExcalidrawAPISetContext,
} from "./components/App";
import { InitializeApp } from "./components/InitializeApp"; import { InitializeApp } from "./components/InitializeApp";
import Footer from "./components/footer/FooterCenter"; import Footer from "./components/footer/FooterCenter";
import LiveCollaborationTrigger from "./components/live-collaboration/LiveCollaborationTrigger"; import LiveCollaborationTrigger from "./components/live-collaboration/LiveCollaborationTrigger";
import MainMenu from "./components/main-menu/MainMenu"; import MainMenu from "./components/main-menu/MainMenu";
import WelcomeScreen from "./components/welcome-screen/WelcomeScreen"; import WelcomeScreen from "./components/welcome-screen/WelcomeScreen";
import { defaultLang } from "./i18n"; import { defaultLang } from "./i18n";
import { useAppStateValue as _useAppStateValue } from "./hooks/useAppStateValue";
import { EditorJotaiProvider, editorJotaiStore } from "./editor-jotai"; import { EditorJotaiProvider, editorJotaiStore } from "./editor-jotai";
import polyfill from "./polyfill"; import polyfill from "./polyfill";
@@ -20,44 +16,16 @@ import "./css/app.scss";
import "./css/styles.scss"; import "./css/styles.scss";
import "./fonts/fonts.css"; import "./fonts/fonts.css";
import type { import type { AppProps, ExcalidrawProps } from "./types";
AppProps,
AppState,
ExcalidrawImperativeAPI,
ExcalidrawProps,
} from "./types";
polyfill(); polyfill();
/**
* Stateless provider that allows `useExcalidrawAPI()` (and hooks built
* on it, such as `useAppStateValue()`) to work outside the <Excalidraw>
* component tree.
*/
export const ExcalidrawAPIProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [api, setApi] = useState<ExcalidrawImperativeAPI | null>(null);
return (
<ExcalidrawAPIContext.Provider value={api}>
<ExcalidrawAPISetContext.Provider value={setApi}>
{children}
</ExcalidrawAPISetContext.Provider>
</ExcalidrawAPIContext.Provider>
);
};
const ExcalidrawBase = (props: ExcalidrawProps) => { const ExcalidrawBase = (props: ExcalidrawProps) => {
const { const {
onExport,
onChange, onChange,
onIncrement, onIncrement,
initialData, initialData,
onExcalidrawAPI, excalidrawAPI,
onMount,
onInitialize,
isCollaborating = false, isCollaborating = false,
onPointerUpdate, onPointerUpdate,
renderTopLeftUI, renderTopLeftUI,
@@ -118,15 +86,6 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
UIOptions.canvasActions.toggleTheme = true; UIOptions.canvasActions.toggleTheme = true;
} }
const setExcalidrawAPI = useContext(ExcalidrawAPISetContext);
const handleExcalidrawAPI = useCallback(
(api: ExcalidrawImperativeAPI) => {
setExcalidrawAPI?.(api);
onExcalidrawAPI?.(api);
},
[onExcalidrawAPI, setExcalidrawAPI],
);
useEffect(() => { useEffect(() => {
const importPolyfill = async () => { const importPolyfill = async () => {
//@ts-ignore //@ts-ignore
@@ -156,13 +115,10 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
<EditorJotaiProvider store={editorJotaiStore}> <EditorJotaiProvider store={editorJotaiStore}>
<InitializeApp langCode={langCode} theme={theme}> <InitializeApp langCode={langCode} theme={theme}>
<App <App
onExport={onExport}
onChange={onChange} onChange={onChange}
onIncrement={onIncrement} onIncrement={onIncrement}
initialData={initialData} initialData={initialData}
onExcalidrawAPI={handleExcalidrawAPI} excalidrawAPI={excalidrawAPI}
onMount={onMount}
onInitialize={onInitialize}
isCollaborating={isCollaborating} isCollaborating={isCollaborating}
onPointerUpdate={onPointerUpdate} onPointerUpdate={onPointerUpdate}
renderTopLeftUI={renderTopLeftUI} renderTopLeftUI={renderTopLeftUI}
@@ -311,7 +267,6 @@ export {
sceneCoordsToViewportCoords, sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords, viewportCoordsToSceneCoords,
getFormFactor, getFormFactor,
throttleRAF,
} from "@excalidraw/common"; } from "@excalidraw/common";
export { export {
@@ -329,13 +284,7 @@ export { Button } from "./components/Button";
export { Footer }; export { Footer };
export { MainMenu }; export { MainMenu };
export { Ellipsify } from "./components/Ellipsify"; export { Ellipsify } from "./components/Ellipsify";
export { export { useEditorInterface, useStylesPanelMode } from "./components/App";
useEditorInterface,
useStylesPanelMode,
useExcalidrawAPI,
ExcalidrawAPIContext,
} from "./components/App";
export { WelcomeScreen }; export { WelcomeScreen };
export { LiveCollaborationTrigger }; export { LiveCollaborationTrigger };
export { Stats } from "./components/Stats"; export { Stats } from "./components/Stats";
@@ -376,37 +325,3 @@ export {
tryParseSpreadsheet, tryParseSpreadsheet,
isSpreadsheetValidForChartType, isSpreadsheetValidForChartType,
} from "./charts"; } from "./charts";
// -----------------------------------------------------------------------------
// useAppStateValue() wrapper for host apps for the return type to reflect the
// the potentially `undefined` value for initial render before the excalidrawAPI
// is ready.
//
/**
* hook that subscribes to specific appState prop(s)
*
* @param prop - appState prop(s) to subscribe to, or a selector function.
* NOTE `prop/selector` is memoized and will not change after initial render
*/
export function useAppStateValue<K extends keyof AppState>(
prop: K,
callback?: (value: AppState[K], appState: AppState) => void,
): AppState[K] | undefined;
export function useAppStateValue<T extends keyof AppState>(
props: T[],
callback?: (values: AppState, appState: AppState) => void,
): AppState | undefined;
export function useAppStateValue<T>(
selector: (appState: AppState) => T,
callback?: (value: T, appState: AppState) => void,
): T | undefined;
export function useAppStateValue(
selector:
| keyof AppState
| (keyof AppState)[]
| ((appState: AppState) => unknown),
callback?: (value: any, appState: AppState) => void,
) {
return _useAppStateValue(selector as any, callback, false);
}
// -----------------------------------------------------------------------------
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "لصق", "paste": "لصق",
"pasteAsPlaintext": "اللصق كنص عادي", "pasteAsPlaintext": "اللصق كنص عادي",
"pasteCharts": "لصق الرسوم البيانية", "pasteCharts": "لصق الرسوم البيانية",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "تحديد الكل", "selectAll": "تحديد الكل",
"multiSelect": "إضافة عنصر للتحديد", "multiSelect": "إضافة عنصر للتحديد",
"moveCanvas": "نقل لوح الرسم", "moveCanvas": "نقل لوح الرسم",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Crow's foot (many)", "arrowhead_crowfoot_many": "Crow's foot (many)",
"arrowhead_crowfoot_one": "Crow's foot (one)", "arrowhead_crowfoot_one": "Crow's foot (one)",
"arrowhead_crowfoot_one_or_many": "Crow's foot (one or many)", "arrowhead_crowfoot_one_or_many": "Crow's foot (one or many)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "خيارات إضافية", "more_options": "خيارات إضافية",
"cardinality": "",
"arrowtypes": "نوع السهم", "arrowtypes": "نوع السهم",
"arrowtype_sharp": "سهم حاد", "arrowtype_sharp": "سهم حاد",
"arrowtype_round": "سهم منحني", "arrowtype_round": "سهم منحني",
@@ -171,7 +182,11 @@
"linkToElement": "رابط إلى الكائن", "linkToElement": "رابط إلى الكائن",
"wrapSelectionInFrame": "تغليف التحديد في إطار", "wrapSelectionInFrame": "تغليف التحديد في إطار",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "رابط إلى الكائن", "title": "رابط إلى الكائن",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "خطأ" "title": "خطأ"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "حفظ الملف على الجهاز", "disk_title": "حفظ الملف على الجهاز",
"disk_details": "تصدير بيانات المشهد إلى ملف يمكنك الاستيراد منه لاحقًا.", "disk_details": "تصدير بيانات المشهد إلى ملف يمكنك الاستيراد منه لاحقًا.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid إلى Excalidraw", "title": "Mermaid إلى Excalidraw",
"button": "إدراج", "button": "إدراج",
"description": "حاليًا، يتم دعم <flowchartLink>مخططات التدفق</flowchartLink>، <sequenceLink>التسلسلات</sequenceLink>، و<classLink>الفئات</classLink> فقط. سيتم عرض الأنواع الأخرى كصورة في Excalidraw.", "description": "",
"syntax": "صيغة Mermaid", "syntax": "صيغة Mermaid",
"preview": "معاينة", "preview": "معاينة",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Yapışdır", "paste": "Yapışdır",
"pasteAsPlaintext": "Düz mətn kimi yapışdırın", "pasteAsPlaintext": "Düz mətn kimi yapışdırın",
"pasteCharts": "Diaqramları yapışdırın", "pasteCharts": "Diaqramları yapışdırın",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Hamısını seç", "selectAll": "Hamısını seç",
"multiSelect": "Seçimə element əlavə edin", "multiSelect": "Seçimə element əlavə edin",
"moveCanvas": "Kanvası köçürün", "moveCanvas": "Kanvası köçürün",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "" "title": ""
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "", "disk_details": "",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Постави", "paste": "Постави",
"pasteAsPlaintext": "Постави като обикновен текст", "pasteAsPlaintext": "Постави като обикновен текст",
"pasteCharts": "Постави графики", "pasteCharts": "Постави графики",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Маркирай всичко", "selectAll": "Маркирай всичко",
"multiSelect": "Добави елемент към селекция", "multiSelect": "Добави елемент към селекция",
"moveCanvas": "Премести платно", "moveCanvas": "Премести платно",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "Вид стрелка", "arrowtypes": "Вид стрелка",
"arrowtype_sharp": "Остра стрелка", "arrowtype_sharp": "Остра стрелка",
"arrowtype_round": "Извита стрелка", "arrowtype_round": "Извита стрелка",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Грешка" "title": "Грешка"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Запази към диск", "disk_title": "Запази към диск",
"disk_details": "", "disk_details": "",
@@ -616,7 +635,8 @@
"syntax": "Mermaid Синтаксис", "syntax": "Mermaid Синтаксис",
"preview": "Преглед", "preview": "Преглед",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "পেস্ট করুন", "paste": "পেস্ট করুন",
"pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন", "pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন",
"pasteCharts": "চার্ট পেস্ট করুন", "pasteCharts": "চার্ট পেস্ট করুন",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "সবটা সিলেক্ট করুন", "selectAll": "সবটা সিলেক্ট করুন",
"multiSelect": "একাধিক সিলেক্ট করুন", "multiSelect": "একাধিক সিলেক্ট করুন",
"moveCanvas": "ক্যানভাস সরান", "moveCanvas": "ক্যানভাস সরান",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "ত্রুটি" "title": "ত্রুটি"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "", "disk_details": "",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "পেস্ট করুন", "paste": "পেস্ট করুন",
"pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন", "pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন",
"pasteCharts": "চার্ট পেস্ট করুন", "pasteCharts": "চার্ট পেস্ট করুন",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "সবটা সিলেক্ট করুন", "selectAll": "সবটা সিলেক্ট করুন",
"multiSelect": "একাধিক সিলেক্ট করুন", "multiSelect": "একাধিক সিলেক্ট করুন",
"moveCanvas": "ক্যানভাস সরান", "moveCanvas": "ক্যানভাস সরান",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "ত্রুটি" "title": "ত্রুটি"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "", "disk_details": "",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Enganxa", "paste": "Enganxa",
"pasteAsPlaintext": "Enganxar com a text pla", "pasteAsPlaintext": "Enganxar com a text pla",
"pasteCharts": "Enganxa els diagrames", "pasteCharts": "Enganxa els diagrames",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Selecciona-ho tot", "selectAll": "Selecciona-ho tot",
"multiSelect": "Afegeix un element a la selecció", "multiSelect": "Afegeix un element a la selecció",
"moveCanvas": "Mou el llenç", "moveCanvas": "Mou el llenç",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Potes de gall (moltes)", "arrowhead_crowfoot_many": "Potes de gall (moltes)",
"arrowhead_crowfoot_one": "Potes de gall (una)", "arrowhead_crowfoot_one": "Potes de gall (una)",
"arrowhead_crowfoot_one_or_many": "Potes de gall (una o moltes)", "arrowhead_crowfoot_one_or_many": "Potes de gall (una o moltes)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Més opcions", "more_options": "Més opcions",
"cardinality": "",
"arrowtypes": "Tipus de fletxa", "arrowtypes": "Tipus de fletxa",
"arrowtype_sharp": "Fletxa Esmolada", "arrowtype_sharp": "Fletxa Esmolada",
"arrowtype_round": "Fletxa corba", "arrowtype_round": "Fletxa corba",
@@ -171,7 +182,11 @@
"linkToElement": "Enllaç a l'objecte", "linkToElement": "Enllaç a l'objecte",
"wrapSelectionInFrame": "Embolica la selecció en un marc", "wrapSelectionInFrame": "Embolica la selecció en un marc",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Enllaç a l'objecte", "title": "Enllaç a l'objecte",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Error" "title": "Error"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Desa al disc", "disk_title": "Desa al disc",
"disk_details": "Exporta les dades de l'escena a un fitxer que després podreu importar.", "disk_details": "Exporta les dades de l'escena a un fitxer que després podreu importar.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid a Excalidraw", "title": "Mermaid a Excalidraw",
"button": "Inseriu", "button": "Inseriu",
"description": "Actualment només s'admeten els diagrames <flowchartLink>Flowchart</flowchartLink>, <sequenceLink> Sequence, </sequenceLink> i <classLink> Class </classLink>. Els altres tipus es representaran com a imatge a Excalidraw.", "description": "",
"syntax": "Sintaxi de Mermaid", "syntax": "Sintaxi de Mermaid",
"preview": "Previsualització", "preview": "Previsualització",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Vložit", "paste": "Vložit",
"pasteAsPlaintext": "Vložit jako prostý text", "pasteAsPlaintext": "Vložit jako prostý text",
"pasteCharts": "Vložit grafy", "pasteCharts": "Vložit grafy",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Vybrat vše", "selectAll": "Vybrat vše",
"multiSelect": "Přidat prvek do výběru", "multiSelect": "Přidat prvek do výběru",
"moveCanvas": "Posunout plátno", "moveCanvas": "Posunout plátno",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "Typ šipky", "arrowtypes": "Typ šipky",
"arrowtype_sharp": "Ostrá šipka", "arrowtype_sharp": "Ostrá šipka",
"arrowtype_round": "Zakřivená šipka", "arrowtype_round": "Zakřivená šipka",
@@ -171,7 +182,11 @@
"linkToElement": "Odkaz na objekt", "linkToElement": "Odkaz na objekt",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Odkaz na objekt", "title": "Odkaz na objekt",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Chyba" "title": "Chyba"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Uložit na disk", "disk_title": "Uložit na disk",
"disk_details": "Exportovat data scény do souboru, ze kterého můžete importovat později.", "disk_details": "Exportovat data scény do souboru, ze kterého můžete importovat později.",
@@ -616,7 +635,8 @@
"syntax": "Mermaid syntaxe", "syntax": "Mermaid syntaxe",
"preview": "Náhled", "preview": "Náhled",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Indsæt", "paste": "Indsæt",
"pasteAsPlaintext": "Indsæt som klartekst", "pasteAsPlaintext": "Indsæt som klartekst",
"pasteCharts": "Indsæt diagrammer", "pasteCharts": "Indsæt diagrammer",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Marker alle", "selectAll": "Marker alle",
"multiSelect": "Tilføj element til markering", "multiSelect": "Tilføj element til markering",
"moveCanvas": "Flyt lærred", "moveCanvas": "Flyt lærred",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Kragefod (mange)", "arrowhead_crowfoot_many": "Kragefod (mange)",
"arrowhead_crowfoot_one": "Kragefod (én)", "arrowhead_crowfoot_one": "Kragefod (én)",
"arrowhead_crowfoot_one_or_many": "Kragefod (én eller mange)", "arrowhead_crowfoot_one_or_many": "Kragefod (én eller mange)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Flere muligheder", "more_options": "Flere muligheder",
"cardinality": "",
"arrowtypes": "Pile type", "arrowtypes": "Pile type",
"arrowtype_sharp": "Skarp pil", "arrowtype_sharp": "Skarp pil",
"arrowtype_round": "Buet pil", "arrowtype_round": "Buet pil",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Fejl" "title": "Fejl"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Gem til disk", "disk_title": "Gem til disk",
"disk_details": "", "disk_details": "",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Einfügen", "paste": "Einfügen",
"pasteAsPlaintext": "Als unformatierten Text einfügen", "pasteAsPlaintext": "Als unformatierten Text einfügen",
"pasteCharts": "Diagramme einfügen", "pasteCharts": "Diagramme einfügen",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Alle auswählen", "selectAll": "Alle auswählen",
"multiSelect": "Element zur Auswahl hinzufügen", "multiSelect": "Element zur Auswahl hinzufügen",
"moveCanvas": "Leinwand verschieben", "moveCanvas": "Leinwand verschieben",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Krähenfuß (viele)", "arrowhead_crowfoot_many": "Krähenfuß (viele)",
"arrowhead_crowfoot_one": "Krähenfuß (einer)", "arrowhead_crowfoot_one": "Krähenfuß (einer)",
"arrowhead_crowfoot_one_or_many": "Krähenfuß (einer oder viele)", "arrowhead_crowfoot_one_or_many": "Krähenfuß (einer oder viele)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Weitere Optionen", "more_options": "Weitere Optionen",
"cardinality": "",
"arrowtypes": "Pfeiltyp", "arrowtypes": "Pfeiltyp",
"arrowtype_sharp": "Scharfer Pfeil", "arrowtype_sharp": "Scharfer Pfeil",
"arrowtype_round": "Gebogener Pfeil", "arrowtype_round": "Gebogener Pfeil",
@@ -171,7 +182,11 @@
"linkToElement": "Link zum Objekt", "linkToElement": "Link zum Objekt",
"wrapSelectionInFrame": "Auswahl in Rahmen einbetten", "wrapSelectionInFrame": "Auswahl in Rahmen einbetten",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Link zum Objekt", "title": "Link zum Objekt",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Fehler" "title": "Fehler"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Auf Festplatte speichern", "disk_title": "Auf Festplatte speichern",
"disk_details": "Exportiere die Zeichnungsdaten in eine Datei, die Du später importieren kannst.", "disk_details": "Exportiere die Zeichnungsdaten in eine Datei, die Du später importieren kannst.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid zu Excalidraw", "title": "Mermaid zu Excalidraw",
"button": "Einfügen", "button": "Einfügen",
"description": "Derzeit werden nur <flowchartLink>Flussdiagramme</flowchartLink>, <sequenceLink>Sequenzdiagramme</sequenceLink> und <classLink>Klassendiagramme</classLink> unterstützt. Die anderen Typen werden als Bild in Excalidraw dargestellt.", "description": "",
"syntax": "Mermaid-Syntax", "syntax": "Mermaid-Syntax",
"preview": "Vorschau", "preview": "Vorschau",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Einfügen", "paste": "Einfügen",
"pasteAsPlaintext": "Als unformatierten Text einfügen", "pasteAsPlaintext": "Als unformatierten Text einfügen",
"pasteCharts": "Diagramme einfügen", "pasteCharts": "Diagramme einfügen",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Alle auswählen", "selectAll": "Alle auswählen",
"multiSelect": "Element zur Auswahl hinzufügen", "multiSelect": "Element zur Auswahl hinzufügen",
"moveCanvas": "Leinwand verschieben", "moveCanvas": "Leinwand verschieben",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Krähenfuß (viele)", "arrowhead_crowfoot_many": "Krähenfuß (viele)",
"arrowhead_crowfoot_one": "Krähenfuß (einer)", "arrowhead_crowfoot_one": "Krähenfuß (einer)",
"arrowhead_crowfoot_one_or_many": "Krähenfuß (einer oder viele)", "arrowhead_crowfoot_one_or_many": "Krähenfuß (einer oder viele)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Weitere Optionen", "more_options": "Weitere Optionen",
"cardinality": "",
"arrowtypes": "Pfeiltyp", "arrowtypes": "Pfeiltyp",
"arrowtype_sharp": "Scharfer Pfeil", "arrowtype_sharp": "Scharfer Pfeil",
"arrowtype_round": "Gebogener Pfeil", "arrowtype_round": "Gebogener Pfeil",
@@ -171,7 +182,11 @@
"linkToElement": "Link zum Objekt", "linkToElement": "Link zum Objekt",
"wrapSelectionInFrame": "Auswahl in Rahmen einbetten", "wrapSelectionInFrame": "Auswahl in Rahmen einbetten",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Link zum Objekt", "title": "Link zum Objekt",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Fehler" "title": "Fehler"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Auf Festplatte speichern", "disk_title": "Auf Festplatte speichern",
"disk_details": "Exportiere die Zeichnungsdaten in eine Datei, die Du später importieren kannst.", "disk_details": "Exportiere die Zeichnungsdaten in eine Datei, die Du später importieren kannst.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid zu Excalidraw", "title": "Mermaid zu Excalidraw",
"button": "Einfügen", "button": "Einfügen",
"description": "Derzeit werden nur <flowchartLink>Flussdiagramme</flowchartLink>, <sequenceLink>Sequenzdiagramme</sequenceLink> und <classLink>Klassendiagramme</classLink> unterstützt. Die anderen Typen werden als Bild in Excalidraw dargestellt.", "description": "",
"syntax": "Mermaid-Syntax", "syntax": "Mermaid-Syntax",
"preview": "Vorschau", "preview": "Vorschau",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Επικόλληση", "paste": "Επικόλληση",
"pasteAsPlaintext": "Επικόλληση ως απλό κείμενο", "pasteAsPlaintext": "Επικόλληση ως απλό κείμενο",
"pasteCharts": "Επικόλληση γραφημάτων", "pasteCharts": "Επικόλληση γραφημάτων",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Επιλογή όλων", "selectAll": "Επιλογή όλων",
"multiSelect": "Προσθέστε το στοιχείο στην επιλογή", "multiSelect": "Προσθέστε το στοιχείο στην επιλογή",
"moveCanvas": "Μετακίνηση καμβά", "moveCanvas": "Μετακίνηση καμβά",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "Τύπος βέλους", "arrowtypes": "Τύπος βέλους",
"arrowtype_sharp": "Αιχμηρό βέλος", "arrowtype_sharp": "Αιχμηρό βέλος",
"arrowtype_round": "Κυρτό βέλος", "arrowtype_round": "Κυρτό βέλος",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Σφάλμα" "title": "Σφάλμα"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Αποθήκευση στο δίσκο", "disk_title": "Αποθήκευση στο δίσκο",
"disk_details": "Εξαγωγή δεδομένων σκηνής σε ένα αρχείο από το οποίο μπορείτε να εισάγετε αργότερα.", "disk_details": "Εξαγωγή δεδομένων σκηνής σε ένα αρχείο από το οποίο μπορείτε να εισάγετε αργότερα.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid σε Excalidraw", "title": "Mermaid σε Excalidraw",
"button": "Εισαγωγή", "button": "Εισαγωγή",
"description": "Επί του παρόντος υποστηρίζονται μόνο Διαγράμματα <flowchartLink>Ροής</flowchartLink>,<sequenceLink> Ακολουθίας, </sequenceLink> και <classLink>Κλάσεων</classLink>. Οι άλλοι τύποι θα αποδοθούν ως εικόνα στο Excalidraw.", "description": "",
"syntax": "Σύνταξη Mermaid", "syntax": "Σύνταξη Mermaid",
"preview": "Προεπισκόπηση", "preview": "Προεπισκόπηση",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+1 -6
View File
@@ -403,10 +403,6 @@
"errorDialog": { "errorDialog": {
"title": "Error" "title": "Error"
}, },
"progressDialog": {
"title": "Saving",
"defaultMessage": "Preparing to save..."
},
"exportDialog": { "exportDialog": {
"disk_title": "Save to disk", "disk_title": "Save to disk",
"disk_details": "Export the scene data to a file from which you can import later.", "disk_details": "Export the scene data to a file from which you can import later.",
@@ -628,8 +624,7 @@
"syntax": "Mermaid Syntax", "syntax": "Mermaid Syntax",
"preview": "Preview", "preview": "Preview",
"label": "Mermaid", "label": "Mermaid",
"inputPlaceholder": "Write Mermaid diagram defintion here...", "inputPlaceholder": "Write Mermaid diagram defintion here..."
"autoFixAvailable": "Auto-fix is available"
}, },
"ttd": { "ttd": {
"error": "Error!" "error": "Error!"
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Pegar", "paste": "Pegar",
"pasteAsPlaintext": "Pegar como texto sin formato", "pasteAsPlaintext": "Pegar como texto sin formato",
"pasteCharts": "Pegar gráficos", "pasteCharts": "Pegar gráficos",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Seleccionar todo", "selectAll": "Seleccionar todo",
"multiSelect": "Añadir elemento a la selección", "multiSelect": "Añadir elemento a la selección",
"moveCanvas": "Mover el lienzo", "moveCanvas": "Mover el lienzo",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Pie de la corona (varios)", "arrowhead_crowfoot_many": "Pie de la corona (varios)",
"arrowhead_crowfoot_one": "Pie de la corona (uno)", "arrowhead_crowfoot_one": "Pie de la corona (uno)",
"arrowhead_crowfoot_one_or_many": "Pie de la corona (uno o varios)", "arrowhead_crowfoot_one_or_many": "Pie de la corona (uno o varios)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Más opciones", "more_options": "Más opciones",
"cardinality": "",
"arrowtypes": "Tipo de flecha", "arrowtypes": "Tipo de flecha",
"arrowtype_sharp": "Flecha Afilada", "arrowtype_sharp": "Flecha Afilada",
"arrowtype_round": "Flecha Curva", "arrowtype_round": "Flecha Curva",
@@ -171,7 +182,11 @@
"linkToElement": "Enlace al objeto", "linkToElement": "Enlace al objeto",
"wrapSelectionInFrame": "Ajustar la selección en el marco", "wrapSelectionInFrame": "Ajustar la selección en el marco",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Enlace al objeto", "title": "Enlace al objeto",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Error" "title": "Error"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Guardar en disco", "disk_title": "Guardar en disco",
"disk_details": "Exportar los datos de la escena a un archivo desde el cual pueda importar más tarde.", "disk_details": "Exportar los datos de la escena a un archivo desde el cual pueda importar más tarde.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid a Excalidraw", "title": "Mermaid a Excalidraw",
"button": "Insertar", "button": "Insertar",
"description": "Actualmente sólo estos tipos de <flowchartLink>diagrama de flujo</flowchartLink>,<sequenceLink> Secuencia, </sequenceLink> y <classLink>Clase </classLink> son soportados. Los otros tipos de diagramas se renderizarán como imagen en Excalidraw.", "description": "",
"syntax": "Sintaxis Mermaid", "syntax": "Sintaxis Mermaid",
"preview": "Vista previa", "preview": "Vista previa",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Itsatsi", "paste": "Itsatsi",
"pasteAsPlaintext": "Itsatsi testu arrunt gisa", "pasteAsPlaintext": "Itsatsi testu arrunt gisa",
"pasteCharts": "Itsatsi grafikoak", "pasteCharts": "Itsatsi grafikoak",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Hautatu dena", "selectAll": "Hautatu dena",
"multiSelect": "Gehitu elementua hautapenera", "multiSelect": "Gehitu elementua hautapenera",
"moveCanvas": "Mugitu oihala", "moveCanvas": "Mugitu oihala",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Errorea" "title": "Errorea"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Gorde diskoan", "disk_title": "Gorde diskoan",
"disk_details": "Esportatu eszenaren datuak geroago inportatu ahal izango duzun fitxategi batan.", "disk_details": "Esportatu eszenaren datuak geroago inportatu ahal izango duzun fitxategi batan.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid-etik Excalidraw-ra", "title": "Mermaid-etik Excalidraw-ra",
"button": "Txertatu", "button": "Txertatu",
"description": "Momentu honetan <flowchartLink>Flowchart</flowchartLink>, <sequenceLink> Sequence, </sequenceLink> eta <classLink>Class </classLink>Diagramak onartzen dira. Beste motak irudi gisa errendatuko dira Excalidrawn.", "description": "",
"syntax": "Mermaid sintaxia", "syntax": "Mermaid sintaxia",
"preview": "Aurrebista", "preview": "Aurrebista",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+31 -11
View File
@@ -3,6 +3,10 @@
"paste": "جایگذاری", "paste": "جایگذاری",
"pasteAsPlaintext": "جایگذاری به عنوان متن ساده", "pasteAsPlaintext": "جایگذاری به عنوان متن ساده",
"pasteCharts": "جایگذاری نمودارها", "pasteCharts": "جایگذاری نمودارها",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "انتخاب همه", "selectAll": "انتخاب همه",
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید", "multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید",
"moveCanvas": "جابجایی بوم", "moveCanvas": "جابجایی بوم",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "پای کلاغی (بسیار)", "arrowhead_crowfoot_many": "پای کلاغی (بسیار)",
"arrowhead_crowfoot_one": "پای کلاغی (یک)", "arrowhead_crowfoot_one": "پای کلاغی (یک)",
"arrowhead_crowfoot_one_or_many": "پای کلاغی (یک یا بسیار)", "arrowhead_crowfoot_one_or_many": "پای کلاغی (یک یا بسیار)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "امکانات بیشتر", "more_options": "امکانات بیشتر",
"cardinality": "",
"arrowtypes": "نوع پیکان", "arrowtypes": "نوع پیکان",
"arrowtype_sharp": "پیکان تیز", "arrowtype_sharp": "پیکان تیز",
"arrowtype_round": "پیکان منحنی", "arrowtype_round": "پیکان منحنی",
@@ -171,7 +182,11 @@
"linkToElement": "لینک به آیتم", "linkToElement": "لینک به آیتم",
"wrapSelectionInFrame": "انتخاب را در قاب قرار دهید", "wrapSelectionInFrame": "انتخاب را در قاب قرار دهید",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "لینک به آیتم", "title": "لینک به آیتم",
@@ -196,7 +211,7 @@
"multipleResults": "نتایج", "multipleResults": "نتایج",
"placeholder": "جستجوی متن در بوم...", "placeholder": "جستجوی متن در بوم...",
"frames": "", "frames": "",
"texts": "" "texts": "متن"
}, },
"buttons": { "buttons": {
"clearReset": "پاکسازی بوم نقاشی", "clearReset": "پاکسازی بوم نقاشی",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "خطا" "title": "خطا"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "ذخیره در دیسک", "disk_title": "ذخیره در دیسک",
"disk_details": "داده های صحنه را به فایلی که بعداً می توانید از آن وارد کنید صادر کنید.", "disk_details": "داده های صحنه را به فایلی که بعداً می توانید از آن وارد کنید صادر کنید.",
@@ -571,7 +590,7 @@
} }
}, },
"colorPicker": { "colorPicker": {
"color": "", "color": "رنگ",
"mostUsedCustomColors": "رنگ های به‌تازگی به‌کار گرفته شده", "mostUsedCustomColors": "رنگ های به‌تازگی به‌کار گرفته شده",
"colors": "رنگ‌ها", "colors": "رنگ‌ها",
"shades": "جلوه‌ها", "shades": "جلوه‌ها",
@@ -612,14 +631,15 @@
"mermaid": { "mermaid": {
"title": "مرمید به excalidraw", "title": "مرمید به excalidraw",
"button": "درج", "button": "درج",
"description": "فعلا فقط <flowchartLink> فلوچارت </flowchartLink> ، <sequenceLink> توالی </sequenceLink> و <classLink> کلاس </classLink> نمودارها پشتیبانی می شوند. انواع دیگر به صورت تصویر در Excalidraw ارائه خواهند شد.", "description": "",
"syntax": "مرمید syntax", "syntax": "مرمید syntax",
"preview": "پیشنمایش", "preview": "پیشنمایش",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": "خطا!"
}, },
"chat": { "chat": {
"inputPlaceholder": "", "inputPlaceholder": "",
@@ -627,8 +647,8 @@
"generating": "", "generating": "",
"rateLimitRemaining": "", "rateLimitRemaining": "",
"role": { "role": {
"user": "", "user": "شما",
"assistant": "", "assistant": "دستیار هوش مصنوعی",
"system": "" "system": ""
}, },
"aiBeta": "", "aiBeta": "",
@@ -707,9 +727,9 @@
"alt": "", "alt": "",
"escape": "", "escape": "",
"enter": "", "enter": "",
"shift": "", "shift": "Shift",
"spacebar": "", "spacebar": "فاصله",
"delete": "", "delete": "حذف",
"mmb": "" "mmb": ""
} }
} }
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Liitä", "paste": "Liitä",
"pasteAsPlaintext": "Liitä pelkkänä tekstinä", "pasteAsPlaintext": "Liitä pelkkänä tekstinä",
"pasteCharts": "Liitä kaaviot", "pasteCharts": "Liitä kaaviot",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Valitse kaikki", "selectAll": "Valitse kaikki",
"multiSelect": "Lisää kohde valintaan", "multiSelect": "Lisää kohde valintaan",
"moveCanvas": "Siirrä piirtoaluetta", "moveCanvas": "Siirrä piirtoaluetta",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Virhe" "title": "Virhe"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Tallenna levylle", "disk_title": "Tallenna levylle",
"disk_details": "Vie työn tiedot tiedostoon, josta sen voi tuoda myöhemmin.", "disk_details": "Vie työn tiedot tiedostoon, josta sen voi tuoda myöhemmin.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "Esikatsele", "preview": "Esikatsele",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+60 -40
View File
@@ -3,6 +3,10 @@
"paste": "Coller", "paste": "Coller",
"pasteAsPlaintext": "Coller comme texte brut", "pasteAsPlaintext": "Coller comme texte brut",
"pasteCharts": "Coller les graphiques", "pasteCharts": "Coller les graphiques",
"chartType_bar": "Graphique à barres",
"chartType_line": "Courbes",
"chartType_radar": "Diagramme en radar",
"chartType_plaintext": "Texte brut",
"selectAll": "Tout sélectionner", "selectAll": "Tout sélectionner",
"multiSelect": "Ajouter l'élément à la sélection", "multiSelect": "Ajouter l'élément à la sélection",
"moveCanvas": "Déplacer le canevas", "moveCanvas": "Déplacer le canevas",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "Pied de Corde (un)", "arrowhead_crowfoot_one": "Pied de Corde (un)",
"arrowhead_crowfoot_one_or_many": "Pied du corbeau (un ou plusieurs)", "arrowhead_crowfoot_one_or_many": "Pied du corbeau (un ou plusieurs)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Plus d'options", "more_options": "Plus d'options",
"cardinality": "",
"arrowtypes": "Type de flèche", "arrowtypes": "Type de flèche",
"arrowtype_sharp": "Flèche pointue", "arrowtype_sharp": "Flèche pointue",
"arrowtype_round": "Flèche incurvée", "arrowtype_round": "Flèche incurvée",
@@ -171,7 +182,11 @@
"linkToElement": "Lien vers un objet", "linkToElement": "Lien vers un objet",
"wrapSelectionInFrame": "Mettre la sélection dans un cadre", "wrapSelectionInFrame": "Mettre la sélection dans un cadre",
"tab": "Onglet", "tab": "Onglet",
"shapeSwitch": "Changer de forme" "shapeSwitch": "Changer de forme",
"preferences": "Préférences",
"preferences_toolLock": "Verrouillage de l'outil",
"arrowBinding": "Liaison de flèches",
"midpointSnapping": "S'accrocher aux points intermédiaires"
}, },
"elementLink": { "elementLink": {
"title": "Lien vers un objet", "title": "Lien vers un objet",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Erreur" "title": "Erreur"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Enregistrer sur le disque", "disk_title": "Enregistrer sur le disque",
"disk_details": "Exporter les données de la scène comme un fichier que vous pourrez importer ultérieurement.", "disk_details": "Exporter les données de la scène comme un fichier que vous pourrez importer ultérieurement.",
@@ -557,9 +576,9 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "Vos dessins sont enregistrés dans le stockage de votre navigateur.",
"center_heading_line2": "", "center_heading_line2": "Le stockage du navigateur peut être effacé de manière inattendue.",
"center_heading_line3": "", "center_heading_line3": "Enregistrez régulièrement votre travail dans un fichier pour éviter de le perdre.",
"center_heading_plus": "Vouliez-vous plutôt aller à Excalidraw+ à la place ?", "center_heading_plus": "Vouliez-vous plutôt aller à Excalidraw+ à la place ?",
"menuHint": "Exportation, préférences, langues, ..." "menuHint": "Exportation, préférences, langues, ..."
}, },
@@ -612,57 +631,58 @@
"mermaid": { "mermaid": {
"title": "De Mermaid à Excalidraw", "title": "De Mermaid à Excalidraw",
"button": "Insérer", "button": "Insérer",
"description": "Actuellement, seuls les diagrammes <flowchartLink>Flowchart</flowchartLink>,<sequenceLink> Sequence, </sequenceLink> et <classLink>de classe </classLink>sont pris en charge. Les autres types seront rendus en tant qu'image dans Excalidraw.", "description": "",
"syntax": "Syntaxe Mermaid", "syntax": "Syntaxe Mermaid",
"preview": "Prévisualisation", "preview": "Prévisualisation",
"label": "", "label": "Mermaid",
"inputPlaceholder": "" "inputPlaceholder": "Écrivez la définition du diagramme de Mermaid ici...",
"autoFixAvailable": "Correction automatique disponible"
}, },
"ttd": { "ttd": {
"error": "" "error": "Erreur!"
}, },
"chat": { "chat": {
"inputPlaceholder": "", "inputPlaceholder": "Commencez à taper votre idée de diagramme ici... ({{shortcut}} pour une nouvelle ligne)",
"inputPlaceholderWithMessages": "", "inputPlaceholderWithMessages": "Continuer à affiner votre diagramme...",
"generating": "", "generating": "Génération...",
"rateLimitRemaining": "", "rateLimitRemaining": "{{count}} demandes restantes aujourd'hui",
"role": { "role": {
"user": "", "user": "Vous",
"assistant": "", "assistant": "Assistant IA",
"system": "" "system": "Système"
}, },
"aiBeta": "", "aiBeta": "Bêta IA",
"label": "", "label": "Chat",
"menu": "", "menu": "Menu",
"newChat": "", "newChat": "Nouveau chat",
"deleteChat": "", "deleteChat": "Supprimer le chat",
"deleteMessage": "", "deleteMessage": "Supprimer le message",
"viewAsMermaid": "", "viewAsMermaid": "Voir en tant que Mermaid",
"placeholder": { "placeholder": {
"title": "", "title": "Créons votre diagramme",
"description": "", "description": "Décrivez le diagramme que vous voulez créer, et nous le générerons pour vous.",
"hint": "" "hint": ""
}, },
"preview": "", "preview": "Prévisualisation",
"insert": "", "insert": "Insérer",
"retry": "", "retry": "Recommencer",
"errors": { "errors": {
"promptTooShort": "", "promptTooShort": "La requête est trop courte (min {{min}} caractères)",
"promptTooLong": "", "promptTooLong": "La requête est trop longue (max {{max}} caractères)",
"generationFailed": "", "generationFailed": "Échec de la génération",
"invalidDiagram": "", "invalidDiagram": "A généré un diagramme non valide :(. Vous pouvez modifier manuellement, réessayer avec la correction automatique, ou essayer une invite différente.",
"fixInMermaid": "", "fixInMermaid": "Modifier Mermaid manuellement →",
"aiRepair": "", "aiRepair": "Régénérer (correction automatique) →",
"requestAborted": "", "requestAborted": "Demande abandonnée",
"requestFailed": "", "requestFailed": "La requête a échoué",
"mermaidParseError": "" "mermaidParseError": "Erreur de syntaxe Mermaid"
}, },
"rateLimit": { "rateLimit": {
"messageLimit": "", "messageLimit": "Vous avez atteint votre limite d'IA sur le plan gratuit. Essayez Excalidraw+ pour plus ou revenez demain.",
"generalRateLimit": "", "generalRateLimit": "Tenez vos chevaux, vous êtes trop rapide pour nous! Veuillez attendre un instant avant de réessayer.",
"messageLimitInputPlaceholder": "" "messageLimitInputPlaceholder": "Vous avez atteint votre limite de messages"
}, },
"upsellBtnLabel": "" "upsellBtnLabel": "Devenez Premium"
}, },
"quickSearch": { "quickSearch": {
"placeholder": "Recherche rapide" "placeholder": "Recherche rapide"
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Pegar", "paste": "Pegar",
"pasteAsPlaintext": "Pegar coma texto sen formato", "pasteAsPlaintext": "Pegar coma texto sen formato",
"pasteCharts": "Pegar gráficos", "pasteCharts": "Pegar gráficos",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Seleccionar todo", "selectAll": "Seleccionar todo",
"multiSelect": "Engadir elemento á selección", "multiSelect": "Engadir elemento á selección",
"moveCanvas": "Mover o lenzo", "moveCanvas": "Mover o lenzo",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Erro" "title": "Erro"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Gardar no disco", "disk_title": "Gardar no disco",
"disk_details": "Exporte os datos da escena a un ficheiro que poderás importar máis tarde.", "disk_details": "Exporte os datos da escena a un ficheiro que poderás importar máis tarde.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "Vista previa", "preview": "Vista previa",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "הדבקה", "paste": "הדבקה",
"pasteAsPlaintext": "הדבקה ללא עיצוב", "pasteAsPlaintext": "הדבקה ללא עיצוב",
"pasteCharts": "הדבקת תרשימים", "pasteCharts": "הדבקת תרשימים",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "בחירה בהכול", "selectAll": "בחירה בהכול",
"multiSelect": "הוספת רכיב לבחירה", "multiSelect": "הוספת רכיב לבחירה",
"moveCanvas": "הזזת הקנבס", "moveCanvas": "הזזת הקנבס",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "רגל עורב (הרבה)", "arrowhead_crowfoot_many": "רגל עורב (הרבה)",
"arrowhead_crowfoot_one": "רגל עורב (אחת)", "arrowhead_crowfoot_one": "רגל עורב (אחת)",
"arrowhead_crowfoot_one_or_many": "רגל עורב (אחת או הרבה)", "arrowhead_crowfoot_one_or_many": "רגל עורב (אחת או הרבה)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "אפשרויות נוספות", "more_options": "אפשרויות נוספות",
"cardinality": "",
"arrowtypes": "סוג החץ", "arrowtypes": "סוג החץ",
"arrowtype_sharp": "חץ מחודד", "arrowtype_sharp": "חץ מחודד",
"arrowtype_round": "חץ מעוקל", "arrowtype_round": "חץ מעוקל",
@@ -171,7 +182,11 @@
"linkToElement": "קישור לפריט", "linkToElement": "קישור לפריט",
"wrapSelectionInFrame": "לעטוף את הבחירה במסגרת", "wrapSelectionInFrame": "לעטוף את הבחירה במסגרת",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "קישור לפריט", "title": "קישור לפריט",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "שגיאה" "title": "שגיאה"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "שמור לכונן", "disk_title": "שמור לכונן",
"disk_details": "ייצא מידע של הקנבאס לקובץ שתוכל לייבא אחר כך.", "disk_details": "ייצא מידע של הקנבאס לקובץ שתוכל לייבא אחר כך.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid ל־Excalidraw", "title": "Mermaid ל־Excalidraw",
"button": "הוספה", "button": "הוספה",
"description": "לעת עתה נתמכים רק <flowchartLink>תרשימי זרימה</flowchartLink>, <sequenceLink>תהליכים</sequenceLink>, <classLink>ודיאגרמת מחלקה</classLink>. שאר הסוגים ייוצרו כתמונות ב-Excalidraw.", "description": "",
"syntax": "תחביר Mermaid", "syntax": "תחביר Mermaid",
"preview": "תצוגה מקדימה", "preview": "תצוגה מקדימה",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "पेस्ट", "paste": "पेस्ट",
"pasteAsPlaintext": "सादे पाठ के रूप में चिपकाएं", "pasteAsPlaintext": "सादे पाठ के रूप में चिपकाएं",
"pasteCharts": "चार्ट चिपकाएँ", "pasteCharts": "चार्ट चिपकाएँ",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "सेलेक्ट ऑल", "selectAll": "सेलेक्ट ऑल",
"multiSelect": "अवयव को चयन में सम्मिलित करें", "multiSelect": "अवयव को चयन में सम्मिलित करें",
"moveCanvas": "चित्रपटल को स्थानांतरित करें", "moveCanvas": "चित्रपटल को स्थानांतरित करें",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "चिड़िया पैर (अनेक)", "arrowhead_crowfoot_many": "चिड़िया पैर (अनेक)",
"arrowhead_crowfoot_one": "चिड़िया पैर (एक)", "arrowhead_crowfoot_one": "चिड़िया पैर (एक)",
"arrowhead_crowfoot_one_or_many": "चिड़िया पैर (एक या अनेक)", "arrowhead_crowfoot_one_or_many": "चिड़िया पैर (एक या अनेक)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "और विकल्प", "more_options": "और विकल्प",
"cardinality": "",
"arrowtypes": "तीर प्रकार", "arrowtypes": "तीर प्रकार",
"arrowtype_sharp": "तीक्ष्ण तीर", "arrowtype_sharp": "तीक्ष्ण तीर",
"arrowtype_round": "गोलाकार तीर", "arrowtype_round": "गोलाकार तीर",
@@ -171,7 +182,11 @@
"linkToElement": "वस्तु की कड़ी", "linkToElement": "वस्तु की कड़ी",
"wrapSelectionInFrame": "चौकट में चुने हुवे को लपेटे", "wrapSelectionInFrame": "चौकट में चुने हुवे को लपेटे",
"tab": "", "tab": "",
"shapeSwitch": "आकार बदलें" "shapeSwitch": "आकार बदलें",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "वस्तु की कड़ी", "title": "वस्तु की कड़ी",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "त्रुटि" "title": "त्रुटि"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "डिस्क पर सुरक्षित करे", "disk_title": "डिस्क पर सुरक्षित करे",
"disk_details": "दृश्य डेटा एक फ़ाइल में निर्यात करे, जहांसे आप उसे पुनः आयात कर सके", "disk_details": "दृश्य डेटा एक फ़ाइल में निर्यात करे, जहांसे आप उसे पुनः आयात कर सके",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "मर्मेड से एक्सकाली में", "title": "मर्मेड से एक्सकाली में",
"button": "सन्निवेश करे", "button": "सन्निवेश करे",
"description": "वर्तमान में केवल <flowchartLink>बहाव चित्र</flowchartLink>, <sequenceLink> अनुक्रम चित्र </sequenceLink> और <classLink>वर्ग चित्र</classLink> का चित्रिकरण संभव हैं. अन्य चित्र प्रकार एक्सकाली प्रतिमा जैसे चित्रित किए जायेंगे.", "description": "",
"syntax": "मर्मेड विन्यास", "syntax": "मर्मेड विन्यास",
"preview": "पूर्वावलोकन", "preview": "पूर्वावलोकन",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Beillesztés", "paste": "Beillesztés",
"pasteAsPlaintext": "Beillesztés formázatlan szövegként", "pasteAsPlaintext": "Beillesztés formázatlan szövegként",
"pasteCharts": "Grafikon beillesztése", "pasteCharts": "Grafikon beillesztése",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Összes kijelölése", "selectAll": "Összes kijelölése",
"multiSelect": "Elem hozzáadása a kijelöléshez", "multiSelect": "Elem hozzáadása a kijelöléshez",
"moveCanvas": "Vászon mozgatása", "moveCanvas": "Vászon mozgatása",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "Nyíl típus", "arrowtypes": "Nyíl típus",
"arrowtype_sharp": "Hegyes nyíl", "arrowtype_sharp": "Hegyes nyíl",
"arrowtype_round": "Ívelt nyíl", "arrowtype_round": "Ívelt nyíl",
@@ -171,7 +182,11 @@
"linkToElement": "Hivatkozás az objektumhoz", "linkToElement": "Hivatkozás az objektumhoz",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "Tab", "tab": "Tab",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Hivatkozás az objektumhoz", "title": "Hivatkozás az objektumhoz",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Hiba" "title": "Hiba"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Mentés lemezre", "disk_title": "Mentés lemezre",
"disk_details": "Exportálja a jelenetadatokat egy fájlba, amelyből később importálhatja.", "disk_details": "Exportálja a jelenetadatokat egy fájlba, amelyből később importálhatja.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Tempel", "paste": "Tempel",
"pasteAsPlaintext": "Tempel sebagai teks biasa", "pasteAsPlaintext": "Tempel sebagai teks biasa",
"pasteCharts": "Tempel diagram", "pasteCharts": "Tempel diagram",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Pilih semua", "selectAll": "Pilih semua",
"multiSelect": "Tambah unsur ke seleksi", "multiSelect": "Tambah unsur ke seleksi",
"moveCanvas": "Pindahkan kanvas", "moveCanvas": "Pindahkan kanvas",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Pilihan lain", "more_options": "Pilihan lain",
"cardinality": "",
"arrowtypes": "Jenis panah", "arrowtypes": "Jenis panah",
"arrowtype_sharp": "Panah runcing", "arrowtype_sharp": "Panah runcing",
"arrowtype_round": "Panah melengkung", "arrowtype_round": "Panah melengkung",
@@ -171,7 +182,11 @@
"linkToElement": "Taut ke objek", "linkToElement": "Taut ke objek",
"wrapSelectionInFrame": "Bungkus pilihan dalam bingkai", "wrapSelectionInFrame": "Bungkus pilihan dalam bingkai",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Taut ke objek", "title": "Taut ke objek",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Kesalahan" "title": "Kesalahan"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Simpan ke disk", "disk_title": "Simpan ke disk",
"disk_details": "Ekspor data pemandangan ke file yang mana Anda dapat impor nanti.", "disk_details": "Ekspor data pemandangan ke file yang mana Anda dapat impor nanti.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid menjadi Excalidraw", "title": "Mermaid menjadi Excalidraw",
"button": "Sisipkan", "button": "Sisipkan",
"description": "Saat ini hanya <flowchartLink>Flowchart</flowchartLink>, <sequenceLink>Sekuen, </sequenceLink>, dan <classLink>Kelas</classLink>Diagram yang didukung. Jenis lainnya akan dirender sebagai gambar di Excalidraw.", "description": "",
"syntax": "Syntax Mermaid", "syntax": "Syntax Mermaid",
"preview": "Pratinjau", "preview": "Pratinjau",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+27 -7
View File
@@ -3,6 +3,10 @@
"paste": "Incolla", "paste": "Incolla",
"pasteAsPlaintext": "Incolla come testo normale", "pasteAsPlaintext": "Incolla come testo normale",
"pasteCharts": "Incolla grafici", "pasteCharts": "Incolla grafici",
"chartType_bar": "Grafico a barre",
"chartType_line": "Grafico a linee",
"chartType_radar": "Grafico radar",
"chartType_plaintext": "Testo semplice",
"selectAll": "Seleziona tutto", "selectAll": "Seleziona tutto",
"multiSelect": "Aggiungi elemento alla selezione", "multiSelect": "Aggiungi elemento alla selezione",
"moveCanvas": "Sposta tela", "moveCanvas": "Sposta tela",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Zampe di gallina (molti)", "arrowhead_crowfoot_many": "Zampe di gallina (molti)",
"arrowhead_crowfoot_one": "Zampa di gallina (una)", "arrowhead_crowfoot_one": "Zampa di gallina (una)",
"arrowhead_crowfoot_one_or_many": "Zampe di gallina (una o molte)", "arrowhead_crowfoot_one_or_many": "Zampe di gallina (una o molte)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Altre opzioni", "more_options": "Altre opzioni",
"cardinality": "",
"arrowtypes": "Tipo di freccia", "arrowtypes": "Tipo di freccia",
"arrowtype_sharp": "Freccia affilata", "arrowtype_sharp": "Freccia affilata",
"arrowtype_round": "Freccia curva", "arrowtype_round": "Freccia curva",
@@ -171,7 +182,11 @@
"linkToElement": "Link all'oggetto", "linkToElement": "Link all'oggetto",
"wrapSelectionInFrame": "Avvolgi la selezione nella cornice", "wrapSelectionInFrame": "Avvolgi la selezione nella cornice",
"tab": "Scheda", "tab": "Scheda",
"shapeSwitch": "Cambia forma" "shapeSwitch": "Cambia forma",
"preferences": "Preferenze",
"preferences_toolLock": "Blocco strumento",
"arrowBinding": "Legatura a freccia",
"midpointSnapping": "Aggancia ai punti medi"
}, },
"elementLink": { "elementLink": {
"title": "Link all'oggetto", "title": "Link all'oggetto",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Errore" "title": "Errore"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Salva su disco", "disk_title": "Salva su disco",
"disk_details": "Esporta i dati della scena su file, dal quale potrai importare in seguito.", "disk_details": "Esporta i dati della scena su file, dal quale potrai importare in seguito.",
@@ -557,9 +576,9 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "I tuoi disegni vengono salvati nella memoria del tuo browser.",
"center_heading_line2": "", "center_heading_line2": "La memoria del browser potrebbe essere cancellata inaspettatamente.",
"center_heading_line3": "", "center_heading_line3": "Salva regolarmente il tuo lavoro in un file per evitare di perderlo.",
"center_heading_plus": "Volevi invece andare su Excalidraw+?", "center_heading_plus": "Volevi invece andare su Excalidraw+?",
"menuHint": "Esporta, preferenze, lingue, ..." "menuHint": "Esporta, preferenze, lingue, ..."
}, },
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Da Mermaid a Excalidraw", "title": "Da Mermaid a Excalidraw",
"button": "Inserisci", "button": "Inserisci",
"description": "Attualmente sono supportati solo diagrammi di <flowchartLink>flusso</flowchartLink>,<sequenceLink> sequenza, </sequenceLink> e <classLink>classe </classLink>. Gli altri tipi saranno rappresentati come immagini in Excalidraw.", "description": "",
"syntax": "Sintassi Mermaid", "syntax": "Sintassi Mermaid",
"preview": "Anteprima", "preview": "Anteprima",
"label": "Mermaid", "label": "Mermaid",
"inputPlaceholder": "Scrivi qui la definizione del diagramma Mermaid..." "inputPlaceholder": "Scrivi qui la definizione del diagramma Mermaid...",
"autoFixAvailable": "Correzione automatica disponibile"
}, },
"ttd": { "ttd": {
"error": "Errore!" "error": "Errore!"
@@ -641,7 +661,7 @@
"placeholder": { "placeholder": {
"title": "Progettiamo il tuo diagramma", "title": "Progettiamo il tuo diagramma",
"description": "Descrivi il diagramma che vuoi creare e noi lo genereremo per te.", "description": "Descrivi il diagramma che vuoi creare e noi lo genereremo per te.",
"hint": "Al momento conosciamo i diagrammi di flusso, di sequenza e di classe." "hint": ""
}, },
"preview": "Anteprima", "preview": "Anteprima",
"insert": "Inserisci", "insert": "Inserisci",
+162 -142
View File
@@ -3,6 +3,10 @@
"paste": "貼り付け", "paste": "貼り付け",
"pasteAsPlaintext": "書式なしテキストとして貼り付け", "pasteAsPlaintext": "書式なしテキストとして貼り付け",
"pasteCharts": "チャートの貼り付け", "pasteCharts": "チャートの貼り付け",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "すべて選択", "selectAll": "すべて選択",
"multiSelect": "複数選択", "multiSelect": "複数選択",
"moveCanvas": "キャンバスを移動", "moveCanvas": "キャンバスを移動",
@@ -34,7 +38,7 @@
"opacity": "透明度", "opacity": "透明度",
"textAlign": "文字の配置", "textAlign": "文字の配置",
"edges": "角", "edges": "角",
"sharp": "四角", "sharp": "シャープ",
"round": "丸", "round": "丸",
"arrowheads": "線の終点", "arrowheads": "線の終点",
"arrowhead_none": "なし", "arrowhead_none": "なし",
@@ -46,14 +50,21 @@
"arrowhead_triangle_outline": "三角 (中抜き)", "arrowhead_triangle_outline": "三角 (中抜き)",
"arrowhead_diamond": "ひし形", "arrowhead_diamond": "ひし形",
"arrowhead_diamond_outline": "ひし形 (中抜き)", "arrowhead_diamond_outline": "ひし形 (中抜き)",
"arrowhead_crowfoot_many": "鳥の足記法(多対多)", "arrowhead_crowfoot_many": "カラスの足 (*)",
"arrowhead_crowfoot_one": "鳥の足記法(一対一)", "arrowhead_crowfoot_one": "カラスの足 (1)",
"arrowhead_crowfoot_one_or_many": "鳥の足記法(一対多)", "arrowhead_crowfoot_one_or_many": "カラスの足 (1..*)",
"more_options": "詳細設定", "arrowhead_cardinality_one": "",
"arrowtypes": "矢印の種類", "arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "その他のオプション",
"cardinality": "",
"arrowtypes": "矢印タイプ",
"arrowtype_sharp": "鋭い矢印", "arrowtype_sharp": "鋭い矢印",
"arrowtype_round": "曲線矢印", "arrowtype_round": "曲線矢印",
"arrowtype_elbowed": "ひじ矢印", "arrowtype_elbowed": "折れ線矢印",
"fontSize": "フォントの大きさ", "fontSize": "フォントの大きさ",
"fontFamily": "フォントの種類", "fontFamily": "フォントの種類",
"addWatermark": "\"Made with Excalidraw\"と表示", "addWatermark": "\"Made with Excalidraw\"と表示",
@@ -75,14 +86,14 @@
"right": "右寄せ", "right": "右寄せ",
"extraBold": "極太", "extraBold": "極太",
"architect": "建築家", "architect": "建築家",
"artist": "アーティスト", "artist": "画家",
"cartoonist": "漫画家", "cartoonist": "漫画家",
"fileTitle": "ファイル名", "fileTitle": "ファイル名",
"colorPicker": "カラーピッカー", "colorPicker": "カラーピッカー",
"canvasColors": "キャンバス上で使用", "canvasColors": "キャンバス上で使用",
"canvasBackground": "キャンバスの背景", "canvasBackground": "キャンバスの背景",
"drawingCanvas": "キャンバスの描画", "drawingCanvas": "キャンバスの描画",
"clearCanvas": "キャンバスを片付ける", "clearCanvas": "キャンバスを消去",
"layers": "レイヤー", "layers": "レイヤー",
"actions": "操作", "actions": "操作",
"language": "言語", "language": "言語",
@@ -101,7 +112,7 @@
"libraryLoadingMessage": "ライブラリを読み込み中…", "libraryLoadingMessage": "ライブラリを読み込み中…",
"libraries": "ライブラリを参照する", "libraries": "ライブラリを参照する",
"loadingScene": "シーンを読み込み中…", "loadingScene": "シーンを読み込み中…",
"loadScene": "ファイルからシーン", "loadScene": "ファイルからシーンを開く",
"align": "配置", "align": "配置",
"alignTop": "上揃え", "alignTop": "上揃え",
"alignBottom": "下揃え", "alignBottom": "下揃え",
@@ -160,18 +171,22 @@
"prompt": "プロンプト", "prompt": "プロンプト",
"followUs": "フォローする", "followUs": "フォローする",
"discordChat": "Discord チャット", "discordChat": "Discord チャット",
"zoomToFitViewport": "表寿範囲に合わせてズーム", "zoomToFitViewport": "表範囲に合わせてズーム",
"zoomToFitSelection": "選択範囲に合わせてズーム", "zoomToFitSelection": "選択範囲に合わせてズーム",
"zoomToFit": "すべての要素が収まるようにズーム", "zoomToFit": "すべての要素が収まるようにズーム",
"installPWA": "Excalidrawをローカルにインストール(PWA)", "installPWA": "Excalidrawをローカルにインストール(PWA)",
"autoResize": "テキストの自動サイズ変更を有効化", "autoResize": "テキストの自動サイズ変更を有効化",
"imageCropping": "画像のトリミング", "imageCropping": "画像の切り抜き",
"unCroppedDimension": "", "unCroppedDimension": "元のサイズ",
"copyElementLink": "", "copyElementLink": "オブジェクトへのリンクをコピー",
"linkToElement": "オブジェクトにリンク", "linkToElement": "オブジェクトにリンク",
"wrapSelectionInFrame": "選択範囲を枠で折り返す", "wrapSelectionInFrame": "選択範囲をフレームで囲む",
"tab": "Tab", "tab": "Tab",
"shapeSwitch": "図形形状を変更" "shapeSwitch": "図形形状を変更",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "オブジェクトにリンク", "title": "オブジェクトにリンク",
@@ -185,15 +200,15 @@
"search": { "search": {
"inputPlaceholder": "ライブラリを検索", "inputPlaceholder": "ライブラリを検索",
"heading": "ライブラリと一致", "heading": "ライブラリと一致",
"noResults": "一致するアイテムが見つかりませんでした…", "noResults": "一致するアイテムはありません…",
"clearSearch": "検索のクリア" "clearSearch": "検索のクリア"
} }
}, },
"search": { "search": {
"title": "キャンバスで検索", "title": "キャンバスで検索",
"noMatch": "一致なし…", "noMatch": "一致する結果はありません…",
"singleResult": "結果", "singleResult": "",
"multipleResults": "結果数", "multipleResults": "",
"placeholder": "キャンバス内のテキストを検索…", "placeholder": "キャンバス内のテキストを検索…",
"frames": "フレーム", "frames": "フレーム",
"texts": "テキスト" "texts": "テキスト"
@@ -240,7 +255,7 @@
"embeddableInteractionButton": "クリックして操作" "embeddableInteractionButton": "クリックして操作"
}, },
"alerts": { "alerts": {
"clearReset": "この操作によってキャンバス全体が消えます。よろしいですか?", "clearReset": "キャンバス全体を消去します。本当によろしいですか?",
"couldNotCreateShareableLink": "共有URLを作成できませんでした。", "couldNotCreateShareableLink": "共有URLを作成できませんでした。",
"couldNotCreateShareableLinkTooBig": "共有可能なリンクを作成できませんでした: シーンが大きすぎます", "couldNotCreateShareableLinkTooBig": "共有可能なリンクを作成できませんでした: シーンが大きすぎます",
"couldNotLoadInvalidFile": "無効なファイルを読み込めませんでした。", "couldNotLoadInvalidFile": "無効なファイルを読み込めませんでした。",
@@ -249,8 +264,8 @@
"couldNotCopyToClipboard": "クリップボードにコピーできませんでした。", "couldNotCopyToClipboard": "クリップボードにコピーできませんでした。",
"decryptFailed": "データを復号できませんでした。", "decryptFailed": "データを復号できませんでした。",
"uploadedSecurly": "データのアップロードはエンドツーエンド暗号化によって保護されています。Excalidrawサーバーと第三者はデータの内容を見ることができません。", "uploadedSecurly": "データのアップロードはエンドツーエンド暗号化によって保護されています。Excalidrawサーバーと第三者はデータの内容を見ることができません。",
"loadSceneOverridePrompt": "外部図面を読み込むと、既存のコンテンツが置き換わります。続行しますか?", "loadSceneOverridePrompt": "外部の描画データを読み込むと、既存のコンテンツが置き換わります。続行しますか?",
"collabStopOverridePrompt": "セッションを停止すると、ローカルに保存されている図が上書きされます。 本当によろしいですか?\n\n(ローカルの図を保持したい場合は、セッションを停止せずにブラウザタブを閉じてください。)", "collabStopOverridePrompt": "セッションを停止すると、ローカルに保存されている図が上書きされます。 本当によろしいですか?\n\n(ローカルの図を保持したい場合は、セッションを停止せずにブラウザーのタブを閉じてください。)",
"errorAddingToLibrary": "アイテムをライブラリに追加できませんでした", "errorAddingToLibrary": "アイテムをライブラリに追加できませんでした",
"errorRemovingFromLibrary": "ライブラリからアイテムを削除できませんでした", "errorRemovingFromLibrary": "ライブラリからアイテムを削除できませんでした",
"confirmAddLibrary": "{{numShapes}} 個の図形をライブラリに追加します。よろしいですか?", "confirmAddLibrary": "{{numShapes}} 個の図形をライブラリに追加します。よろしいですか?",
@@ -261,7 +276,7 @@
"removeItemsFromsLibrary": "{{count}} 個のアイテムをライブラリから削除しますか?", "removeItemsFromsLibrary": "{{count}} 個のアイテムをライブラリから削除しますか?",
"invalidEncryptionKey": "暗号化キーは22文字でなければなりません。共同編集は無効化されています。", "invalidEncryptionKey": "暗号化キーは22文字でなければなりません。共同編集は無効化されています。",
"collabOfflineWarning": "インターネットに接続されていません。\n変更は保存されません!", "collabOfflineWarning": "インターネットに接続されていません。\n変更は保存されません!",
"localStorageQuotaExceeded": "" "localStorageQuotaExceeded": "ブラウザーのストレージ容量を超えました。変更は保存されません。"
}, },
"errors": { "errors": {
"unsupportedFileType": "サポートされていないファイル形式です。", "unsupportedFileType": "サポートされていないファイル形式です。",
@@ -271,13 +286,13 @@
"failedToFetchImage": "画像の読み込みに失敗しました。", "failedToFetchImage": "画像の読み込みに失敗しました。",
"cannotResolveCollabServer": "コラボレーションサーバに接続できませんでした。ページを再読み込みして、もう一度お試しください。", "cannotResolveCollabServer": "コラボレーションサーバに接続できませんでした。ページを再読み込みして、もう一度お試しください。",
"importLibraryError": "ライブラリを読み込めませんでした。", "importLibraryError": "ライブラリを読み込めませんでした。",
"saveLibraryError": "", "saveLibraryError": "ライブラリをストレージに保存できませんでした。変更を失わないように、ローカルにファイルとして保存してください。",
"collabSaveFailed": "バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。", "collabSaveFailed": "バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
"collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。", "collabSaveFailed_sizeExceeded": "キャンバスが大きすぎるため、バックエンドデータベースに保存できませんでした。問題が解決しない場合は、作業を失わないようにローカルにファイルを保存してください。",
"imageToolNotSupported": "画像ツールは使用不可です。", "imageToolNotSupported": "画像ツールは使用不可です。",
"brave_measure_text_error": { "brave_measure_text_error": {
"line1": "<bold>Aggressly Block Fingerprinting</bold> 設定が有効なBraveブラウザを使用しているようです。", "line1": "<bold>Aggressly Block Fingerprinting</bold> 設定が有効なBraveブラウザを使用しているようです。",
"line2": "これにより、図面の <bold>テキスト要素</bold> が壊れる可能性があります。", "line2": "これにより、描画内の<bold>テキスト要素</bold>が壊れる可能性があります。",
"line3": "この設定を無効にすることを強く推奨します。 <link>設定手順</link> をこちらから確認できます。", "line3": "この設定を無効にすることを強く推奨します。 <link>設定手順</link> をこちらから確認できます。",
"line4": "この設定を無効にすると、テキスト要素の表示が修正されません。 GitHub で <issueLink>Issue</issueLink> を開くか、 <discordLink>Discord</discordLink> にご記入ください" "line4": "この設定を無効にすると、テキスト要素の表示が修正されません。 GitHub で <issueLink>Issue</issueLink> を開くか、 <discordLink>Discord</discordLink> にご記入ください"
}, },
@@ -292,9 +307,9 @@
}, },
"toolBar": { "toolBar": {
"selection": "選択", "selection": "選択",
"lasso": "", "lasso": "なげなわ選択",
"image": "画像を挿入", "image": "画像を挿入",
"rectangle": "形", "rectangle": "長方形",
"diamond": "ひし形", "diamond": "ひし形",
"ellipse": "楕円", "ellipse": "楕円",
"arrow": "矢印", "arrow": "矢印",
@@ -304,31 +319,31 @@
"library": "ライブラリ", "library": "ライブラリ",
"lock": "描画後も使用中のツールを選択したままにする", "lock": "描画後も使用中のツールを選択したままにする",
"penMode": "ペンモード - タッチ防止", "penMode": "ペンモード - タッチ防止",
"link": "", "link": "選択した図形にリンクを追加・更新",
"eraser": "消しゴム", "eraser": "消しゴム",
"frame": "フレームツール", "frame": "フレーム",
"magicframe": "ワイヤーフレームからコードを生成", "magicframe": "ワイヤーフレームからコードを生成",
"embeddable": "Web埋め込み", "embeddable": "Web埋め込み",
"laser": "レーザーポインター", "laser": "レーザーポインター",
"hand": "手 (パンニングツール)", "hand": "手 (パンニングツール)",
"extraTools": "その他のツール", "extraTools": "その他のツール",
"mermaidToExcalidraw": "Mermaid を Excalidraw に変換", "mermaidToExcalidraw": "Mermaid を Excalidraw に変換",
"convertElementType": "" "convertElementType": "図形タイプを切り替え"
}, },
"element": { "element": {
"rectangle": "", "rectangle": "長方形",
"diamond": "", "diamond": "ひし形",
"ellipse": "楕円", "ellipse": "楕円",
"arrow": "", "arrow": "矢印",
"line": "", "line": "",
"freedraw": "", "freedraw": "フリーハンド",
"text": "文字", "text": "文字",
"image": "画像", "image": "画像",
"group": "グループ", "group": "グループ",
"frame": "フレーム", "frame": "フレーム",
"magicframe": "ワイヤーフレームからコードを生成", "magicframe": "ワイヤーフレームからコードを生成",
"embeddable": "", "embeddable": "Web埋め込み",
"selection": "", "selection": "選択",
"iframe": "IFrame" "iframe": "IFrame"
}, },
"headings": { "headings": {
@@ -337,34 +352,34 @@
"shapes": "図形" "shapes": "図形"
}, },
"hints": { "hints": {
"dismissSearch": "", "dismissSearch": "{{shortcut}} で検索を閉じる",
"canvasPanning": "", "canvasPanning": "キャンバスを移動するには、{{shortcut_1}} か {{shortcut_2}} を押しながらドラッグ、または手のひらツールを使用",
"linearElement": "クリックすると複数の頂点からなる曲線を開始、ドラッグすると直線", "linearElement": "クリックすると複数の頂点からなる曲線を開始、ドラッグすると直線",
"arrowTool": "", "arrowTool": "クリックで複数の点を追加、ドラッグで直線。{{shortcut}} を再度押すと矢印タイプが変更されます。",
"arrowBindModifiers": "", "arrowBindModifiers": "{{shortcut_1}} を押し続けるとバインドを無効化、{{shortcut_2}} を押し続けると固定点にバインド",
"freeDraw": "クリックしてドラッグします。離すと終了します", "freeDraw": "クリックしてドラッグします。離すと終了します",
"text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます", "text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます",
"embeddable": "クリックしてドラッグし、ウェブサイトを埋め込む", "embeddable": "クリックしてドラッグし、ウェブサイトを埋め込む",
"text_selected": "", "text_selected": "ダブルクリック、または {{shortcut}} を押してテキストを編集",
"text_editing": "", "text_editing": "{{shortcut_1}} または {{shortcut_2}} を押して編集を終了",
"linearElementMulti": "", "linearElementMulti": "最後の点をクリック、または {{shortcut_1}} か {{shortcut_2}} を押して完了",
"lockAngle": "", "lockAngle": "{{shortcut}} を押し続けて角度を固定",
"resize": "", "resize": "{{shortcut_1}} を押し続けて縦横比を維持、\n{{shortcut_2}} を押し続けて中心からリサイズ",
"resizeImage": "", "resizeImage": "{{shortcut_1}} を押し続けて自由にリサイズ、\n{{shortcut_2}} を押し続けて中心からリサイズ",
"rotate": "", "rotate": "{{shortcut}} を押し続けて角度を固定して回転",
"lineEditor_info": "", "lineEditor_info": "{{shortcut_1}} を押しながらダブルクリック、または {{shortcut_2}} を押して点を編集",
"lineEditor_line_info": "", "lineEditor_line_info": "ダブルクリック、または {{shortcut}} を押して点を編集",
"lineEditor_pointSelected": "", "lineEditor_pointSelected": "{{shortcut_1}} で点を削除、\n{{shortcut_2}} で複製、またはドラッグで移動",
"lineEditor_nothingSelected": "", "lineEditor_nothingSelected": "点を選択して編集 ({{shortcut_1}} を押し続けて複数選択)、\nまたは {{shortcut_2}} を押しながらクリックで新しい点を追加",
"publishLibrary": "自分のライブラリを公開", "publishLibrary": "自分のライブラリを公開",
"bindTextToElement": "", "bindTextToElement": "{{shortcut}} でテキストを追加",
"createFlowchart": "", "createFlowchart": "{{shortcut}} でフローチャートを作成",
"deepBoxSelect": "", "deepBoxSelect": "{{shortcut}} を押し続けて詳細選択・ドラッグ防止",
"eraserRevert": "", "eraserRevert": "{{shortcut}} を押し続けて削除マークを取り消し",
"firefox_clipboard_write": "この機能は、\"dom.events.asyncClipboard.clipboardItem\" フラグを \"true\" に設定することで有効になる可能性があります。Firefox でブラウザーの設定を変更するには、\"about:config\" ページを参照してください。", "firefox_clipboard_write": "この機能は、\"dom.events.asyncClipboard.clipboardItem\" フラグを \"true\" に設定することで有効になる可能性があります。Firefox でブラウザーの設定を変更するには、\"about:config\" ページを参照してください。",
"disableSnapping": "", "disableSnapping": "{{shortcut}} を押し続けてスナップを無効化",
"enterCropEditor": "", "enterCropEditor": "画像をダブルクリック、または {{shortcut}} を押して切り抜き",
"leaveCropEditor": "" "leaveCropEditor": "画像の外をクリック、または {{shortcut_1}} か {{shortcut_2}} を押して切り抜きを終了"
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "プレビューを表示できません", "cannotShowPreview": "プレビューを表示できません",
@@ -373,17 +388,17 @@
}, },
"errorSplash": { "errorSplash": {
"headingMain": "エラーが発生しました。もう一度やり直してください。 <button>ページを再読み込みする。</button>", "headingMain": "エラーが発生しました。もう一度やり直してください。 <button>ページを再読み込みする。</button>",
"clearCanvasMessage": "再読み込みがうまくいかない場合は、 <button>キャンバスを消去しています</button>", "clearCanvasMessage": "再読み込みがうまくいかない場合は、<button>キャンバスを消去</button>してみてください。",
"clearCanvasCaveat": " これにより作業が失われます ", "clearCanvasCaveat": " これにより作業が失われます ",
"trackedToSentry": "識別子のエラー {{eventId}} が我々のシステムで追跡されました。", "trackedToSentry": "エラー (識別子: {{eventId}}) はシステムに記録されました。",
"openIssueMessage": "エラーに関するシーン情報を含めないように非常に慎重に設定しました。もしあなたのシーンがプライベートでない場合は、私たちのフォローアップを検討してください。 <button>バグ報告</button> GitHub Issueに以下の情報をコピーして貼り付けてください。", "openIssueMessage": "エラーにシーン情報が含まれないよう配慮しています。シーンが非公開でなければ、<button>バグ報告</button>へのご協力をお願いします。以下の情報をコピーしてGitHub Issueに貼り付けてください。",
"sceneContent": "シーンの内容:" "sceneContent": "シーンの内容:"
}, },
"shareDialog": { "shareDialog": {
"or": "または" "or": "または"
}, },
"roomDialog": { "roomDialog": {
"desc_intro": "図面にコラボレーションするよう人々を招待しま。", "desc_intro": "他の人を描画の共同編集に招待しましょう。",
"desc_privacy": "心配しないでください、セッションはエンドツーエンドで暗号化されており、完全にプライベートです。私たちのサーバーでさえも、あなたが描いたものを見ることができません。", "desc_privacy": "心配しないでください、セッションはエンドツーエンドで暗号化されており、完全にプライベートです。私たちのサーバーでさえも、あなたが描いたものを見ることができません。",
"button_startSession": "セッションを開始する", "button_startSession": "セッションを開始する",
"button_stopSession": "セッションを終了する", "button_stopSession": "セッションを終了する",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "エラー" "title": "エラー"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "ディスクに保存", "disk_title": "ディスクに保存",
"disk_details": "シーンデータを後からインポートできるファイルにエクスポートします。", "disk_details": "シーンデータを後からインポートできるファイルにエクスポートします。",
@@ -411,8 +430,8 @@
"click": "クリック", "click": "クリック",
"deepSelect": "深い選択", "deepSelect": "深い選択",
"deepBoxSelect": "ボックス内の深い選択、およびドラッグの抑止", "deepBoxSelect": "ボックス内の深い選択、およびドラッグの抑止",
"createFlowchart": "", "createFlowchart": "汎用要素からフローチャートを作成",
"navigateFlowchart": "", "navigateFlowchart": "フローチャート内を移動",
"curvedArrow": "カーブした矢印", "curvedArrow": "カーブした矢印",
"curvedLine": "曲線", "curvedLine": "曲線",
"documentation": "ドキュメント", "documentation": "ドキュメント",
@@ -436,8 +455,8 @@
"toggleElementLock": "選択したアイテムをロック/ロック解除", "toggleElementLock": "選択したアイテムをロック/ロック解除",
"movePageUpDown": "ページを上下に移動", "movePageUpDown": "ページを上下に移動",
"movePageLeftRight": "ページを左右に移動", "movePageLeftRight": "ページを左右に移動",
"cropStart": "", "cropStart": "画像を切り抜き",
"cropFinish": "" "cropFinish": "画像の切り抜きを終了"
}, },
"clearCanvasDialog": { "clearCanvasDialog": {
"title": "キャンバスを消去" "title": "キャンバスを消去"
@@ -463,10 +482,10 @@
"required": "必須項目", "required": "必須項目",
"website": "有効な URL を入力してください" "website": "有効な URL を入力してください"
}, },
"noteDescription": "以下に含めるライブラリを提出してください <link>公開ライブラリリポジトリ</link>他の人が作図に使えるようにするためです", "noteDescription": "あなたのライブラリを<link>公開ライブラリリポジトリ</link>に投稿して、他の人が作図に使えるようにしましょう。",
"noteGuidelines": "最初にライブラリを手動で承認する必要があります。次をお読みください <link>ガイドライン</link> 送信する前に、GitHubアカウントが必要になりますが、必須ではありません。", "noteGuidelines": "投稿されたライブラリは担当者が審査します。投稿前に<link>ガイドライン</link>をお読みください。修正をお願いすることがあるため、GitHubアカウントがあると便利ですが、必須ではありません。",
"noteLicense": "提出することにより、ライブラリが次の下で公開されることに同意します: <link>MIT ライセンス </link>つまり誰でも制限なく使えるということです", "noteLicense": "投稿すること、ライブラリが<link>MITライセンス</link>で公開されることに同意したものとみなします。これは誰でも自由に利用できることを意味します。",
"noteItems": "各ライブラリ項目は、フィルタリングのために独自の名前を持つ必要があります。以下のライブラリアイテムが含まれます:", "noteItems": "各アイテムにはフィルタリング用に固有の名前が必要です。以下のアイテムが含まれます:",
"atleastOneLibItem": "開始するには少なくとも1つのライブラリ項目を選択してください", "atleastOneLibItem": "開始するには少なくとも1つのライブラリ項目を選択してください",
"republishWarning": "注意: 選択された項目の中には、すでに公開/投稿済みと表示されているものがあります。既存のライブラリや投稿を更新する場合のみ、アイテムを再投稿してください。" "republishWarning": "注意: 選択された項目の中には、すでに公開/投稿済みと表示されているものがあります。既存のライブラリや投稿を更新する場合のみ、アイテムを再投稿してください。"
}, },
@@ -508,15 +527,15 @@
}, },
"stats": { "stats": {
"angle": "角度", "angle": "角度",
"shapes": "", "shapes": "図形",
"height": "高さ", "height": "高さ",
"scene": "シーン", "scene": "シーン",
"selected": "選択済み", "selected": "選択済み",
"storage": "ストレージ", "storage": "ストレージ",
"fullTitle": "", "fullTitle": "キャンバス・図形のプロパティ",
"title": "プロパティ", "title": "プロパティ",
"generalStats": "", "generalStats": "全般",
"elementProperties": "", "elementProperties": "図形のプロパティ",
"total": "合計", "total": "合計",
"version": "バージョン", "version": "バージョン",
"versionCopy": "クリックしてコピー", "versionCopy": "クリックしてコピー",
@@ -528,7 +547,7 @@
"copyStyles": "スタイルをコピーしました。", "copyStyles": "スタイルをコピーしました。",
"copyToClipboard": "クリップボードにコピー", "copyToClipboard": "クリップボードにコピー",
"copyToClipboardAsPng": "{{exportSelection}} を PNG 形式でクリップボードにコピーしました\n({{exportColorScheme}})", "copyToClipboardAsPng": "{{exportSelection}} を PNG 形式でクリップボードにコピーしました\n({{exportColorScheme}})",
"copyToClipboardAsSvg": "", "copyToClipboardAsSvg": "{{exportSelection}} を SVG 形式でクリップボードにコピーしました\n({{exportColorScheme}})",
"fileSaved": "ファイルを保存しました", "fileSaved": "ファイルを保存しました",
"fileSavedToFilename": "{filename} に保存しました", "fileSavedToFilename": "{filename} に保存しました",
"canvas": "キャンバス", "canvas": "キャンバス",
@@ -536,7 +555,7 @@
"pasteAsSingleElement": "{{shortcut}} を使用して単一の要素として貼り付けるか、\n既存のテキストエディタに貼り付け", "pasteAsSingleElement": "{{shortcut}} を使用して単一の要素として貼り付けるか、\n既存のテキストエディタに貼り付け",
"unableToEmbed": "この URL の埋め込みは現在許可されていません。URL のホワイトリストへの追加をリクエストするには、GitHub で Issue を上げてください。", "unableToEmbed": "この URL の埋め込みは現在許可されていません。URL のホワイトリストへの追加をリクエストするには、GitHub で Issue を上げてください。",
"unrecognizedLinkFormat": "埋め込もうとしたリンクは期待するフォーマットと一致しません。埋め込み元のサイトで提供される「embed」の文字列を貼り付けてください。", "unrecognizedLinkFormat": "埋め込もうとしたリンクは期待するフォーマットと一致しません。埋め込み元のサイトで提供される「embed」の文字列を貼り付けてください。",
"elementLinkCopied": "" "elementLinkCopied": "リンクをクリップボードにコピーしました"
}, },
"colors": { "colors": {
"transparent": "透明", "transparent": "透明",
@@ -557,9 +576,9 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "描画データはブラウザーのストレージに保存されます。",
"center_heading_line2": "", "center_heading_line2": "ブラウザーのストレージは予期せず消去されることがあります。",
"center_heading_line3": "", "center_heading_line3": "作業を失わないよう、定期的にファイルへ保存してください。",
"center_heading_plus": "代わりにExcalidraw+を開きますか?", "center_heading_plus": "代わりにExcalidraw+を開きますか?",
"menuHint": "エクスポート、設定、言語..." "menuHint": "エクスポート、設定、言語..."
}, },
@@ -571,7 +590,7 @@
} }
}, },
"colorPicker": { "colorPicker": {
"color": "", "color": "",
"mostUsedCustomColors": "最も使用されているカスタム色", "mostUsedCustomColors": "最も使用されているカスタム色",
"colors": "色", "colors": "色",
"shades": "影", "shades": "影",
@@ -600,76 +619,77 @@
"loadFromFile": { "loadFromFile": {
"title": "ファイルからロード", "title": "ファイルからロード",
"button": "ファイルからロード", "button": "ファイルからロード",
"description": "ファイルからのロードは、<bold>現在の描画内容を置き換えます</bold>。<br></br>その前に、以下の選択肢のいずれかにより描画内容を保存できます。" "description": "ファイルからの読み込みは、<bold>現在の描画内容を置き換えます</bold>。<br></br>その前に、以下の選択肢のいずれかにより描画内容を保存できます。"
}, },
"shareableLink": { "shareableLink": {
"title": "リンクからロード", "title": "リンクから読み込み",
"button": "描画内容を置き換える", "button": "描画内容を置き換える",
"description": "外部図面のロードは、<bold>現在の描画内容を置き換えます</bold>。<br></br>その前に、以下の選択肢のいずれかにより描画内容を保存できます。" "description": "外部の描画データの読み込みは、<bold>現在の描画内容を置き換えます</bold>。<br></br>その前に、以下の選択肢のいずれかにより描画内容を保存できます。"
} }
} }
}, },
"mermaid": { "mermaid": {
"title": "Mermaid を Excalidraw に変換", "title": "Mermaid を Excalidraw に変換",
"button": "挿入", "button": "挿入",
"description": "現在、<flowchartLink>Flowchart</flowchartLink>、<sequenceLink>Sequence</sequenceLink>、<classLink>Class</classLink> のダイアグラムのみに対応しています。その他の種類は、Excalidraw では画像として描画されます。", "description": "",
"syntax": "Mermaid 構文", "syntax": "Mermaid 構文",
"preview": "プレビュー", "preview": "プレビュー",
"label": "", "label": "Mermaid",
"inputPlaceholder": "" "inputPlaceholder": "Mermaid のダイアグラム定義をここに入力...",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": "エラー!"
}, },
"chat": { "chat": {
"inputPlaceholder": "", "inputPlaceholder": "ダイアグラムのアイデアを入力... ({{shortcut}} で改行)",
"inputPlaceholderWithMessages": "", "inputPlaceholderWithMessages": "ダイアグラムを修正...",
"generating": "", "generating": "生成中...",
"rateLimitRemaining": "", "rateLimitRemaining": "本日の残りリクエスト回数: {{count}}",
"role": { "role": {
"user": "", "user": "あなた",
"assistant": "", "assistant": "AIアシスタント",
"system": "" "system": "システム"
}, },
"aiBeta": "", "aiBeta": "AI ベータ版",
"label": "", "label": "チャット",
"menu": "", "menu": "メニュー",
"newChat": "", "newChat": "新しいチャット",
"deleteChat": "", "deleteChat": "チャットを削除",
"deleteMessage": "", "deleteMessage": "メッセージを削除",
"viewAsMermaid": "", "viewAsMermaid": "Mermaidで表示",
"placeholder": { "placeholder": {
"title": "", "title": "ダイアグラムをデザインしよう",
"description": "", "description": "作りたいダイアグラムを説明してください。AIが生成します。",
"hint": "" "hint": ""
}, },
"preview": "", "preview": "プレビュー",
"insert": "", "insert": "挿入",
"retry": "", "retry": "再試行",
"errors": { "errors": {
"promptTooShort": "", "promptTooShort": "プロンプトが短すぎます ({{min}} 文字以上)",
"promptTooLong": "", "promptTooLong": "プロンプトが長すぎます ({{max}} 文字以下)",
"generationFailed": "", "generationFailed": "生成に失敗しました",
"invalidDiagram": "", "invalidDiagram": "無効なダイアグラムが生成されました。手動で編集、自動修正で再試行、または別のプロンプトをお試しください。",
"fixInMermaid": "", "fixInMermaid": "Mermaidで手動編集 →",
"aiRepair": "", "aiRepair": "再生成 (自動修正) →",
"requestAborted": "", "requestAborted": "リクエストが中断されました",
"requestFailed": "", "requestFailed": "リクエストに失敗しました",
"mermaidParseError": "" "mermaidParseError": "Mermaid構文エラー"
}, },
"rateLimit": { "rateLimit": {
"messageLimit": "", "messageLimit": "無料プランのAI利用上限に達しました。Excalidraw+をお試しいただくか、また明日ご利用ください。",
"generalRateLimit": "", "generalRateLimit": "操作が速すぎたようです。もう少し待ってから再度お試しください。",
"messageLimitInputPlaceholder": "" "messageLimitInputPlaceholder": "メッセージの送信上限に達しました"
}, },
"upsellBtnLabel": "" "upsellBtnLabel": "Plusにアップグレード"
}, },
"quickSearch": { "quickSearch": {
"placeholder": "" "placeholder": "クイック検索"
}, },
"fontList": { "fontList": {
"badge": { "badge": {
"old": "" "old": ""
}, },
"sceneFonts": "このシーン内", "sceneFonts": "このシーン内",
"availableFonts": "利用可能フォント", "availableFonts": "利用可能フォント",
@@ -680,36 +700,36 @@
"hint": { "hint": {
"text": "ユーザーをクリックしてフォロー", "text": "ユーザーをクリックしてフォロー",
"followStatus": "現在このユーザーをフォローしています", "followStatus": "現在このユーザーをフォローしています",
"inCall": "", "inCall": "ユーザーは音声通話中です",
"micMuted": "", "micMuted": "ユーザーのマイクはミュート中です",
"isSpeaking": "" "isSpeaking": "ユーザーは発話中です"
} }
}, },
"commandPalette": { "commandPalette": {
"title": "", "title": "コマンドパレット",
"shortcuts": { "shortcuts": {
"select": "選択", "select": "選択",
"confirm": "確認", "confirm": "確認",
"close": "閉じる" "close": "閉じる"
}, },
"recents": "", "recents": "最近使ったもの",
"search": { "search": {
"placeholder": "", "placeholder": "メニュー、コマンドを検索して便利な機能を見つけよう",
"noMatch": "" "noMatch": "一致するコマンドはありません..."
}, },
"itemNotAvailable": "", "itemNotAvailable": "コマンドを利用できません...",
"shortcutHint": "コマンドパレットには{{shortcut}}を使用" "shortcutHint": "コマンドパレットには{{shortcut}}を使用"
}, },
"keys": { "keys": {
"ctrl": "", "ctrl": "Ctrl",
"option": "", "option": "Option",
"cmd": "", "cmd": "Cmd",
"alt": "", "alt": "Alt",
"escape": "", "escape": "Esc",
"enter": "", "enter": "Enter",
"shift": "", "shift": "Shift",
"spacebar": "", "spacebar": "スペース",
"delete": "", "delete": "Delete",
"mmb": "" "mmb": "スクロールホイール"
} }
} }
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Qoyıw", "paste": "Qoyıw",
"pasteAsPlaintext": "Ápiwayı tekst retinde qoyıw", "pasteAsPlaintext": "Ápiwayı tekst retinde qoyıw",
"pasteCharts": "Diagrammalardı qoyıw", "pasteCharts": "Diagrammalardı qoyıw",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Barlıǵın tańlaw", "selectAll": "Barlıǵın tańlaw",
"multiSelect": "", "multiSelect": "",
"moveCanvas": "", "moveCanvas": "",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Qátelik" "title": "Qátelik"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Diskke saqlaw", "disk_title": "Diskke saqlaw",
"disk_details": "", "disk_details": "",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Senṭeḍ", "paste": "Senṭeḍ",
"pasteAsPlaintext": "", "pasteAsPlaintext": "",
"pasteCharts": "Senṭeḍ udlifen", "pasteCharts": "Senṭeḍ udlifen",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Fren akk", "selectAll": "Fren akk",
"multiSelect": "Rnu aferdis ɣer tefrayt", "multiSelect": "Rnu aferdis ɣer tefrayt",
"moveCanvas": "Smutti taɣzut n usuneɣ", "moveCanvas": "Smutti taɣzut n usuneɣ",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Tuccḍa" "title": "Tuccḍa"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Sekles deg uḍebsi", "disk_title": "Sekles deg uḍebsi",
"disk_details": "Sekles isefka n usayes deg ufaylu ansi ara tizmireḍ ad d-tketreḍ areḍqal.", "disk_details": "Sekles isefka n usayes deg ufaylu ansi ara tizmireḍ ad d-tketreḍ areḍqal.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Қою", "paste": "Қою",
"pasteAsPlaintext": "", "pasteAsPlaintext": "",
"pasteCharts": "Диаграммаларды қою", "pasteCharts": "Диаграммаларды қою",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Бәрін таңдау", "selectAll": "Бәрін таңдау",
"multiSelect": "", "multiSelect": "",
"moveCanvas": "", "moveCanvas": "",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Қате" "title": "Қате"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "Сахна деректерін кейін қайта импорттауға болатын файлға экспорттаңыз.", "disk_details": "Сахна деректерін кейін қайта импорттауға болатын файлға экспорттаңыз.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "បិទភ្ជាប់", "paste": "បិទភ្ជាប់",
"pasteAsPlaintext": "បិទភ្ជាប់ជាអត្ថបទធម្មតា", "pasteAsPlaintext": "បិទភ្ជាប់ជាអត្ថបទធម្មតា",
"pasteCharts": "បិទភ្ជាប់តារាង", "pasteCharts": "បិទភ្ជាប់តារាង",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "ជ្រើសរើស​ទាំងអស់", "selectAll": "ជ្រើសរើស​ទាំងអស់",
"multiSelect": "បន្ថែមធាតុទៅលើការជ្រើសរើស", "multiSelect": "បន្ថែមធាតុទៅលើការជ្រើសរើស",
"moveCanvas": "ផ្លាស់ទីបាវ", "moveCanvas": "ផ្លាស់ទីបាវ",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "មានកំហុស" "title": "មានកំហុស"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "រក្សាទុកទៅថាស", "disk_title": "រក្សាទុកទៅថាស",
"disk_details": "នាំចេញទិន្នន័យរបស់ស៊ីនជាឯកសារដែលអ្នកអាចនាំចូលនៅពេលក្រោយ។", "disk_details": "នាំចេញទិន្នន័យរបស់ស៊ីនជាឯកសារដែលអ្នកអាចនាំចូលនៅពេលក្រោយ។",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "붙여넣기", "paste": "붙여넣기",
"pasteAsPlaintext": "일반 텍스트로 붙여넣기", "pasteAsPlaintext": "일반 텍스트로 붙여넣기",
"pasteCharts": "차트 붙여넣기", "pasteCharts": "차트 붙여넣기",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "전체 선택", "selectAll": "전체 선택",
"multiSelect": "선택 영역에 추가하기", "multiSelect": "선택 영역에 추가하기",
"moveCanvas": "캔버스 이동", "moveCanvas": "캔버스 이동",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "화살표 모양", "arrowtypes": "화살표 모양",
"arrowtype_sharp": "뾰족한 화살표", "arrowtype_sharp": "뾰족한 화살표",
"arrowtype_round": "곡선 화살표", "arrowtype_round": "곡선 화살표",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "오류" "title": "오류"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "디스크에 저장", "disk_title": "디스크에 저장",
"disk_details": "나중에 다시 불러올 수 있도록 화면 데이터를 내보냅니다.", "disk_details": "나중에 다시 불러올 수 있도록 화면 데이터를 내보냅니다.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "دانانەوە", "paste": "دانانەوە",
"pasteAsPlaintext": "دایبنێ وەک دەقی سادە", "pasteAsPlaintext": "دایبنێ وەک دەقی سادە",
"pasteCharts": "دانانەوەی خشتەکان", "pasteCharts": "دانانەوەی خشتەکان",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "دیاریکردنی هەموو", "selectAll": "دیاریکردنی هەموو",
"multiSelect": "زیادکردنی بۆ دیاریکراوەکان", "multiSelect": "زیادکردنی بۆ دیاریکراوەکان",
"moveCanvas": "تابلۆ بجوڵێنە", "moveCanvas": "تابلۆ بجوڵێنە",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "هه‌ڵه‌ ڕوویدا" "title": "هه‌ڵه‌ ڕوویدا"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "پاشەکەوت بکە لە دیسک", "disk_title": "پاشەکەوت بکە لە دیسک",
"disk_details": "هەناردەکردنی داتای دیمەنەکە بۆ فایلێک کە دواتر دەتوانیت لێی هاوردە بکەیت.", "disk_details": "هەناردەکردنی داتای دیمەنەکە بۆ فایلێک کە دواتر دەتوانیت لێی هاوردە بکەیت.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Įklijuoti", "paste": "Įklijuoti",
"pasteAsPlaintext": "Įklijuoti kaip paprastą tekstą", "pasteAsPlaintext": "Įklijuoti kaip paprastą tekstą",
"pasteCharts": "Įklijuoti diagramas", "pasteCharts": "Įklijuoti diagramas",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Pažymėti viską", "selectAll": "Pažymėti viską",
"multiSelect": "Pridėkite elementą prie pasirinktų", "multiSelect": "Pridėkite elementą prie pasirinktų",
"moveCanvas": "Judinti drobę", "moveCanvas": "Judinti drobę",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Klaida" "title": "Klaida"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Įrašyti į diską", "disk_title": "Įrašyti į diską",
"disk_details": "", "disk_details": "",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Ielīmēt", "paste": "Ielīmēt",
"pasteAsPlaintext": "Ielīmēt kā vienkāršu tekstu", "pasteAsPlaintext": "Ielīmēt kā vienkāršu tekstu",
"pasteCharts": "Ielīmēt grafikus", "pasteCharts": "Ielīmēt grafikus",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Atlasīt visu", "selectAll": "Atlasīt visu",
"multiSelect": "Pievienot elementu atlasei", "multiSelect": "Pievienot elementu atlasei",
"moveCanvas": "Pārvietot tāfeli", "moveCanvas": "Pārvietot tāfeli",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Kļūda" "title": "Kļūda"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Saglabāt diskā", "disk_title": "Saglabāt diskā",
"disk_details": "Eksportēt ainas datus datnē, ko vēlāk varēsiet importēt.", "disk_details": "Eksportēt ainas datus datnē, ko vēlāk varēsiet importēt.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "चिटकवा", "paste": "चिटकवा",
"pasteAsPlaintext": "साधा मजकूर च्या रुपात पेस्ट करा", "pasteAsPlaintext": "साधा मजकूर च्या रुपात पेस्ट करा",
"pasteCharts": "चार्ट चिकटवा", "pasteCharts": "चार्ट चिकटवा",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "समस्त निवडा", "selectAll": "समस्त निवडा",
"multiSelect": "निवडित तत्व जोडा", "multiSelect": "निवडित तत्व जोडा",
"moveCanvas": "पटल हलवा", "moveCanvas": "पटल हलवा",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "चिमणि पाय (अनेक)", "arrowhead_crowfoot_many": "चिमणि पाय (अनेक)",
"arrowhead_crowfoot_one": "चिमणि पाय (एक)", "arrowhead_crowfoot_one": "चिमणि पाय (एक)",
"arrowhead_crowfoot_one_or_many": "चिमणि पाय (एक अथवा अनेक)", "arrowhead_crowfoot_one_or_many": "चिमणि पाय (एक अथवा अनेक)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "आणिक विकल्प", "more_options": "आणिक विकल्प",
"cardinality": "",
"arrowtypes": "बाणाचे प्रकार", "arrowtypes": "बाणाचे प्रकार",
"arrowtype_sharp": "तीक्ष्ण तीर", "arrowtype_sharp": "तीक्ष्ण तीर",
"arrowtype_round": "वक्राकार तीर", "arrowtype_round": "वक्राकार तीर",
@@ -171,7 +182,11 @@
"linkToElement": "वस्तू ह्याचा दुवा", "linkToElement": "वस्तू ह्याचा दुवा",
"wrapSelectionInFrame": "चौकटित निवडलेले गुंडाळा", "wrapSelectionInFrame": "चौकटित निवडलेले गुंडाळा",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "वस्तू ह्याचा दुवा", "title": "वस्तू ह्याचा दुवा",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "त्रुटि" "title": "त्रुटि"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "डिस्क मधे जतन करा", "disk_title": "डिस्क मधे जतन करा",
"disk_details": "सीन डेटा बाहेर एक फ़ाइल मधे जतन करा, त्या फ़ाइल मधुम तो डेटा नंतर परत आणु शकता.", "disk_details": "सीन डेटा बाहेर एक फ़ाइल मधे जतन करा, त्या फ़ाइल मधुम तो डेटा नंतर परत आणु शकता.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "मर्मेड पासून एक्सकाली मधे", "title": "मर्मेड पासून एक्सकाली मधे",
"button": "शिरवा", "button": "शिरवा",
"description": "सध्या फक्त <flowchartLink> प्रवाह चित्र (फ़्लो चार्ट) </flowchartLink> आणि <sequenceLink> क्रम चित्र (सिकवेंस ड़ायग्राम) </sequenceLink> करता येतात. बाक़ीचे चित्र प्रकार एक्सकाली चित्र पद्धति नी चित्रित होतील.", "description": "",
"syntax": "मर्मेड संरचना नियम", "syntax": "मर्मेड संरचना नियम",
"preview": "पूर्वावलोकन", "preview": "पूर्वावलोकन",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "ထား", "paste": "ထား",
"pasteAsPlaintext": "", "pasteAsPlaintext": "",
"pasteCharts": "", "pasteCharts": "",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "အကုန်ရွေး", "selectAll": "အကုန်ရွေး",
"multiSelect": "ရွေးထားသည့်ထဲပုံထည့်", "multiSelect": "ရွေးထားသည့်ထဲပုံထည့်",
"moveCanvas": "ကားချပ်ရွှေ့", "moveCanvas": "ကားချပ်ရွှေ့",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "ချို့ယွင်းချက်" "title": "ချို့ယွင်းချက်"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "", "disk_details": "",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Lim inn", "paste": "Lim inn",
"pasteAsPlaintext": "Lim inn som klartekst", "pasteAsPlaintext": "Lim inn som klartekst",
"pasteCharts": "Lim inn diagrammer", "pasteCharts": "Lim inn diagrammer",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Velg alt", "selectAll": "Velg alt",
"multiSelect": "Legg til element i utvalg", "multiSelect": "Legg til element i utvalg",
"moveCanvas": "Flytt lerretet", "moveCanvas": "Flytt lerretet",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Kråkefot (mange)", "arrowhead_crowfoot_many": "Kråkefot (mange)",
"arrowhead_crowfoot_one": "Kråkefot (én)", "arrowhead_crowfoot_one": "Kråkefot (én)",
"arrowhead_crowfoot_one_or_many": "Kråkefot (én eller mange)", "arrowhead_crowfoot_one_or_many": "Kråkefot (én eller mange)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Flere alternativer", "more_options": "Flere alternativer",
"cardinality": "",
"arrowtypes": "Type pil", "arrowtypes": "Type pil",
"arrowtype_sharp": "Skarp pil", "arrowtype_sharp": "Skarp pil",
"arrowtype_round": "Buet pil", "arrowtype_round": "Buet pil",
@@ -171,7 +182,11 @@
"linkToElement": "Lenke til objekt", "linkToElement": "Lenke til objekt",
"wrapSelectionInFrame": "Brekk om utvalg i ramme", "wrapSelectionInFrame": "Brekk om utvalg i ramme",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Lenke til objekt", "title": "Lenke til objekt",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Feil" "title": "Feil"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Lagre til disk", "disk_title": "Lagre til disk",
"disk_details": "Eksporter scene-dataene til en fil som du kan importere fra senere.", "disk_details": "Eksporter scene-dataene til en fil som du kan importere fra senere.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid til Excalidraw", "title": "Mermaid til Excalidraw",
"button": "Sett inn", "button": "Sett inn",
"description": "Foreløpig er bare <flowchartLink>Flowchart</flowchartLink>-,<sequenceLink> Sequence</sequenceLink>- og <classLink>klasse </classLink>-diagrammer støttet. De andre typene vil bli gjengitt som bilde i Excalidraw.", "description": "",
"syntax": "Mermaid-syntaks", "syntax": "Mermaid-syntaks",
"preview": "Forhåndsvisning", "preview": "Forhåndsvisning",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+29 -9
View File
@@ -3,6 +3,10 @@
"paste": "Plakken", "paste": "Plakken",
"pasteAsPlaintext": "Plakken als platte tekst", "pasteAsPlaintext": "Plakken als platte tekst",
"pasteCharts": "Grafieken plakken", "pasteCharts": "Grafieken plakken",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Alles selecteren", "selectAll": "Alles selecteren",
"multiSelect": "Voeg element toe aan selectie", "multiSelect": "Voeg element toe aan selectie",
"moveCanvas": "Canvas verplaatsen", "moveCanvas": "Canvas verplaatsen",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Kraaienpoot (veel)", "arrowhead_crowfoot_many": "Kraaienpoot (veel)",
"arrowhead_crowfoot_one": "Kraaienpoot (enkel)", "arrowhead_crowfoot_one": "Kraaienpoot (enkel)",
"arrowhead_crowfoot_one_or_many": "Kraaienpoot (enkel of veel)", "arrowhead_crowfoot_one_or_many": "Kraaienpoot (enkel of veel)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Meer opties", "more_options": "Meer opties",
"cardinality": "",
"arrowtypes": "Pijl type", "arrowtypes": "Pijl type",
"arrowtype_sharp": "Scherpe pijl", "arrowtype_sharp": "Scherpe pijl",
"arrowtype_round": "Gebogen pijl", "arrowtype_round": "Gebogen pijl",
@@ -171,7 +182,11 @@
"linkToElement": "Link naar object", "linkToElement": "Link naar object",
"wrapSelectionInFrame": "Selectie omslaan in frame", "wrapSelectionInFrame": "Selectie omslaan in frame",
"tab": "Tab", "tab": "Tab",
"shapeSwitch": "Verander vorm" "shapeSwitch": "Verander vorm",
"preferences": "Voorkeuren",
"preferences_toolLock": "Tool vergrendelen",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Link naar object", "title": "Link naar object",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Fout" "title": "Fout"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Opslaan op schijf", "disk_title": "Opslaan op schijf",
"disk_details": "De scènegegevens exporteren naar een bestand waaruit u later kunt importeren.", "disk_details": "De scènegegevens exporteren naar een bestand waaruit u later kunt importeren.",
@@ -557,9 +576,9 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "Je tekeningen worden bewaard in de opslag van je browser.",
"center_heading_line2": "", "center_heading_line2": "Browser opslag kan onverwacht gewist worden.",
"center_heading_line3": "", "center_heading_line3": "Sla je werk regelmatig op in een bestand om te voorkomen dat je het kwijtraakt.",
"center_heading_plus": "Wil je in plaats daarvan naar Excalidraw+ gaan?", "center_heading_plus": "Wil je in plaats daarvan naar Excalidraw+ gaan?",
"menuHint": "Exporteren, voorkeuren en meer, ..." "menuHint": "Exporteren, voorkeuren en meer, ..."
}, },
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid naar Excalidraw", "title": "Mermaid naar Excalidraw",
"button": "Invoegen", "button": "Invoegen",
"description": "Momenteel worden alleen <flowchartLink>Flowchart</flowchartLink>-, <sequenceLink>Sequence</sequenceLink>- en <classLink>Class</classLink>-diagrammen ondersteund. De andere types worden als afbeelding weergegeven in Excalidraw.", "description": "",
"syntax": "Mermaid Syntaxis", "syntax": "Mermaid Syntaxis",
"preview": "Voorbeeld", "preview": "Voorbeeld",
"label": "Mermaid", "label": "Mermaid",
"inputPlaceholder": "Noteer Mermaid diagram omschrijving hier..." "inputPlaceholder": "Noteer Mermaid diagram omschrijving hier...",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "Fout!" "error": "Fout!"
@@ -624,7 +644,7 @@
"chat": { "chat": {
"inputPlaceholder": "Begin je diagram idee hier in te typen... ({{shortcut}} voor een nieuwe lijn)", "inputPlaceholder": "Begin je diagram idee hier in te typen... ({{shortcut}} voor een nieuwe lijn)",
"inputPlaceholderWithMessages": "Ga door met het verfijnen van je diagram...", "inputPlaceholderWithMessages": "Ga door met het verfijnen van je diagram...",
"generating": "", "generating": "Genereren...",
"rateLimitRemaining": "{{count}} verzoeken over vandaag", "rateLimitRemaining": "{{count}} verzoeken over vandaag",
"role": { "role": {
"user": "Jij", "user": "Jij",
@@ -641,7 +661,7 @@
"placeholder": { "placeholder": {
"title": "Laten we jouw diagram ontwerpen", "title": "Laten we jouw diagram ontwerpen",
"description": "Beschrijf het diagram dat je wilt aanmaken, en we genereren het voor je.", "description": "Beschrijf het diagram dat je wilt aanmaken, en we genereren het voor je.",
"hint": "Op dit moment kennen we Flowchart, Sequence, en Class diagrammen." "hint": ""
}, },
"preview": "Voorbeeld", "preview": "Voorbeeld",
"insert": "Invoegen", "insert": "Invoegen",
@@ -655,7 +675,7 @@
"aiRepair": "Opnieuw genereren (automatisch repareren) →", "aiRepair": "Opnieuw genereren (automatisch repareren) →",
"requestAborted": "Verzoek gestopt", "requestAborted": "Verzoek gestopt",
"requestFailed": "Verzoek mislukt", "requestFailed": "Verzoek mislukt",
"mermaidParseError": "" "mermaidParseError": "Mermaid syntax fout"
}, },
"rateLimit": { "rateLimit": {
"messageLimit": "Je hebt je AI limiet bereikt op het gratis abonnement. Probeer Excalidraw+ uit voor meer of kom morgen terug.", "messageLimit": "Je hebt je AI limiet bereikt op het gratis abonnement. Probeer Excalidraw+ uit voor meer of kom morgen terug.",
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "Lim inn", "paste": "Lim inn",
"pasteAsPlaintext": "", "pasteAsPlaintext": "",
"pasteCharts": "Lim inn diagram", "pasteCharts": "Lim inn diagram",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Vel alt", "selectAll": "Vel alt",
"multiSelect": "Legg til element i utval", "multiSelect": "Legg til element i utval",
"moveCanvas": "Flytt lerretet", "moveCanvas": "Flytt lerretet",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Feil" "title": "Feil"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Lagre til disk", "disk_title": "Lagre til disk",
"disk_details": "Eksporter scenedataa til ei fil du kan importere seinare.", "disk_details": "Eksporter scenedataa til ei fil du kan importere seinare.",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Pegar", "paste": "Pegar",
"pasteAsPlaintext": "Pegar en tèxt brut", "pasteAsPlaintext": "Pegar en tèxt brut",
"pasteCharts": "Pegar los grafics", "pasteCharts": "Pegar los grafics",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Tot seleccionar", "selectAll": "Tot seleccionar",
"multiSelect": "Apondre un element a la seleccion", "multiSelect": "Apondre un element a la seleccion",
"moveCanvas": "Desplaçar lo canabàs", "moveCanvas": "Desplaçar lo canabàs",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "Ligam cap a lobjècte", "linkToElement": "Ligam cap a lobjècte",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "Onglet", "tab": "Onglet",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Error" "title": "Error"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Salvar al disc", "disk_title": "Salvar al disc",
"disk_details": "Exportar las donadas de la scèna cap a un fichièr que podètz importar mai tard.", "disk_details": "Exportar las donadas de la scèna cap a un fichièr que podètz importar mai tard.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "De Mermaid cap a Excalidraw", "title": "De Mermaid cap a Excalidraw",
"button": "Inserir", "button": "Inserir",
"description": "Actualament, sonque los diagramas <flowchartLink>logics</flowchartLink>,<sequenceLink> de sequéncia</sequenceLink> e <classLink>de classa </classLink>son preses en carga. Los autres tipes seràn afichats coma imatge dins Excalidraw.", "description": "",
"syntax": "Sintaxi Mermaid", "syntax": "Sintaxi Mermaid",
"preview": "Apercebut", "preview": "Apercebut",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+22 -2
View File
@@ -3,6 +3,10 @@
"paste": "ਪੇਸਟ ਕਰੋ", "paste": "ਪੇਸਟ ਕਰੋ",
"pasteAsPlaintext": "ਸਾਦੇ ਪਾਠ ਵਜੋਂ ਪੇਸਟ ਕਰੋ", "pasteAsPlaintext": "ਸਾਦੇ ਪਾਠ ਵਜੋਂ ਪੇਸਟ ਕਰੋ",
"pasteCharts": "ਚਾਰਟ ਪੇਸਟ ਕਰੋ", "pasteCharts": "ਚਾਰਟ ਪੇਸਟ ਕਰੋ",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "ਸਾਰੇ ਚੁਣੋ", "selectAll": "ਸਾਰੇ ਚੁਣੋ",
"multiSelect": "ਐਲੀਮੈਂਟ ਨੂੰ ਚੋਣ ਵਿੱਚ ਜੋੜੋ", "multiSelect": "ਐਲੀਮੈਂਟ ਨੂੰ ਚੋਣ ਵਿੱਚ ਜੋੜੋ",
"moveCanvas": "ਕੈਨਵਸ ਹਿਲਾਓ", "moveCanvas": "ਕੈਨਵਸ ਹਿਲਾਓ",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "", "arrowhead_crowfoot_many": "",
"arrowhead_crowfoot_one": "", "arrowhead_crowfoot_one": "",
"arrowhead_crowfoot_one_or_many": "", "arrowhead_crowfoot_one_or_many": "",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "", "more_options": "",
"cardinality": "",
"arrowtypes": "", "arrowtypes": "",
"arrowtype_sharp": "", "arrowtype_sharp": "",
"arrowtype_round": "", "arrowtype_round": "",
@@ -171,7 +182,11 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "ਗਲਤੀ" "title": "ਗਲਤੀ"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "ਡਿਸਕ ਵਿੱਚ ਸਾਂਭੋ", "disk_title": "ਡਿਸਕ ਵਿੱਚ ਸਾਂਭੋ",
"disk_details": "ਦ੍ਰਿਸ਼ ਦਾ ਡਾਟਾ ਫਾਈਲ ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ ਜਿੱਥੋਂ ਤੁਸੀਂ ਇਸਨੂੰ ਬਾਅਦ ਵਿੱਚ ਆਯਾਤ ਕਰ ਸਕਦੇ ਹੋ।", "disk_details": "ਦ੍ਰਿਸ਼ ਦਾ ਡਾਟਾ ਫਾਈਲ ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ ਜਿੱਥੋਂ ਤੁਸੀਂ ਇਸਨੂੰ ਬਾਅਦ ਵਿੱਚ ਆਯਾਤ ਕਰ ਸਕਦੇ ਹੋ।",
@@ -616,7 +635,8 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+52 -52
View File
@@ -1,59 +1,59 @@
{ {
"ar-SA": 84, "ar-SA": 82,
"az-AZ": 25, "az-AZ": 24,
"bg-BG": 65, "bg-BG": 63,
"bn-BD": 39, "bn-BD": 38,
"bn-IN": 39, "bn-IN": 38,
"ca-ES": 84, "ca-ES": 82,
"cs-CZ": 76, "cs-CZ": 74,
"da-DK": 28, "da-DK": 28,
"de-CH": 85, "de-CH": 83,
"de-DE": 85, "de-DE": 83,
"el-GR": 80, "el-GR": 77,
"en": 100, "en": 100,
"es-ES": 85, "es-ES": 82,
"eu-ES": 72, "eu-ES": 70,
"fa-IR": 84, "fa-IR": 83,
"fi-FI": 62, "fi-FI": 60,
"fr-FR": 92, "fr-FR": 97,
"gl-ES": 63, "gl-ES": 61,
"he-IL": 84, "he-IL": 82,
"hi-IN": 86, "hi-IN": 83,
"hu-HU": 67, "hu-HU": 65,
"id-ID": 84, "id-ID": 81,
"it-IT": 99, "it-IT": 98,
"ja-JP": 81, "ja-JP": 96,
"kaa": 22, "kaa": 21,
"kab-KAB": 56, "kab-KAB": 54,
"kk-KZ": 13, "kk-KZ": 13,
"km-KH": 56, "km-KH": 54,
"ko-KR": 75, "ko-KR": 73,
"ku-TR": 59, "ku-TR": 58,
"lt-LT": 35, "lt-LT": 33,
"lv-LV": 53, "lv-LV": 51,
"mr-IN": 84, "mr-IN": 82,
"my-MM": 25, "my-MM": 24,
"nb-NO": 84, "nb-NO": 82,
"nl-NL": 99, "nl-NL": 96,
"nn-NO": 45, "nn-NO": 43,
"oc-FR": 79, "oc-FR": 76,
"pa-IN": 54, "pa-IN": 52,
"pl-PL": 93, "pl-PL": 90,
"pt-BR": 84, "pt-BR": 86,
"pt-PT": 84, "pt-PT": 82,
"ro-RO": 100, "ro-RO": 98,
"ru-RU": 99, "ru-RU": 98,
"si-LK": 70, "si-LK": 67,
"sk-SK": 93, "sk-SK": 90,
"sl-SI": 84, "sl-SI": 82,
"sv-SE": 85, "sv-SE": 82,
"ta-IN": 81, "ta-IN": 78,
"th-TH": 59, "th-TH": 65,
"tr-TR": 88, "tr-TR": 92,
"uk-UA": 84, "uk-UA": 86,
"uz-UZ": 0, "uz-UZ": 0,
"vi-VN": 70, "vi-VN": 68,
"zh-CN": 93, "zh-CN": 91,
"zh-HK": 16, "zh-HK": 16,
"zh-TW": 84 "zh-TW": 98
} }
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Wklej", "paste": "Wklej",
"pasteAsPlaintext": "Wklej jako zwykły tekst", "pasteAsPlaintext": "Wklej jako zwykły tekst",
"pasteCharts": "Wklej wykresy", "pasteCharts": "Wklej wykresy",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Zaznacz wszystko", "selectAll": "Zaznacz wszystko",
"multiSelect": "Dodaj element do zaznaczenia", "multiSelect": "Dodaj element do zaznaczenia",
"moveCanvas": "Przesuń obszar roboczy", "moveCanvas": "Przesuń obszar roboczy",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Notacja Martina (wiele)", "arrowhead_crowfoot_many": "Notacja Martina (wiele)",
"arrowhead_crowfoot_one": "Notacja Martina (jeden)", "arrowhead_crowfoot_one": "Notacja Martina (jeden)",
"arrowhead_crowfoot_one_or_many": "Notacja Martina (jeden lub wiele)", "arrowhead_crowfoot_one_or_many": "Notacja Martina (jeden lub wiele)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Więcej opcji", "more_options": "Więcej opcji",
"cardinality": "",
"arrowtypes": "Typ strzałki", "arrowtypes": "Typ strzałki",
"arrowtype_sharp": "Ostra strzałka", "arrowtype_sharp": "Ostra strzałka",
"arrowtype_round": "Zaokrąglona strzałka", "arrowtype_round": "Zaokrąglona strzałka",
@@ -171,7 +182,11 @@
"linkToElement": "Link do obiektu", "linkToElement": "Link do obiektu",
"wrapSelectionInFrame": "Owiń wybór w ramkę", "wrapSelectionInFrame": "Owiń wybór w ramkę",
"tab": "Tab", "tab": "Tab",
"shapeSwitch": "Wybór kształtu" "shapeSwitch": "Wybór kształtu",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Link do obiektu", "title": "Link do obiektu",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Wystąpił błąd" "title": "Wystąpił błąd"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Zapisz na dysku", "disk_title": "Zapisz na dysku",
"disk_details": "Eksportuj dane sceny do pliku, z którego możesz importować później.", "disk_details": "Eksportuj dane sceny do pliku, z którego możesz importować później.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Konwertuj diagram Mermaid do Excalidraw", "title": "Konwertuj diagram Mermaid do Excalidraw",
"button": "Wstaw", "button": "Wstaw",
"description": "Obecnie wspierane są jedynie <flowchartLink>proste grafy</flowchartLink>, <sequenceLink>sekwencje</sequenceLink> i <classLink>diagramy klas</classLink>. Pozostałe typy będą wyświetlane jako obrazy w Excalidraw.", "description": "",
"syntax": "Składnia diagramów Mermaid", "syntax": "Składnia diagramów Mermaid",
"preview": "Podgląd", "preview": "Podgląd",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+46 -26
View File
@@ -3,6 +3,10 @@
"paste": "Colar", "paste": "Colar",
"pasteAsPlaintext": "Colar como texto sem formatação", "pasteAsPlaintext": "Colar como texto sem formatação",
"pasteCharts": "Colar gráficos", "pasteCharts": "Colar gráficos",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Selecionar tudo", "selectAll": "Selecionar tudo",
"multiSelect": "Adicionar elemento à seleção", "multiSelect": "Adicionar elemento à seleção",
"moveCanvas": "Mover tela", "moveCanvas": "Mover tela",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Pé de pássaro (muitos)", "arrowhead_crowfoot_many": "Pé de pássaro (muitos)",
"arrowhead_crowfoot_one": "Pé de pássaro (um)", "arrowhead_crowfoot_one": "Pé de pássaro (um)",
"arrowhead_crowfoot_one_or_many": "Pé de pássaro (um ou muitos)", "arrowhead_crowfoot_one_or_many": "Pé de pássaro (um ou muitos)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Mais opções", "more_options": "Mais opções",
"cardinality": "",
"arrowtypes": "Tipo de seta", "arrowtypes": "Tipo de seta",
"arrowtype_sharp": "Seta afiada", "arrowtype_sharp": "Seta afiada",
"arrowtype_round": "Seta curva", "arrowtype_round": "Seta curva",
@@ -135,15 +146,15 @@
"labelEmbed": "Vincular e incorporar", "labelEmbed": "Vincular e incorporar",
"empty": "Nenhum link foi definido", "empty": "Nenhum link foi definido",
"hint": "Digite ou cole o seu link aqui", "hint": "Digite ou cole o seu link aqui",
"goToElement": "" "goToElement": "Ir para o elemento alvo"
}, },
"lineEditor": { "lineEditor": {
"edit": "Editar linha", "edit": "Editar linha",
"editArrow": "Editar seta" "editArrow": "Editar seta"
}, },
"polygon": { "polygon": {
"breakPolygon": "", "breakPolygon": "Parar polígono",
"convertToPolygon": "" "convertToPolygon": "Converter para polígono"
}, },
"elementLock": { "elementLock": {
"lock": "Bloquear", "lock": "Bloquear",
@@ -170,11 +181,15 @@
"copyElementLink": "Copiar link para o objeto", "copyElementLink": "Copiar link para o objeto",
"linkToElement": "Links para o objeto", "linkToElement": "Links para o objeto",
"wrapSelectionInFrame": "Ajustar seleção no frame", "wrapSelectionInFrame": "Ajustar seleção no frame",
"tab": "", "tab": "Aba",
"shapeSwitch": "" "shapeSwitch": "Trocar forma",
"preferences": "Preferências",
"preferences_toolLock": "Bloqueio de ferramenta",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "Links para o objeto",
"desc": "Clique em uma forma na tela ou cole um link.", "desc": "Clique em uma forma na tela ou cole um link.",
"notFound": "Objeto vinculado não foi encontrado na tela." "notFound": "Objeto vinculado não foi encontrado na tela."
}, },
@@ -184,9 +199,9 @@
"hint_emptyPrivateLibrary": "Selecione um item na tela para adicioná-lo aqui.", "hint_emptyPrivateLibrary": "Selecione um item na tela para adicioná-lo aqui.",
"search": { "search": {
"inputPlaceholder": "", "inputPlaceholder": "",
"heading": "", "heading": "Correspondências da biblioteca",
"noResults": "", "noResults": "Nenhum resultado encontrado...",
"clearSearch": "" "clearSearch": "Limpar pesquisa"
} }
}, },
"search": { "search": {
@@ -195,8 +210,8 @@
"singleResult": "resultado", "singleResult": "resultado",
"multipleResults": "resultados", "multipleResults": "resultados",
"placeholder": "Procurar texto na tela...", "placeholder": "Procurar texto na tela...",
"frames": "", "frames": "Quadros",
"texts": "" "texts": "Textos"
}, },
"buttons": { "buttons": {
"clearReset": "Limpar a tela", "clearReset": "Limpar a tela",
@@ -230,7 +245,7 @@
"objectsSnapMode": "Encaixar em objetos", "objectsSnapMode": "Encaixar em objetos",
"exitZenMode": "Sair do modo zen", "exitZenMode": "Sair do modo zen",
"cancel": "Cancelar", "cancel": "Cancelar",
"saveLibNames": "", "saveLibNames": "Salvar nome(s) e sair",
"clear": "Limpar", "clear": "Limpar",
"remove": "Remover", "remove": "Remover",
"embed": "Alternar incorporação", "embed": "Alternar incorporação",
@@ -261,7 +276,7 @@
"removeItemsFromsLibrary": "Excluir {{count}} item(ns) da biblioteca?", "removeItemsFromsLibrary": "Excluir {{count}} item(ns) da biblioteca?",
"invalidEncryptionKey": "A chave de encriptação deve ter 22 caracteres. A colaboração ao vivo está desabilitada.", "invalidEncryptionKey": "A chave de encriptação deve ter 22 caracteres. A colaboração ao vivo está desabilitada.",
"collabOfflineWarning": "Sem conexão com a internet disponível.\nSuas alterações não serão salvas!", "collabOfflineWarning": "Sem conexão com a internet disponível.\nSuas alterações não serão salvas!",
"localStorageQuotaExceeded": "" "localStorageQuotaExceeded": "A cota de armazenamento do navegador excedida. As alterações não serão salvas."
}, },
"errors": { "errors": {
"unsupportedFileType": "Tipo de arquivo não suportado.", "unsupportedFileType": "Tipo de arquivo não suportado.",
@@ -313,7 +328,7 @@
"hand": "Mão (ferramenta de rolagem)", "hand": "Mão (ferramenta de rolagem)",
"extraTools": "Mais ferramentas", "extraTools": "Mais ferramentas",
"mermaidToExcalidraw": "Mermaid para Excalidraw", "mermaidToExcalidraw": "Mermaid para Excalidraw",
"convertElementType": "" "convertElementType": "Alternar tipo de forma"
}, },
"element": { "element": {
"rectangle": "Retângulo", "rectangle": "Retângulo",
@@ -337,7 +352,7 @@
"shapes": "Formas" "shapes": "Formas"
}, },
"hints": { "hints": {
"dismissSearch": "", "dismissSearch": "{{shortcut}} para descartar a pesquisa",
"canvasPanning": "", "canvasPanning": "",
"linearElement": "Clique para iniciar vários pontos, arraste para uma única linha", "linearElement": "Clique para iniciar vários pontos, arraste para uma única linha",
"arrowTool": "", "arrowTool": "",
@@ -357,8 +372,8 @@
"lineEditor_pointSelected": "", "lineEditor_pointSelected": "",
"lineEditor_nothingSelected": "", "lineEditor_nothingSelected": "",
"publishLibrary": "Publicar sua própria biblioteca", "publishLibrary": "Publicar sua própria biblioteca",
"bindTextToElement": "", "bindTextToElement": "{{shortcut}} para adicionar texto",
"createFlowchart": "", "createFlowchart": "{{shortcut}} para criar um fluxo",
"deepBoxSelect": "", "deepBoxSelect": "",
"eraserRevert": "", "eraserRevert": "",
"firefox_clipboard_write": "Esse recurso pode ser ativado configurando a opção \"dom.events.asyncClipboard.clipboardItem\" como \"true\". Para alterar os sinalizadores do navegador no Firefox, visite a página \"about:config\".", "firefox_clipboard_write": "Esse recurso pode ser ativado configurando a opção \"dom.events.asyncClipboard.clipboardItem\" como \"true\". Para alterar os sinalizadores do navegador no Firefox, visite a página \"about:config\".",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Erro" "title": "Erro"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Salvar no computador", "disk_title": "Salvar no computador",
"disk_details": "Exportar os dados da cena para um arquivo que você poderá importar mais tarde.", "disk_details": "Exportar os dados da cena para um arquivo que você poderá importar mais tarde.",
@@ -536,7 +555,7 @@
"pasteAsSingleElement": "Use {{shortcut}} para colar como um único elemento,\nou cole em um editor de texto já existente", "pasteAsSingleElement": "Use {{shortcut}} para colar como um único elemento,\nou cole em um editor de texto já existente",
"unableToEmbed": "No momento não é permitido incorporar esta URL. Crie uma issue no GitHub para solicitar a lista branca da URL", "unableToEmbed": "No momento não é permitido incorporar esta URL. Crie uma issue no GitHub para solicitar a lista branca da URL",
"unrecognizedLinkFormat": "O link incorporado não corresponde ao formato esperado. Por favor, tente colar a string 'incorporada' que foi fornecida pelo site de origem", "unrecognizedLinkFormat": "O link incorporado não corresponde ao formato esperado. Por favor, tente colar a string 'incorporada' que foi fornecida pelo site de origem",
"elementLinkCopied": "" "elementLinkCopied": "Link copiado para a área de transferência"
}, },
"colors": { "colors": {
"transparent": "Transparente", "transparent": "Transparente",
@@ -557,8 +576,8 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "", "center_heading": "Seus desenhos estão salvos no armazenamento do seu navegador.",
"center_heading_line2": "", "center_heading_line2": "O armazenamento do navegador pode ser apagado inesperadamente.",
"center_heading_line3": "", "center_heading_line3": "",
"center_heading_plus": "Você queria ir para o Excalidraw+ em vez disso?", "center_heading_plus": "Você queria ir para o Excalidraw+ em vez disso?",
"menuHint": "Exportar, preferências, idiomas..." "menuHint": "Exportar, preferências, idiomas..."
@@ -571,7 +590,7 @@
} }
}, },
"colorPicker": { "colorPicker": {
"color": "", "color": "Cor",
"mostUsedCustomColors": "Cores personalizadas mais usadas", "mostUsedCustomColors": "Cores personalizadas mais usadas",
"colors": "Cores", "colors": "Cores",
"shades": "Tons", "shades": "Tons",
@@ -612,18 +631,19 @@
"mermaid": { "mermaid": {
"title": "Mermaid para Excalidraw", "title": "Mermaid para Excalidraw",
"button": "Inserir", "button": "Inserir",
"description": "Atualmente apenas os diagramas<flowchartLink>Flowchart</flowchartLink><sequenceLink>Sequência,</sequenceLink> e <classLink>Class</classLink>são suportados. Os outros tipos serão renderizados como uma imagem no Excalidraw.", "description": "",
"syntax": "Sintaxe em Mermaid", "syntax": "Sintaxe em Mermaid",
"preview": "Visualizar", "preview": "Visualizar",
"label": "", "label": "Mermaid",
"inputPlaceholder": "" "inputPlaceholder": "Escreva definição de diagrama Mermaid aqui...",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": "Erro!"
}, },
"chat": { "chat": {
"inputPlaceholder": "", "inputPlaceholder": "",
"inputPlaceholderWithMessages": "", "inputPlaceholderWithMessages": "Continuar refinando seu diagrama...",
"generating": "", "generating": "",
"rateLimitRemaining": "", "rateLimitRemaining": "",
"role": { "role": {
+23 -3
View File
@@ -3,6 +3,10 @@
"paste": "Colar", "paste": "Colar",
"pasteAsPlaintext": "Colar como texto simples", "pasteAsPlaintext": "Colar como texto simples",
"pasteCharts": "Colar gráficos", "pasteCharts": "Colar gráficos",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Selecionar tudo", "selectAll": "Selecionar tudo",
"multiSelect": "Adicionar elemento à seleção", "multiSelect": "Adicionar elemento à seleção",
"moveCanvas": "Mover área de desenho", "moveCanvas": "Mover área de desenho",
@@ -49,7 +53,14 @@
"arrowhead_crowfoot_many": "Pé de coroa (muitos)", "arrowhead_crowfoot_many": "Pé de coroa (muitos)",
"arrowhead_crowfoot_one": "Pé de coroa (um)", "arrowhead_crowfoot_one": "Pé de coroa (um)",
"arrowhead_crowfoot_one_or_many": "Pé de coroa (um ou muitos)", "arrowhead_crowfoot_one_or_many": "Pé de coroa (um ou muitos)",
"arrowhead_cardinality_one": "",
"arrowhead_cardinality_many": "",
"arrowhead_cardinality_one_or_many": "",
"arrowhead_cardinality_exactly_one": "",
"arrowhead_cardinality_zero_or_one": "",
"arrowhead_cardinality_zero_or_many": "",
"more_options": "Mais opções", "more_options": "Mais opções",
"cardinality": "",
"arrowtypes": "Tipo de seta", "arrowtypes": "Tipo de seta",
"arrowtype_sharp": "Seta retilínea", "arrowtype_sharp": "Seta retilínea",
"arrowtype_round": "Seta curva", "arrowtype_round": "Seta curva",
@@ -171,7 +182,11 @@
"linkToElement": "Hiperligação para o objeto", "linkToElement": "Hiperligação para o objeto",
"wrapSelectionInFrame": "Ajustar seleção no quadro", "wrapSelectionInFrame": "Ajustar seleção no quadro",
"tab": "", "tab": "",
"shapeSwitch": "" "shapeSwitch": "",
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Hiperligação para o objeto", "title": "Hiperligação para o objeto",
@@ -395,6 +410,10 @@
"errorDialog": { "errorDialog": {
"title": "Erro" "title": "Erro"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Guardar no disco", "disk_title": "Guardar no disco",
"disk_details": "Exportar os dados da cena para um ficheiro do qual poderá importar mais tarde.", "disk_details": "Exportar os dados da cena para um ficheiro do qual poderá importar mais tarde.",
@@ -612,11 +631,12 @@
"mermaid": { "mermaid": {
"title": "Mermaid para Excalidraw", "title": "Mermaid para Excalidraw",
"button": "Inserir", "button": "Inserir",
"description": "Atualmente apenas são suportados diagramas <flowchartLink>fluxo</flowchartLink>, <sequenceLink>sequência, </sequenceLink> e <classLink>classe</classLink>. Os outros tipos serão renderizados como imagem no Excalidraw.", "description": "",
"syntax": "Sintaxe Mermaid", "syntax": "Sintaxe Mermaid",
"preview": "Pré-visualizar", "preview": "Pré-visualizar",
"label": "", "label": "",
"inputPlaceholder": "" "inputPlaceholder": "",
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""

Some files were not shown because too many files have changed in this diff Show More