Compare commits

..

8 Commits

Author SHA1 Message Date
dwelle c9f62f20d5 debuggs 2026-03-09 22:04:43 +01:00
David Luzar 21dd1cfacc feat(packages/excalidraw): state tracking, api hook, and others (#10870) 2026-03-08 23:15:18 +01:00
David Luzar fa1f7d9f22 feat(packages/excalidraw): export throttleRAF (#10912) 2026-03-07 12:05:33 +01:00
David Luzar 3d8c12fba4 fix(editor): do not conditionally disable midpoint snapping menu preference (#10906) 2026-03-06 20:44:57 +01:00
David Luzar 757dfeb6ad fix(editor): call throttleRAF with lastArgs and remove trailing (#10905)
Co-authored-by: Varun Chawla <varun_6april@hotmail.com>
Co-authored-by: aziamimoh <aziamimoh@users.noreply.github.com>
Co-authored-by: pgzcoa <pgzcoa@users.noreply.github.com>
Co-authored-by: TinaZhang24 <TinaZhang24@users.noreply.github.com>
2026-03-06 20:40:36 +01:00
David Luzar a0e93b6040 feat(editor): sync export theme with ui theme (#10903) 2026-03-06 18:37:28 +01:00
Hendrik Horstmann 499e9d64a5 fix: dropdownMenu item badge position (#10895) 2026-03-06 08:41:49 +00:00
David Luzar c1dbbdf678 feat(editor): mermaid code editor & improve parsing (#10897) 2026-03-05 18:52:41 +01:00
130 changed files with 4426 additions and 2428 deletions
+22 -1
View File
@@ -39,5 +39,26 @@
"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": {
"react": "19.0.0",
"react-dom": "19.0.0",
"@excalidraw/excalidraw": "*", "@excalidraw/excalidraw": "*",
"browser-fs-access": "0.29.1" "browser-fs-access": "0.38.0",
"react": "19.0.0",
"react-dom": "19.0.0"
}, },
"devDependencies": { "devDependencies": {
"vite": "5.0.12", "typescript": "^5",
"typescript": "^5" "vite": "5.0.12"
}, },
"scripts": { "scripts": {
"start": "vite", "start": "vite",
-36
View File
@@ -4,8 +4,6 @@ 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;
@@ -54,40 +52,6 @@ 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>;
}; };
+92 -20
View File
@@ -5,6 +5,8 @@ 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";
@@ -34,7 +36,6 @@ 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 {
@@ -74,6 +75,7 @@ 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";
@@ -114,6 +116,7 @@ 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,
@@ -369,6 +372,8 @@ 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();
@@ -399,9 +404,6 @@ 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, () => {
@@ -433,18 +435,15 @@ const ExcalidrawWrapper = () => {
} }
}, [excalidrawAPI]); }, [excalidrawAPI]);
useEffect(() => { // ---------------------------------------------------------------------------
if (!excalidrawAPI || (!isCollabDisabled && !collabAPI)) { // Hoisted loadImages
return; // ---------------------------------------------------------------------------
} const loadImages = useCallback(
(data: ResolutionType<typeof initializeScene>, isInitialLoad = false) => {
const loadImages = ( if (!data.scene || !excalidrawAPI) {
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
@@ -471,6 +470,12 @@ 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,
@@ -482,12 +487,18 @@ 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(({ loadedFiles, erroredFiles }) => { .then(async ({ loadedFiles, erroredFiles }) => {
if (loadedFiles.length) { if (loadedFiles.length) {
excalidrawAPI.addFiles(loadedFiles); excalidrawAPI.addFiles(loadedFiles);
} }
@@ -500,10 +511,19 @@ 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({ currentFileIds: fileIds }); LocalData.fileStorage.clearObsoleteFiles({
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);
@@ -628,7 +648,7 @@ const ExcalidrawWrapper = () => {
false, false,
); );
}; };
}, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode]); }, [isCollabDisabled, collabAPI, excalidrawAPI, setLangCode, loadImages]);
useEffect(() => { useEffect(() => {
const unloadHandler = (event: BeforeUnloadEvent) => { const unloadHandler = (event: BeforeUnloadEvent) => {
@@ -773,6 +793,56 @@ 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
@@ -839,8 +909,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}
@@ -1206,7 +1276,9 @@ const ExcalidrawApp = () => {
return ( return (
<TopErrorBoundary> <TopErrorBoundary>
<Provider store={appJotaiStore}> <Provider store={appJotaiStore}>
<ExcalidrawWrapper /> <ExcalidrawAPIProvider>
<ExcalidrawWrapper />
</ExcalidrawAPIProvider>
</Provider> </Provider>
</TopErrorBoundary> </TopErrorBoundary>
); );
+2
View File
@@ -72,6 +72,7 @@ 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,
@@ -149,6 +150,7 @@ 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,7 +414,6 @@ 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,10 +40,12 @@ 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[];
@@ -53,9 +55,13 @@ 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;
} }
/** /**
@@ -146,6 +152,8 @@ 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);
@@ -156,6 +164,13 @@ 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) {
@@ -195,6 +210,13 @@ 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,6 +42,7 @@ 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";
@@ -166,6 +167,7 @@ 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
@@ -0,0 +1,48 @@
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 };
}
}
+10 -1
View File
@@ -106,6 +106,10 @@ 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";
}
}, },
}, },
}, },
@@ -150,6 +154,11 @@ 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: [
{ {
@@ -189,7 +198,7 @@ export default defineConfig(({ mode }) => {
}, },
}, },
{ {
urlPattern: new RegExp(".chunk-.+.js"), urlPattern: new RegExp("(.chunk-.+|CodeMirrorEditor-.+)\\.js"),
handler: "CacheFirst", handler: "CacheFirst",
options: { options: {
cacheName: "chunk", cacheName: "chunk",
+74
View File
@@ -0,0 +1,74 @@
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
@@ -0,0 +1,136 @@
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,5 +11,7 @@ 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,6 +3,12 @@ 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()", () => {
@@ -79,4 +85,87 @@ 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();
});
});
}); });
+13 -23
View File
@@ -88,7 +88,8 @@ 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,
@@ -150,38 +151,27 @@ export const debounce = <T extends any[]>(
return ret; return ret;
}; };
// throttle callback to execute once per animation frame // throttle callback to execute once per animation frame using the latest args
export const throttleRAF = <T extends any[]>( export const throttleRAF = <T extends any[]>(fn: (...args: T) => void) => {
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 = (args: T) => { const scheduleFunc = () => {
timerId = window.requestAnimationFrame(() => { timerId = window.requestAnimationFrame(() => {
timerId = null; timerId = null;
fn(...args); const args = lastArgs;
lastArgs = null; lastArgs = null;
if (lastArgsTrailing) {
lastArgs = lastArgsTrailing; if (args) {
lastArgsTrailing = null; fn(...args);
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(lastArgs); scheduleFunc();
} else if (opts?.trailing) {
lastArgsTrailing = args;
} }
}; };
ret.flush = () => { ret.flush = () => {
@@ -190,12 +180,12 @@ export const throttleRAF = <T extends any[]>(
timerId = null; timerId = null;
} }
if (lastArgs) { if (lastArgs) {
fn(...(lastArgsTrailing || lastArgs)); fn(...lastArgs);
lastArgs = lastArgsTrailing = null; lastArgs = null;
} }
}; };
ret.cancel = () => { ret.cancel = () => {
lastArgs = lastArgsTrailing = null; lastArgs = null;
if (timerId !== null) { if (timerId !== null) {
cancelAnimationFrame(timerId); cancelAnimationFrame(timerId);
timerId = null; timerId = null;
@@ -0,0 +1,70 @@
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,6 +11,92 @@ 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 ".."; import { useStylesPanelMode } from "../components/App";
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 ".."; import { useStylesPanelMode } from "../components/App";
import { register } from "./register"; import { register } from "./register";
+219 -37
View File
@@ -9,18 +9,20 @@ import { getNonDeletedElements } from "@excalidraw/element";
import { CaptureUpdateAction } from "@excalidraw/element"; import { CaptureUpdateAction } from "@excalidraw/element";
import type { Theme } from "@excalidraw/element/types"; import type { ExcalidrawElement, 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";
@@ -31,7 +33,15 @@ import "../components/ToolIcon.scss";
import { register } from "./register"; import { register } from "./register";
import type { AppState } from "../types"; import type { JSONExportData } from "../data/json";
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",
@@ -150,6 +160,143 @@ 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",
@@ -163,42 +310,62 @@ export const actionSaveToActiveFile = register({
); );
}, },
perform: async (elements, appState, value, app) => { perform: async (elements, appState, value, app) => {
const fileHandleExists = !!appState.fileHandle; if (onExportInProgress) {
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(appState.fileHandle) const { fileHandle } = isImageFileHandle(previousFileHandle)
? await resaveAsImageWithScene( ? await resaveAsImageWithScene(
elements, exportedDataPromise,
appState, previousFileHandle,
app.files, filename,
app.getName(),
) )
: await saveAsJSON(elements, appState, app.files, app.getName()); : await saveAsJSON({
data: exportedDataPromise,
filename,
fileHandle: previousFileHandle,
});
return { return {
captureUpdate: CaptureUpdateAction.EVENTUALLY, captureUpdate: CaptureUpdateAction.NEVER,
appState: { appState: {
...appState,
fileHandle, fileHandle,
toast: fileHandleExists toast: {
? { message:
message: fileHandle?.name previousFileHandle && 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 { captureUpdate: CaptureUpdateAction.EVENTUALLY }; return {
captureUpdate: CaptureUpdateAction.NEVER,
appState: {
toast: null,
},
};
} finally {
onExportInProgress = false;
} }
}, },
keyTest: (event) => keyTest: (event) =>
@@ -212,36 +379,50 @@ 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 } = await saveAsJSON( const { fileHandle: savedFileHandle } = await saveAsJSON({
elements, data: exportedDataPromise,
{ filename: app.getName(),
...appState, fileHandle: null,
fileHandle: null, });
},
app.files,
app.getName(),
);
return { return {
captureUpdate: CaptureUpdateAction.EVENTUALLY, captureUpdate: CaptureUpdateAction.NEVER,
appState: { appState: {
...appState,
openDialog: null, openDialog: null,
fileHandle, fileHandle: savedFileHandle,
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 { captureUpdate: CaptureUpdateAction.EVENTUALLY }; return {
captureUpdate: CaptureUpdateAction.NEVER,
appState: {
toast: null,
},
};
} finally {
onExportInProgress = false;
} }
}, },
keyTest: (event) => keyTest: (event) =>
event.key === KEYS.S && event.shiftKey && event[KEYS.CTRL_OR_CMD], event.key.toLowerCase() === KEYS.S &&
event.shiftKey &&
event[KEYS.CTRL_OR_CMD],
PanelComponent: ({ updateData }) => ( PanelComponent: ({ updateData }) => (
<ToolButton <ToolButton
type="button" type="button"
@@ -300,7 +481,8 @@ 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) => { perform: (_elements, appState, value, app) => {
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 ".."; import { useStylesPanelMode } from "../components/App";
import type { History } from "../history"; import type { History } from "../history";
import type { AppClassProperties, AppState } from "../types"; import type { AppClassProperties, AppState } from "../types";
+2 -4
View File
@@ -33,8 +33,6 @@ 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 = {
@@ -369,7 +367,7 @@ type AllowedParsedDataTransferItem =
type: ValueOf<typeof IMAGE_MIME_TYPES>; type: ValueOf<typeof IMAGE_MIME_TYPES>;
kind: "file"; kind: "file";
file: File; file: File;
fileHandle: FileSystemHandle | null; fileHandle: FileSystemFileHandle | null;
} }
| { type: ValueOf<typeof STRING_MIME_TYPES>; kind: "string"; value: string }; | { type: ValueOf<typeof STRING_MIME_TYPES>; kind: "string"; value: string };
@@ -378,7 +376,7 @@ type ParsedDataTransferItem =
type: string; type: string;
kind: "file"; kind: "file";
file: File; file: File;
fileHandle: FileSystemHandle | null; fileHandle: FileSystemFileHandle | null;
} }
| { type: string; kind: "string"; value: string }; | { type: string; kind: "string"; value: string };
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,208 @@
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,12 +10,11 @@ import {
isWritableElement, isWritableElement,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { actionToggleShapeSwitch } from "@excalidraw/excalidraw/actions/actionToggleShapeSwitch";
import { getShortcutKey } from "@excalidraw/excalidraw/shortcut";
import type { MarkRequired } from "@excalidraw/common/utility-types"; import type { MarkRequired } from "@excalidraw/common/utility-types";
import { actionToggleShapeSwitch } from "../../actions/actionToggleShapeSwitch";
import { getShortcutKey } from "../../shortcut";
import { import {
actionClearCanvas, actionClearCanvas,
actionLink, actionLink,
@@ -1,7 +1,9 @@
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";
@@ -37,6 +39,8 @@ import {
isProdEnv, isProdEnv,
mapFind, mapFind,
reduceToCommonValue, reduceToCommonValue,
ROUNDNESS,
sceneCoordsToViewportCoords,
updateActiveTool, updateActiveTool,
} from "@excalidraw/common"; } from "@excalidraw/common";
@@ -71,12 +75,6 @@ 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,6 +59,7 @@ type ImageExportModalProps = {
actionManager: ActionManager; actionManager: ActionManager;
onExportImage: AppClassProperties["onExportImage"]; onExportImage: AppClassProperties["onExportImage"];
name: string; name: string;
exportWithDarkMode: boolean;
}; };
const ImageExportModal = ({ const ImageExportModal = ({
@@ -68,6 +69,7 @@ const ImageExportModal = ({
actionManager, actionManager,
onExportImage, onExportImage,
name, name,
exportWithDarkMode,
}: ImageExportModalProps) => { }: ImageExportModalProps) => {
const hasSelection = isSomeElementSelected( const hasSelection = isSomeElementSelected(
elementsSnapshot, elementsSnapshot,
@@ -79,15 +81,13 @@ 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,
exportDarkMode, exportWithDarkMode,
exportScale, exportScale,
embedScene, embedScene,
resetCopyStatus, resetCopyStatus,
@@ -122,13 +122,18 @@ 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: exportDarkMode, exportWithDarkMode,
exportScale, exportScale,
exportEmbedScene: embedScene, exportEmbedScene: embedScene,
}, },
@@ -137,25 +142,41 @@ const ImageExportModal = ({
maxWidthOrHeight: Math.max(maxWidth, maxHeight), maxWidthOrHeight: Math.max(maxWidth, maxHeight),
exportingFrame, exportingFrame,
}) })
.then((canvas) => { .then(async (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);
// if converting to blob fails, there's some problem that will previewNode.replaceChildren(canvas);
// 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,
@@ -163,7 +184,7 @@ const ImageExportModal = ({
exportingFrame, exportingFrame,
projectName, projectName,
exportWithBackground, exportWithBackground,
exportDarkMode, exportWithDarkMode,
exportScale, exportScale,
embedScene, embedScene,
]); ]);
@@ -233,9 +254,8 @@ const ImageExportModal = ({
> >
<Switch <Switch
name="exportDarkModeSwitch" name="exportDarkModeSwitch"
checked={exportDarkMode} checked={exportWithDarkMode}
onChange={(checked) => { onChange={(checked) => {
setExportDarkMode(checked);
actionManager.executeAction( actionManager.executeAction(
actionExportWithDarkMode, actionExportWithDarkMode,
"ui", "ui",
@@ -399,6 +419,7 @@ export const ImageExportDialog = ({
actionManager={actionManager} actionManager={actionManager}
onExportImage={onExportImage} onExportImage={onExportImage}
name={name} name={name}
exportWithDarkMode={appState.exportWithDarkMode}
/> />
</Dialog> </Dialog>
); );
+25 -12
View File
@@ -60,6 +60,7 @@ 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";
@@ -605,18 +606,30 @@ const LayerUI = ({
showExitZenModeBtn={showExitZenModeBtn} showExitZenModeBtn={showExitZenModeBtn}
renderWelcomeScreen={renderWelcomeScreen} renderWelcomeScreen={renderWelcomeScreen}
/> />
{appState.scrolledOutside && ( {(appState.toast || appState.scrolledOutside) && (
<button <div className="floating-status-stack">
type="button" {appState.toast && (
className="scroll-back-to-content" <Toast
onClick={() => { message={appState.toast.message}
setAppState((appState) => ({ onClose={() => setAppState({ toast: null })}
...calculateScrollCenter(elements, appState), duration={appState.toast.duration}
})); closable={appState.toast.closable}
}} />
> )}
{t("buttons.scrollBackToContent")} {!appState.toast && appState.scrolledOutside && (
</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 "."; import { ChatInterface } from "./ChatInterface";
import type { TTDPanelAction } from "../TTDDialogPanel"; import type { TTDPanelAction } from "../TTDDialogPanel";
@@ -0,0 +1,239 @@
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,6 +17,11 @@ 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,
@@ -33,6 +38,27 @@ 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,
@@ -46,8 +72,16 @@ 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.trim()); const deferredText = useDeferredValue(text);
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<{
@@ -61,7 +95,7 @@ const MermaidToExcalidraw = ({
useEffect(() => { useEffect(() => {
const doRender = async () => { const doRender = async () => {
try { try {
if (!deferredText) { if (!deferredText.trim()) {
resetPreview({ canvasRef, setError }); resetPreview({ canvasRef, setError });
return; return;
} }
@@ -98,6 +132,88 @@ 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,
@@ -107,6 +223,13 @@ const MermaidToExcalidraw = ({
}); });
}; };
const onApplyAutoFix = () => {
if (!autoFixCandidate) {
return;
}
setText(autoFixCandidate);
};
return ( return (
<> <>
<div className="ttd-dialog-desc"> <div className="ttd-dialog-desc">
@@ -130,7 +253,8 @@ const MermaidToExcalidraw = ({
<TTDDialogInput <TTDDialogInput
input={text} input={text}
placeholder={t("mermaid.inputPlaceholder")} placeholder={t("mermaid.inputPlaceholder")}
onChange={(event) => setText(event.target.value)} onChange={(value) => setText(value)}
errorLine={errorLine}
onKeyboardSubmit={() => { onKeyboardSubmit={() => {
onInsertToEditor(); onInsertToEditor();
}} }}
@@ -153,6 +277,9 @@ 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,6 +219,49 @@ $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;
@@ -331,14 +374,55 @@ $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: 100%; max-width: 640px;
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,28 +1,84 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef, useState } from "react";
import { EVENT, KEYS } from "@excalidraw/common"; import { EVENT, KEYS } from "@excalidraw/common";
import type { ChangeEventHandler } from "react"; import Spinner from "../Spinner";
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: ChangeEventHandler<HTMLTextAreaElement>; onChange: (value: string) => void;
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;
} }
@@ -40,15 +96,42 @@ export const TTDDialogInput = ({
textarea.removeEventListener(EVENT.KEYDOWN, handleKeyDown); textarea.removeEventListener(EVENT.KEYDOWN, handleKeyDown);
}; };
} }
}, []); }, [editorState.type]);
return ( if (editorState.type === "ready") {
<textarea const CodeMirrorEditor = editorState.component;
className="ttd-dialog-input" return (
onChange={onChange} <CodeMirrorEditor
value={input} value={input}
placeholder={placeholder} onChange={onChange}
ref={ref} onKeyboardSubmit={onKeyboardSubmit}
/> 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,14 +1,24 @@
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 = ({
@@ -16,7 +26,24 @@ 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 ${
@@ -33,14 +60,48 @@ export const TTDDialogOutput = ({
<div className="ttd-dialog-output-error-icon"> <div className="ttd-dialog-output-error-icon">
{alertTriangleIcon} {alertTriangleIcon}
</div> </div>
<div className="ttd-dialog-output-error-title"> {syntaxGuidance && (
{t("ttd.error")} <div className="ttd-dialog-output-error-summary">
</div> <div className="ttd-dialog-output-error-summary__headline">
{syntaxGuidance.summary}
</div>
<div className="ttd-dialog-output-error-summary__label">
Likely causes:
</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"> <div className="ttd-dialog-output-error-message">
{hideErrorDetails {errorMessageLines.map((line, index) => (
? t("chat.errors.mermaidParseError") <span
: error.message} 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 "@excalidraw/excalidraw/shortcut"; import { getShortcutKey } from "../../shortcut";
export const TTDDialogSubmitShortcut = () => { export const TTDDialogSubmitShortcut = () => {
return ( return (
@@ -0,0 +1,89 @@
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,4 +1,12 @@
import { DEFAULT_EXPORT_PADDING, EDITOR_LS_KEYS } from "@excalidraw/common"; import {
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,
@@ -6,11 +14,6 @@ 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";
@@ -72,15 +75,23 @@ 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;
@@ -0,0 +1,82 @@
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,9 +1,6 @@
import { RequestError } from "@excalidraw/excalidraw/errors"; import { RequestError } from "../../../errors";
import type { import type { LLMMessage, TTTDDialog } from "../types";
LLMMessage,
TTTDDialog,
} from "@excalidraw/excalidraw/components/TTDDialog/types";
interface RateLimitInfo { interface RateLimitInfo {
rateLimit?: number; rateLimit?: number;
@@ -0,0 +1,116 @@
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`);
});
});
@@ -0,0 +1,175 @@
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;
};
@@ -0,0 +1,155 @@
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();
});
});
@@ -0,0 +1,133 @@
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();
};
+31 -15
View File
@@ -1,35 +1,51 @@
@use "../css/variables.module" as *;
.excalidraw { .excalidraw {
.Toast { .Toast {
$closeButtonSize: 1.2rem; $closeButtonSize: 1.2rem;
$closeButtonPadding: 0.4rem; $closeButtonPadding: 0.4rem;
animation: fade-in 0.5s; animation: Toast-fade-in 0.5s;
background-color: var(--button-gray-1); min-width: 220px;
border-radius: 4px; max-width: min(360px, calc(100vw - 32px));
bottom: 10px; border-radius: var(--border-radius-lg);
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;
left: 50%; pointer-events: none;
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;
@@ -38,7 +54,7 @@
} }
} }
@keyframes fade-in { @keyframes Toast-fade-in {
from { from {
opacity: 0; opacity: 0;
} }
+18 -4
View File
@@ -5,11 +5,22 @@ import { ToolButton } from "./ToolButton";
import "./Toast.scss"; import "./Toast.scss";
import type { CSSProperties } from "react"; import type { CSSProperties, ReactNode } from "react";
const DEFAULT_TOAST_TIMEOUT = 5000; const DEFAULT_TOAST_TIMEOUT = 5000;
export const Toast = ({ const ProgressBar = ({ progress }: { progress: number }) => (
<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,
@@ -17,7 +28,7 @@ export const Toast = ({
duration = DEFAULT_TOAST_TIMEOUT, duration = DEFAULT_TOAST_TIMEOUT,
style, style,
}: { }: {
message: string; message: ReactNode;
onClose: () => void; onClose: () => void;
closable?: boolean; closable?: boolean;
duration?: number; duration?: number;
@@ -47,11 +58,12 @@ export const Toast = ({
return ( return (
<div <div
className="Toast" className="Toast"
role="status"
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
style={style} style={style}
> >
<p className="Toast__message">{message}</p> <div className="Toast__message">{message}</div>
{closable && ( {closable && (
<ToolButton <ToolButton
icon={CloseIcon} icon={CloseIcon}
@@ -64,3 +76,5 @@ export const Toast = ({
</div> </div>
); );
}; };
export const Toast = Object.assign(ToastComponent, { ProgressBar });
@@ -6,7 +6,6 @@ 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,
@@ -24,6 +23,8 @@ 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,7 +469,6 @@ 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,6 +500,26 @@ 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;
+10 -9
View File
@@ -27,7 +27,6 @@ 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> => {
@@ -104,7 +103,7 @@ export const getMimeType = (blob: Blob | string): string => {
return ""; return "";
}; };
export const getFileHandleType = (handle: FileSystemHandle | null) => { export const getFileHandleType = (handle: FileSystemFileHandle | null) => {
if (!handle) { if (!handle) {
return null; return null;
} }
@@ -118,7 +117,9 @@ export const isImageFileHandleType = (
return type === "png" || type === "svg"; return type === "png" || type === "svg";
}; };
export const isImageFileHandle = (handle: FileSystemHandle | null) => { export const isImageFileHandle = (
handle: FileSystemFileHandle | null,
): handle is FileSystemFileHandle => {
const type = getFileHandleType(handle); const type = getFileHandleType(handle);
return type === "png" || type === "svg"; return type === "png" || type === "svg";
}; };
@@ -139,8 +140,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,
/** FileSystemHandle. Defaults to `blob.handle` if defined, otherwise null. */ /** FileSystemFileHandle. Defaults to `blob.handle` if defined, otherwise null. */
fileHandle?: FileSystemHandle | null, fileHandle?: FileSystemFileHandle | null,
) => { ) => {
const contents = await parseFileContents(blob); const contents = await parseFileContents(blob);
let data; let data;
@@ -198,8 +199,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,
/** FileSystemHandle. Defaults to `blob.handle` if defined, otherwise null. */ /** FileSystemFileHandle. Defaults to `blob.handle` if defined, otherwise null. */
fileHandle?: FileSystemHandle | null, fileHandle?: FileSystemFileHandle | null,
) => { ) => {
const ret = await loadSceneOrLibraryFromBlob( const ret = await loadSceneOrLibraryFromBlob(
blob, blob,
@@ -392,7 +393,7 @@ export const ImageURLToFile = async (
export const getFileHandle = async ( export const getFileHandle = async (
event: DragEvent | React.DragEvent | DataTransferItem, event: DragEvent | React.DragEvent | DataTransferItem,
): Promise<FileSystemHandle | null> => { ): Promise<FileSystemFileHandle | null> => {
if (nativeFileSystemSupported) { if (nativeFileSystemSupported) {
try { try {
const dataTransferItem = const dataTransferItem =
@@ -400,7 +401,7 @@ export const getFileHandle = async (
? event ? event
: (event as DragEvent).dataTransfer?.items?.[0]; : (event as DragEvent).dataTransfer?.items?.[0];
const handle: FileSystemHandle | null = const handle: FileSystemFileHandle | null =
(await (dataTransferItem as any).getAsFileSystemHandle()) || null; (await (dataTransferItem as any).getAsFileSystemHandle()) || null;
return handle; return handle;
+4 -44
View File
@@ -4,18 +4,12 @@ import {
supported as nativeFileSystemSupported, supported as nativeFileSystemSupported,
} from "browser-fs-access"; } from "browser-fs-access";
import { EVENT, MIME_TYPES, debounce } from "@excalidraw/common"; import { MIME_TYPES } 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;
@@ -42,40 +36,6 @@ 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)) {
@@ -95,8 +55,8 @@ export const fileSave = (
extension: FILE_EXTENSION; extension: FILE_EXTENSION;
mimeTypes?: string[]; mimeTypes?: string[];
description: string; description: string;
/** existing FileSystemHandle */ /** existing FileSystemFileHandle */
fileHandle?: FileSystemHandle | null; fileHandle?: FileSystemFileHandle | null;
}, },
) => { ) => {
return _fileSave( return _fileSave(
@@ -108,8 +68,8 @@ export const fileSave = (
mimeTypes: opts.mimeTypes, mimeTypes: opts.mimeTypes,
}, },
opts.fileHandle, opts.fileHandle,
false,
); );
}; };
export { nativeFileSystemSupported }; export { nativeFileSystemSupported };
export type { FileSystemHandle };
+1 -3
View File
@@ -33,8 +33,6 @@ 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";
@@ -110,7 +108,7 @@ export const exportCanvas = async (
viewBackgroundColor: string; viewBackgroundColor: string;
/** filename, if applicable */ /** filename, if applicable */
name?: string; name?: string;
fileHandle?: FileSystemHandle | null; fileHandle?: FileSystemFileHandle | null;
exportingFrame: ExcalidrawFrameLikeElement | null; exportingFrame: ExcalidrawFrameLikeElement | null;
}, },
) => { ) => {
+27 -18
View File
@@ -1,12 +1,13 @@
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 } from "@excalidraw/element/types"; import type { ExcalidrawElement, NonDeleted } from "@excalidraw/element/types";
import type { MaybePromise } from "@excalidraw/common/utility-types";
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState"; import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
@@ -21,6 +22,12 @@ 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
*/ */
@@ -67,27 +74,29 @@ export const serializeAsJSON = (
return JSON.stringify(data, null, 2); return JSON.stringify(data, null, 2);
}; };
export const saveAsJSON = async ( export const saveAsJSON = async ({
elements: readonly ExcalidrawElement[], data,
appState: AppState, filename,
files: BinaryFiles, fileHandle,
/** filename */ }: {
name: string = appState.name || DEFAULT_FILENAME, data: MaybePromise<JSONExportData>;
) => { filename: string;
const serialized = serializeAsJSON(elements, appState, files, "local"); fileHandle: AppState["fileHandle"];
const blob = new Blob([serialized], { }) => {
type: MIME_TYPES.excalidraw, const blob = Promise.resolve(data).then(({ elements, appState, files }) => {
const serialized = serializeAsJSON(elements, appState, files, "local");
return new Blob([serialized], {
type: MIME_TYPES.excalidraw,
});
}); });
const fileHandle = await fileSave(blob, { const savedFileHandle = await fileSave(blob, {
name, name: filename,
extension: "excalidraw", extension: "excalidraw",
description: "Excalidraw file", description: "Excalidraw file",
fileHandle: isImageFileHandle(appState.fileHandle) fileHandle: isImageFileHandle(fileHandle) ? null : fileHandle,
? null
: appState.fileHandle,
}); });
return { fileHandle }; return { fileHandle: savedFileHandle };
}; };
export const loadFromJSON = async ( export const loadFromJSON = async (
+21 -8
View File
@@ -1,26 +1,39 @@
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 (
elements: readonly ExcalidrawElement[], data: MaybePromise<{
appState: AppState, elements: readonly ExcalidrawElement[];
files: BinaryFiles, appState: AppState;
name: string, files: BinaryFiles;
}>,
fileHandle: FileSystemFileHandle,
filename: string,
) => { ) => {
const { exportBackground, viewBackgroundColor, fileHandle } = appState;
const fileHandleType = getFileHandleType(fileHandle); const fileHandleType = getFileHandleType(fileHandle);
if (!fileHandle || !isImageFileHandleType(fileHandleType)) { if (Math.random() < 1) {
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,
@@ -35,7 +48,7 @@ export const resaveAsImageWithScene = async (
await exportCanvas(fileHandleType, exportedElements, appState, files, { await exportCanvas(fileHandleType, exportedElements, appState, files, {
exportBackground, exportBackground,
viewBackgroundColor, viewBackgroundColor,
name, name: filename,
fileHandle, fileHandle,
exportingFrame, exportingFrame,
}); });
+1 -1
View File
@@ -52,7 +52,7 @@ declare module "png-chunks-extract" {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
interface Blob { interface Blob {
handle?: import("browser-fs-acces").FileSystemHandle; handle?: FileSystemFileHandle;
name?: string; name?: string;
} }
@@ -0,0 +1,110 @@
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;
}
+91 -6
View File
@@ -1,14 +1,18 @@
import React, { useEffect } from "react"; import React, { useCallback, useContext, useEffect, useState } from "react";
import { DEFAULT_UI_OPTIONS, isShallowEqual } from "@excalidraw/common"; import { DEFAULT_UI_OPTIONS, isShallowEqual } from "@excalidraw/common";
import App from "./components/App"; import 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";
@@ -16,16 +20,44 @@ import "./css/app.scss";
import "./css/styles.scss"; import "./css/styles.scss";
import "./fonts/fonts.css"; import "./fonts/fonts.css";
import type { AppProps, ExcalidrawProps } from "./types"; import type {
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,
excalidrawAPI, onExcalidrawAPI,
onMount,
onInitialize,
isCollaborating = false, isCollaborating = false,
onPointerUpdate, onPointerUpdate,
renderTopLeftUI, renderTopLeftUI,
@@ -86,6 +118,15 @@ 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
@@ -115,10 +156,13 @@ 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}
excalidrawAPI={excalidrawAPI} onExcalidrawAPI={handleExcalidrawAPI}
onMount={onMount}
onInitialize={onInitialize}
isCollaborating={isCollaborating} isCollaborating={isCollaborating}
onPointerUpdate={onPointerUpdate} onPointerUpdate={onPointerUpdate}
renderTopLeftUI={renderTopLeftUI} renderTopLeftUI={renderTopLeftUI}
@@ -267,6 +311,7 @@ export {
sceneCoordsToViewportCoords, sceneCoordsToViewportCoords,
viewportCoordsToSceneCoords, viewportCoordsToSceneCoords,
getFormFactor, getFormFactor,
throttleRAF,
} from "@excalidraw/common"; } from "@excalidraw/common";
export { export {
@@ -284,7 +329,13 @@ export { Button } from "./components/Button";
export { Footer }; export { Footer };
export { MainMenu }; export { MainMenu };
export { Ellipsify } from "./components/Ellipsify"; export { Ellipsify } from "./components/Ellipsify";
export { useEditorInterface, useStylesPanelMode } from "./components/App"; export {
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";
@@ -325,3 +376,37 @@ 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);
}
// -----------------------------------------------------------------------------
+3 -23
View File
@@ -3,10 +3,6 @@
"paste": "لصق", "paste": "لصق",
"pasteAsPlaintext": "اللصق كنص عادي", "pasteAsPlaintext": "اللصق كنص عادي",
"pasteCharts": "لصق الرسوم البيانية", "pasteCharts": "لصق الرسوم البيانية",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "تحديد الكل", "selectAll": "تحديد الكل",
"multiSelect": "إضافة عنصر للتحديد", "multiSelect": "إضافة عنصر للتحديد",
"moveCanvas": "نقل لوح الرسم", "moveCanvas": "نقل لوح الرسم",
@@ -53,14 +49,7 @@
"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": "سهم منحني",
@@ -182,11 +171,7 @@
"linkToElement": "رابط إلى الكائن", "linkToElement": "رابط إلى الكائن",
"wrapSelectionInFrame": "تغليف التحديد في إطار", "wrapSelectionInFrame": "تغليف التحديد في إطار",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "رابط إلى الكائن", "title": "رابط إلى الكائن",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "خطأ" "title": "خطأ"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "حفظ الملف على الجهاز", "disk_title": "حفظ الملف على الجهاز",
"disk_details": "تصدير بيانات المشهد إلى ملف يمكنك الاستيراد منه لاحقًا.", "disk_details": "تصدير بيانات المشهد إلى ملف يمكنك الاستيراد منه لاحقًا.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid إلى Excalidraw", "title": "Mermaid إلى Excalidraw",
"button": "إدراج", "button": "إدراج",
"description": "", "description": "حاليًا، يتم دعم <flowchartLink>مخططات التدفق</flowchartLink>، <sequenceLink>التسلسلات</sequenceLink>، و<classLink>الفئات</classLink> فقط. سيتم عرض الأنواع الأخرى كصورة في Excalidraw.",
"syntax": "صيغة Mermaid", "syntax": "صيغة Mermaid",
"preview": "معاينة", "preview": "معاينة",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "" "title": ""
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "", "disk_details": "",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "Постави", "paste": "Постави",
"pasteAsPlaintext": "Постави като обикновен текст", "pasteAsPlaintext": "Постави като обикновен текст",
"pasteCharts": "Постави графики", "pasteCharts": "Постави графики",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Маркирай всичко", "selectAll": "Маркирай всичко",
"multiSelect": "Добави елемент към селекция", "multiSelect": "Добави елемент към селекция",
"moveCanvas": "Премести платно", "moveCanvas": "Премести платно",
@@ -53,14 +49,7 @@
"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": "Извита стрелка",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "Грешка" "title": "Грешка"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Запази към диск", "disk_title": "Запази към диск",
"disk_details": "", "disk_details": "",
@@ -635,8 +616,7 @@
"syntax": "Mermaid Синтаксис", "syntax": "Mermaid Синтаксис",
"preview": "Преглед", "preview": "Преглед",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "পেস্ট করুন", "paste": "পেস্ট করুন",
"pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন", "pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন",
"pasteCharts": "চার্ট পেস্ট করুন", "pasteCharts": "চার্ট পেস্ট করুন",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "সবটা সিলেক্ট করুন", "selectAll": "সবটা সিলেক্ট করুন",
"multiSelect": "একাধিক সিলেক্ট করুন", "multiSelect": "একাধিক সিলেক্ট করুন",
"moveCanvas": "ক্যানভাস সরান", "moveCanvas": "ক্যানভাস সরান",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "ত্রুটি" "title": "ত্রুটি"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "", "disk_details": "",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "পেস্ট করুন", "paste": "পেস্ট করুন",
"pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন", "pasteAsPlaintext": "প্লেইনটেক্সট হিসাবে পেস্ট করুন",
"pasteCharts": "চার্ট পেস্ট করুন", "pasteCharts": "চার্ট পেস্ট করুন",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "সবটা সিলেক্ট করুন", "selectAll": "সবটা সিলেক্ট করুন",
"multiSelect": "একাধিক সিলেক্ট করুন", "multiSelect": "একাধিক সিলেক্ট করুন",
"moveCanvas": "ক্যানভাস সরান", "moveCanvas": "ক্যানভাস সরান",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "ত্রুটি" "title": "ত্রুটি"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "", "disk_details": "",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"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ç",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid a Excalidraw", "title": "Mermaid a Excalidraw",
"button": "Inseriu", "button": "Inseriu",
"description": "", "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.",
"syntax": "Sintaxi de Mermaid", "syntax": "Sintaxi de Mermaid",
"preview": "Previsualització", "preview": "Previsualització",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -635,8 +616,7 @@
"syntax": "Mermaid syntaxe", "syntax": "Mermaid syntaxe",
"preview": "Náhled", "preview": "Náhled",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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": "",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid zu Excalidraw", "title": "Mermaid zu Excalidraw",
"button": "Einfügen", "button": "Einfügen",
"description": "", "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.",
"syntax": "Mermaid-Syntax", "syntax": "Mermaid-Syntax",
"preview": "Vorschau", "preview": "Vorschau",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid zu Excalidraw", "title": "Mermaid zu Excalidraw",
"button": "Einfügen", "button": "Einfügen",
"description": "", "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.",
"syntax": "Mermaid-Syntax", "syntax": "Mermaid-Syntax",
"preview": "Vorschau", "preview": "Vorschau",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"paste": "Επικόλληση", "paste": "Επικόλληση",
"pasteAsPlaintext": "Επικόλληση ως απλό κείμενο", "pasteAsPlaintext": "Επικόλληση ως απλό κείμενο",
"pasteCharts": "Επικόλληση γραφημάτων", "pasteCharts": "Επικόλληση γραφημάτων",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Επιλογή όλων", "selectAll": "Επιλογή όλων",
"multiSelect": "Προσθέστε το στοιχείο στην επιλογή", "multiSelect": "Προσθέστε το στοιχείο στην επιλογή",
"moveCanvas": "Μετακίνηση καμβά", "moveCanvas": "Μετακίνηση καμβά",
@@ -53,14 +49,7 @@
"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": "Κυρτό βέλος",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "Σφάλμα" "title": "Σφάλμα"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "Αποθήκευση στο δίσκο", "disk_title": "Αποθήκευση στο δίσκο",
"disk_details": "Εξαγωγή δεδομένων σκηνής σε ένα αρχείο από το οποίο μπορείτε να εισάγετε αργότερα.", "disk_details": "Εξαγωγή δεδομένων σκηνής σε ένα αρχείο από το οποίο μπορείτε να εισάγετε αργότερα.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid σε Excalidraw", "title": "Mermaid σε Excalidraw",
"button": "Εισαγωγή", "button": "Εισαγωγή",
"description": "", "description": "Επί του παρόντος υποστηρίζονται μόνο Διαγράμματα <flowchartLink>Ροής</flowchartLink>,<sequenceLink> Ακολουθίας, </sequenceLink> και <classLink>Κλάσεων</classLink>. Οι άλλοι τύποι θα αποδοθούν ως εικόνα στο Excalidraw.",
"syntax": "Σύνταξη Mermaid", "syntax": "Σύνταξη Mermaid",
"preview": "Προεπισκόπηση", "preview": "Προεπισκόπηση",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+6 -1
View File
@@ -403,6 +403,10 @@
"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.",
@@ -624,7 +628,8 @@
"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!"
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid a Excalidraw", "title": "Mermaid a Excalidraw",
"button": "Insertar", "button": "Insertar",
"description": "", "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.",
"syntax": "Sintaxis Mermaid", "syntax": "Sintaxis Mermaid",
"preview": "Vista previa", "preview": "Vista previa",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid-etik Excalidraw-ra", "title": "Mermaid-etik Excalidraw-ra",
"button": "Txertatu", "button": "Txertatu",
"description": "", "description": "Momentu honetan <flowchartLink>Flowchart</flowchartLink>, <sequenceLink> Sequence, </sequenceLink> eta <classLink>Class </classLink>Diagramak onartzen dira. Beste motak irudi gisa errendatuko dira Excalidrawn.",
"syntax": "Mermaid sintaxia", "syntax": "Mermaid sintaxia",
"preview": "Aurrebista", "preview": "Aurrebista",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+11 -31
View File
@@ -3,10 +3,6 @@
"paste": "جایگذاری", "paste": "جایگذاری",
"pasteAsPlaintext": "جایگذاری به عنوان متن ساده", "pasteAsPlaintext": "جایگذاری به عنوان متن ساده",
"pasteCharts": "جایگذاری نمودارها", "pasteCharts": "جایگذاری نمودارها",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "انتخاب همه", "selectAll": "انتخاب همه",
"multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید", "multiSelect": "یک ایتم به انتخاب شده ها اضافه کنید",
"moveCanvas": "جابجایی بوم", "moveCanvas": "جابجایی بوم",
@@ -53,14 +49,7 @@
"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": "پیکان منحنی",
@@ -182,11 +171,7 @@
"linkToElement": "لینک به آیتم", "linkToElement": "لینک به آیتم",
"wrapSelectionInFrame": "انتخاب را در قاب قرار دهید", "wrapSelectionInFrame": "انتخاب را در قاب قرار دهید",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "لینک به آیتم", "title": "لینک به آیتم",
@@ -211,7 +196,7 @@
"multipleResults": "نتایج", "multipleResults": "نتایج",
"placeholder": "جستجوی متن در بوم...", "placeholder": "جستجوی متن در بوم...",
"frames": "", "frames": "",
"texts": "متن" "texts": ""
}, },
"buttons": { "buttons": {
"clearReset": "پاکسازی بوم نقاشی", "clearReset": "پاکسازی بوم نقاشی",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "خطا" "title": "خطا"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "ذخیره در دیسک", "disk_title": "ذخیره در دیسک",
"disk_details": "داده های صحنه را به فایلی که بعداً می توانید از آن وارد کنید صادر کنید.", "disk_details": "داده های صحنه را به فایلی که بعداً می توانید از آن وارد کنید صادر کنید.",
@@ -590,7 +571,7 @@
} }
}, },
"colorPicker": { "colorPicker": {
"color": "رنگ", "color": "",
"mostUsedCustomColors": "رنگ های به‌تازگی به‌کار گرفته شده", "mostUsedCustomColors": "رنگ های به‌تازگی به‌کار گرفته شده",
"colors": "رنگ‌ها", "colors": "رنگ‌ها",
"shades": "جلوه‌ها", "shades": "جلوه‌ها",
@@ -631,15 +612,14 @@
"mermaid": { "mermaid": {
"title": "مرمید به excalidraw", "title": "مرمید به excalidraw",
"button": "درج", "button": "درج",
"description": "", "description": "فعلا فقط <flowchartLink> فلوچارت </flowchartLink> ، <sequenceLink> توالی </sequenceLink> و <classLink> کلاس </classLink> نمودارها پشتیبانی می شوند. انواع دیگر به صورت تصویر در Excalidraw ارائه خواهند شد.",
"syntax": "مرمید syntax", "syntax": "مرمید syntax",
"preview": "پیشنمایش", "preview": "پیشنمایش",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "خطا!" "error": ""
}, },
"chat": { "chat": {
"inputPlaceholder": "", "inputPlaceholder": "",
@@ -647,8 +627,8 @@
"generating": "", "generating": "",
"rateLimitRemaining": "", "rateLimitRemaining": "",
"role": { "role": {
"user": "شما", "user": "",
"assistant": "دستیار هوش مصنوعی", "assistant": "",
"system": "" "system": ""
}, },
"aiBeta": "", "aiBeta": "",
@@ -727,9 +707,9 @@
"alt": "", "alt": "",
"escape": "", "escape": "",
"enter": "", "enter": "",
"shift": "Shift", "shift": "",
"spacebar": "فاصله", "spacebar": "",
"delete": "حذف", "delete": "",
"mmb": "" "mmb": ""
} }
} }
+2 -22
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "Esikatsele", "preview": "Esikatsele",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+40 -60
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -576,9 +557,9 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "Vos dessins sont enregistrés dans le stockage de votre navigateur.", "center_heading": "",
"center_heading_line2": "Le stockage du navigateur peut être effacé de manière inattendue.", "center_heading_line2": "",
"center_heading_line3": "Enregistrez régulièrement votre travail dans un fichier pour éviter de le perdre.", "center_heading_line3": "",
"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, ..."
}, },
@@ -631,58 +612,57 @@
"mermaid": { "mermaid": {
"title": "De Mermaid à Excalidraw", "title": "De Mermaid à Excalidraw",
"button": "Insérer", "button": "Insérer",
"description": "", "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.",
"syntax": "Syntaxe Mermaid", "syntax": "Syntaxe Mermaid",
"preview": "Prévisualisation", "preview": "Prévisualisation",
"label": "Mermaid", "label": "",
"inputPlaceholder": "Écrivez la définition du diagramme de Mermaid ici...", "inputPlaceholder": ""
"autoFixAvailable": "Correction automatique disponible"
}, },
"ttd": { "ttd": {
"error": "Erreur!" "error": ""
}, },
"chat": { "chat": {
"inputPlaceholder": "Commencez à taper votre idée de diagramme ici... ({{shortcut}} pour une nouvelle ligne)", "inputPlaceholder": "",
"inputPlaceholderWithMessages": "Continuer à affiner votre diagramme...", "inputPlaceholderWithMessages": "",
"generating": "Génération...", "generating": "",
"rateLimitRemaining": "{{count}} demandes restantes aujourd'hui", "rateLimitRemaining": "",
"role": { "role": {
"user": "Vous", "user": "",
"assistant": "Assistant IA", "assistant": "",
"system": "Système" "system": ""
}, },
"aiBeta": "Bêta IA", "aiBeta": "",
"label": "Chat", "label": "",
"menu": "Menu", "menu": "",
"newChat": "Nouveau chat", "newChat": "",
"deleteChat": "Supprimer le chat", "deleteChat": "",
"deleteMessage": "Supprimer le message", "deleteMessage": "",
"viewAsMermaid": "Voir en tant que Mermaid", "viewAsMermaid": "",
"placeholder": { "placeholder": {
"title": "Créons votre diagramme", "title": "",
"description": "Décrivez le diagramme que vous voulez créer, et nous le générerons pour vous.", "description": "",
"hint": "" "hint": ""
}, },
"preview": "Prévisualisation", "preview": "",
"insert": "Insérer", "insert": "",
"retry": "Recommencer", "retry": "",
"errors": { "errors": {
"promptTooShort": "La requête est trop courte (min {{min}} caractères)", "promptTooShort": "",
"promptTooLong": "La requête est trop longue (max {{max}} caractères)", "promptTooLong": "",
"generationFailed": "Échec de la génération", "generationFailed": "",
"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.", "invalidDiagram": "",
"fixInMermaid": "Modifier Mermaid manuellement →", "fixInMermaid": "",
"aiRepair": "Régénérer (correction automatique) →", "aiRepair": "",
"requestAborted": "Demande abandonnée", "requestAborted": "",
"requestFailed": "La requête a échoué", "requestFailed": "",
"mermaidParseError": "Erreur de syntaxe Mermaid" "mermaidParseError": ""
}, },
"rateLimit": { "rateLimit": {
"messageLimit": "Vous avez atteint votre limite d'IA sur le plan gratuit. Essayez Excalidraw+ pour plus ou revenez demain.", "messageLimit": "",
"generalRateLimit": "Tenez vos chevaux, vous êtes trop rapide pour nous! Veuillez attendre un instant avant de réessayer.", "generalRateLimit": "",
"messageLimitInputPlaceholder": "Vous avez atteint votre limite de messages" "messageLimitInputPlaceholder": ""
}, },
"upsellBtnLabel": "Devenez Premium" "upsellBtnLabel": ""
}, },
"quickSearch": { "quickSearch": {
"placeholder": "Recherche rapide" "placeholder": "Recherche rapide"
+2 -22
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "Vista previa", "preview": "Vista previa",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"paste": "הדבקה", "paste": "הדבקה",
"pasteAsPlaintext": "הדבקה ללא עיצוב", "pasteAsPlaintext": "הדבקה ללא עיצוב",
"pasteCharts": "הדבקת תרשימים", "pasteCharts": "הדבקת תרשימים",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "בחירה בהכול", "selectAll": "בחירה בהכול",
"multiSelect": "הוספת רכיב לבחירה", "multiSelect": "הוספת רכיב לבחירה",
"moveCanvas": "הזזת הקנבס", "moveCanvas": "הזזת הקנבס",
@@ -53,14 +49,7 @@
"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": "חץ מעוקל",
@@ -182,11 +171,7 @@
"linkToElement": "קישור לפריט", "linkToElement": "קישור לפריט",
"wrapSelectionInFrame": "לעטוף את הבחירה במסגרת", "wrapSelectionInFrame": "לעטוף את הבחירה במסגרת",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "קישור לפריט", "title": "קישור לפריט",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "שגיאה" "title": "שגיאה"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "שמור לכונן", "disk_title": "שמור לכונן",
"disk_details": "ייצא מידע של הקנבאס לקובץ שתוכל לייבא אחר כך.", "disk_details": "ייצא מידע של הקנבאס לקובץ שתוכל לייבא אחר כך.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid ל־Excalidraw", "title": "Mermaid ל־Excalidraw",
"button": "הוספה", "button": "הוספה",
"description": "", "description": "לעת עתה נתמכים רק <flowchartLink>תרשימי זרימה</flowchartLink>, <sequenceLink>תהליכים</sequenceLink>, <classLink>ודיאגרמת מחלקה</classLink>. שאר הסוגים ייוצרו כתמונות ב-Excalidraw.",
"syntax": "תחביר Mermaid", "syntax": "תחביר Mermaid",
"preview": "תצוגה מקדימה", "preview": "תצוגה מקדימה",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"paste": "पेस्ट", "paste": "पेस्ट",
"pasteAsPlaintext": "सादे पाठ के रूप में चिपकाएं", "pasteAsPlaintext": "सादे पाठ के रूप में चिपकाएं",
"pasteCharts": "चार्ट चिपकाएँ", "pasteCharts": "चार्ट चिपकाएँ",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "सेलेक्ट ऑल", "selectAll": "सेलेक्ट ऑल",
"multiSelect": "अवयव को चयन में सम्मिलित करें", "multiSelect": "अवयव को चयन में सम्मिलित करें",
"moveCanvas": "चित्रपटल को स्थानांतरित करें", "moveCanvas": "चित्रपटल को स्थानांतरित करें",
@@ -53,14 +49,7 @@
"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": "गोलाकार तीर",
@@ -182,11 +171,7 @@
"linkToElement": "वस्तु की कड़ी", "linkToElement": "वस्तु की कड़ी",
"wrapSelectionInFrame": "चौकट में चुने हुवे को लपेटे", "wrapSelectionInFrame": "चौकट में चुने हुवे को लपेटे",
"tab": "", "tab": "",
"shapeSwitch": "आकार बदलें", "shapeSwitch": "आकार बदलें"
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "वस्तु की कड़ी", "title": "वस्तु की कड़ी",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "त्रुटि" "title": "त्रुटि"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "डिस्क पर सुरक्षित करे", "disk_title": "डिस्क पर सुरक्षित करे",
"disk_details": "दृश्य डेटा एक फ़ाइल में निर्यात करे, जहांसे आप उसे पुनः आयात कर सके", "disk_details": "दृश्य डेटा एक फ़ाइल में निर्यात करे, जहांसे आप उसे पुनः आयात कर सके",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "मर्मेड से एक्सकाली में", "title": "मर्मेड से एक्सकाली में",
"button": "सन्निवेश करे", "button": "सन्निवेश करे",
"description": "", "description": "वर्तमान में केवल <flowchartLink>बहाव चित्र</flowchartLink>, <sequenceLink> अनुक्रम चित्र </sequenceLink> और <classLink>वर्ग चित्र</classLink> का चित्रिकरण संभव हैं. अन्य चित्र प्रकार एक्सकाली प्रतिमा जैसे चित्रित किए जायेंगे.",
"syntax": "मर्मेड विन्यास", "syntax": "मर्मेड विन्यास",
"preview": "पूर्वावलोकन", "preview": "पूर्वावलोकन",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid menjadi Excalidraw", "title": "Mermaid menjadi Excalidraw",
"button": "Sisipkan", "button": "Sisipkan",
"description": "", "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.",
"syntax": "Syntax Mermaid", "syntax": "Syntax Mermaid",
"preview": "Pratinjau", "preview": "Pratinjau",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+7 -27
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -576,9 +557,9 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "I tuoi disegni vengono salvati nella memoria del tuo browser.", "center_heading": "",
"center_heading_line2": "La memoria del browser potrebbe essere cancellata inaspettatamente.", "center_heading_line2": "",
"center_heading_line3": "Salva regolarmente il tuo lavoro in un file per evitare di perderlo.", "center_heading_line3": "",
"center_heading_plus": "Volevi invece andare su Excalidraw+?", "center_heading_plus": "Volevi invece andare su Excalidraw+?",
"menuHint": "Esporta, preferenze, lingue, ..." "menuHint": "Esporta, preferenze, lingue, ..."
}, },
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Da Mermaid a Excalidraw", "title": "Da Mermaid a Excalidraw",
"button": "Inserisci", "button": "Inserisci",
"description": "", "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.",
"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!"
@@ -661,7 +641,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": "" "hint": "Al momento conosciamo i diagrammi di flusso, di sequenza e di classe."
}, },
"preview": "Anteprima", "preview": "Anteprima",
"insert": "Inserisci", "insert": "Inserisci",
+142 -162
View File
@@ -3,10 +3,6 @@
"paste": "貼り付け", "paste": "貼り付け",
"pasteAsPlaintext": "書式なしテキストとして貼り付け", "pasteAsPlaintext": "書式なしテキストとして貼り付け",
"pasteCharts": "チャートの貼り付け", "pasteCharts": "チャートの貼り付け",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "すべて選択", "selectAll": "すべて選択",
"multiSelect": "複数選択", "multiSelect": "複数選択",
"moveCanvas": "キャンバスを移動", "moveCanvas": "キャンバスを移動",
@@ -38,7 +34,7 @@
"opacity": "透明度", "opacity": "透明度",
"textAlign": "文字の配置", "textAlign": "文字の配置",
"edges": "角", "edges": "角",
"sharp": "シャープ", "sharp": "四角",
"round": "丸", "round": "丸",
"arrowheads": "線の終点", "arrowheads": "線の終点",
"arrowhead_none": "なし", "arrowhead_none": "なし",
@@ -50,21 +46,14 @@
"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": "カラスの足 (1)", "arrowhead_crowfoot_one": "鳥の足記法(一対一)",
"arrowhead_crowfoot_one_or_many": "カラスの足 (1..*)", "arrowhead_crowfoot_one_or_many": "鳥の足記法(一対多)",
"arrowhead_cardinality_one": "", "more_options": "詳細設定",
"arrowhead_cardinality_many": "", "arrowtypes": "矢印の種類",
"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\"と表示",
@@ -86,14 +75,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": "言語",
@@ -112,7 +101,7 @@
"libraryLoadingMessage": "ライブラリを読み込み中…", "libraryLoadingMessage": "ライブラリを読み込み中…",
"libraries": "ライブラリを参照する", "libraries": "ライブラリを参照する",
"loadingScene": "シーンを読み込み中…", "loadingScene": "シーンを読み込み中…",
"loadScene": "ファイルからシーンを開く", "loadScene": "ファイルからシーン",
"align": "配置", "align": "配置",
"alignTop": "上揃え", "alignTop": "上揃え",
"alignBottom": "下揃え", "alignBottom": "下揃え",
@@ -171,22 +160,18 @@
"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": "オブジェクトにリンク",
@@ -200,15 +185,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": "テキスト"
@@ -255,7 +240,7 @@
"embeddableInteractionButton": "クリックして操作" "embeddableInteractionButton": "クリックして操作"
}, },
"alerts": { "alerts": {
"clearReset": "キャンバス全体を消去します。本当によろしいですか?", "clearReset": "この操作によってキャンバス全体が消えます。よろしいですか?",
"couldNotCreateShareableLink": "共有URLを作成できませんでした。", "couldNotCreateShareableLink": "共有URLを作成できませんでした。",
"couldNotCreateShareableLinkTooBig": "共有可能なリンクを作成できませんでした: シーンが大きすぎます", "couldNotCreateShareableLinkTooBig": "共有可能なリンクを作成できませんでした: シーンが大きすぎます",
"couldNotLoadInvalidFile": "無効なファイルを読み込めませんでした。", "couldNotLoadInvalidFile": "無効なファイルを読み込めませんでした。",
@@ -264,8 +249,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}} 個の図形をライブラリに追加します。よろしいですか?",
@@ -276,7 +261,7 @@
"removeItemsFromsLibrary": "{{count}} 個のアイテムをライブラリから削除しますか?", "removeItemsFromsLibrary": "{{count}} 個のアイテムをライブラリから削除しますか?",
"invalidEncryptionKey": "暗号化キーは22文字でなければなりません。共同編集は無効化されています。", "invalidEncryptionKey": "暗号化キーは22文字でなければなりません。共同編集は無効化されています。",
"collabOfflineWarning": "インターネットに接続されていません。\n変更は保存されません!", "collabOfflineWarning": "インターネットに接続されていません。\n変更は保存されません!",
"localStorageQuotaExceeded": "ブラウザーのストレージ容量を超えました。変更は保存されません。" "localStorageQuotaExceeded": ""
}, },
"errors": { "errors": {
"unsupportedFileType": "サポートされていないファイル形式です。", "unsupportedFileType": "サポートされていないファイル形式です。",
@@ -286,13 +271,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> にご記入ください"
}, },
@@ -307,9 +292,9 @@
}, },
"toolBar": { "toolBar": {
"selection": "選択", "selection": "選択",
"lasso": "なげなわ選択", "lasso": "",
"image": "画像を挿入", "image": "画像を挿入",
"rectangle": "長方形", "rectangle": "形",
"diamond": "ひし形", "diamond": "ひし形",
"ellipse": "楕円", "ellipse": "楕円",
"arrow": "矢印", "arrow": "矢印",
@@ -319,31 +304,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": "Web埋め込み", "embeddable": "",
"selection": "選択", "selection": "",
"iframe": "IFrame" "iframe": "IFrame"
}, },
"headings": { "headings": {
@@ -352,34 +337,34 @@
"shapes": "図形" "shapes": "図形"
}, },
"hints": { "hints": {
"dismissSearch": "{{shortcut}} で検索を閉じる", "dismissSearch": "",
"canvasPanning": "キャンバスを移動するには、{{shortcut_1}} か {{shortcut_2}} を押しながらドラッグ、または手のひらツールを使用", "canvasPanning": "",
"linearElement": "クリックすると複数の頂点からなる曲線を開始、ドラッグすると直線", "linearElement": "クリックすると複数の頂点からなる曲線を開始、ドラッグすると直線",
"arrowTool": "クリックで複数の点を追加、ドラッグで直線。{{shortcut}} を再度押すと矢印タイプが変更されます。", "arrowTool": "",
"arrowBindModifiers": "{{shortcut_1}} を押し続けるとバインドを無効化、{{shortcut_2}} を押し続けると固定点にバインド", "arrowBindModifiers": "",
"freeDraw": "クリックしてドラッグします。離すと終了します", "freeDraw": "クリックしてドラッグします。離すと終了します",
"text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます", "text": "ヒント: 選択ツールを使用して任意の場所をダブルクリックしてテキストを追加することもできます",
"embeddable": "クリックしてドラッグし、ウェブサイトを埋め込む", "embeddable": "クリックしてドラッグし、ウェブサイトを埋め込む",
"text_selected": "ダブルクリック、または {{shortcut}} を押してテキストを編集", "text_selected": "",
"text_editing": "{{shortcut_1}} または {{shortcut_2}} を押して編集を終了", "text_editing": "",
"linearElementMulti": "最後の点をクリック、または {{shortcut_1}} か {{shortcut_2}} を押して完了", "linearElementMulti": "",
"lockAngle": "{{shortcut}} を押し続けて角度を固定", "lockAngle": "",
"resize": "{{shortcut_1}} を押し続けて縦横比を維持、\n{{shortcut_2}} を押し続けて中心からリサイズ", "resize": "",
"resizeImage": "{{shortcut_1}} を押し続けて自由にリサイズ、\n{{shortcut_2}} を押し続けて中心からリサイズ", "resizeImage": "",
"rotate": "{{shortcut}} を押し続けて角度を固定して回転", "rotate": "",
"lineEditor_info": "{{shortcut_1}} を押しながらダブルクリック、または {{shortcut_2}} を押して点を編集", "lineEditor_info": "",
"lineEditor_line_info": "ダブルクリック、または {{shortcut}} を押して点を編集", "lineEditor_line_info": "",
"lineEditor_pointSelected": "{{shortcut_1}} で点を削除、\n{{shortcut_2}} で複製、またはドラッグで移動", "lineEditor_pointSelected": "",
"lineEditor_nothingSelected": "点を選択して編集 ({{shortcut_1}} を押し続けて複数選択)、\nまたは {{shortcut_2}} を押しながらクリックで新しい点を追加", "lineEditor_nothingSelected": "",
"publishLibrary": "自分のライブラリを公開", "publishLibrary": "自分のライブラリを公開",
"bindTextToElement": "{{shortcut}} でテキストを追加", "bindTextToElement": "",
"createFlowchart": "{{shortcut}} でフローチャートを作成", "createFlowchart": "",
"deepBoxSelect": "{{shortcut}} を押し続けて詳細選択・ドラッグ防止", "deepBoxSelect": "",
"eraserRevert": "{{shortcut}} を押し続けて削除マークを取り消し", "eraserRevert": "",
"firefox_clipboard_write": "この機能は、\"dom.events.asyncClipboard.clipboardItem\" フラグを \"true\" に設定することで有効になる可能性があります。Firefox でブラウザーの設定を変更するには、\"about:config\" ページを参照してください。", "firefox_clipboard_write": "この機能は、\"dom.events.asyncClipboard.clipboardItem\" フラグを \"true\" に設定することで有効になる可能性があります。Firefox でブラウザーの設定を変更するには、\"about:config\" ページを参照してください。",
"disableSnapping": "{{shortcut}} を押し続けてスナップを無効化", "disableSnapping": "",
"enterCropEditor": "画像をダブルクリック、または {{shortcut}} を押して切り抜き", "enterCropEditor": "",
"leaveCropEditor": "画像の外をクリック、または {{shortcut_1}} か {{shortcut_2}} を押して切り抜きを終了" "leaveCropEditor": ""
}, },
"canvasError": { "canvasError": {
"cannotShowPreview": "プレビューを表示できません", "cannotShowPreview": "プレビューを表示できません",
@@ -388,17 +373,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": "セッションを終了する",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "エラー" "title": "エラー"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "ディスクに保存", "disk_title": "ディスクに保存",
"disk_details": "シーンデータを後からインポートできるファイルにエクスポートします。", "disk_details": "シーンデータを後からインポートできるファイルにエクスポートします。",
@@ -430,8 +411,8 @@
"click": "クリック", "click": "クリック",
"deepSelect": "深い選択", "deepSelect": "深い選択",
"deepBoxSelect": "ボックス内の深い選択、およびドラッグの抑止", "deepBoxSelect": "ボックス内の深い選択、およびドラッグの抑止",
"createFlowchart": "汎用要素からフローチャートを作成", "createFlowchart": "",
"navigateFlowchart": "フローチャート内を移動", "navigateFlowchart": "",
"curvedArrow": "カーブした矢印", "curvedArrow": "カーブした矢印",
"curvedLine": "曲線", "curvedLine": "曲線",
"documentation": "ドキュメント", "documentation": "ドキュメント",
@@ -455,8 +436,8 @@
"toggleElementLock": "選択したアイテムをロック/ロック解除", "toggleElementLock": "選択したアイテムをロック/ロック解除",
"movePageUpDown": "ページを上下に移動", "movePageUpDown": "ページを上下に移動",
"movePageLeftRight": "ページを左右に移動", "movePageLeftRight": "ページを左右に移動",
"cropStart": "画像を切り抜き", "cropStart": "",
"cropFinish": "画像の切り抜きを終了" "cropFinish": ""
}, },
"clearCanvasDialog": { "clearCanvasDialog": {
"title": "キャンバスを消去" "title": "キャンバスを消去"
@@ -482,10 +463,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": "注意: 選択された項目の中には、すでに公開/投稿済みと表示されているものがあります。既存のライブラリや投稿を更新する場合のみ、アイテムを再投稿してください。"
}, },
@@ -527,15 +508,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": "クリックしてコピー",
@@ -547,7 +528,7 @@
"copyStyles": "スタイルをコピーしました。", "copyStyles": "スタイルをコピーしました。",
"copyToClipboard": "クリップボードにコピー", "copyToClipboard": "クリップボードにコピー",
"copyToClipboardAsPng": "{{exportSelection}} を PNG 形式でクリップボードにコピーしました\n({{exportColorScheme}})", "copyToClipboardAsPng": "{{exportSelection}} を PNG 形式でクリップボードにコピーしました\n({{exportColorScheme}})",
"copyToClipboardAsSvg": "{{exportSelection}} を SVG 形式でクリップボードにコピーしました\n({{exportColorScheme}})", "copyToClipboardAsSvg": "",
"fileSaved": "ファイルを保存しました", "fileSaved": "ファイルを保存しました",
"fileSavedToFilename": "{filename} に保存しました", "fileSavedToFilename": "{filename} に保存しました",
"canvas": "キャンバス", "canvas": "キャンバス",
@@ -555,7 +536,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": "透明",
@@ -576,9 +557,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": "エクスポート、設定、言語..."
}, },
@@ -590,7 +571,7 @@
} }
}, },
"colorPicker": { "colorPicker": {
"color": "", "color": "",
"mostUsedCustomColors": "最も使用されているカスタム色", "mostUsedCustomColors": "最も使用されているカスタム色",
"colors": "色", "colors": "色",
"shades": "影", "shades": "影",
@@ -619,77 +600,76 @@
"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": "", "description": "現在、<flowchartLink>Flowchart</flowchartLink>、<sequenceLink>Sequence</sequenceLink>、<classLink>Class</classLink> のダイアグラムのみに対応しています。その他の種類は、Excalidraw では画像として描画されます。",
"syntax": "Mermaid 構文", "syntax": "Mermaid 構文",
"preview": "プレビュー", "preview": "プレビュー",
"label": "Mermaid", "label": "",
"inputPlaceholder": "Mermaid のダイアグラム定義をここに入力...", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "エラー!" "error": ""
}, },
"chat": { "chat": {
"inputPlaceholder": "ダイアグラムのアイデアを入力... ({{shortcut}} で改行)", "inputPlaceholder": "",
"inputPlaceholderWithMessages": "ダイアグラムを修正...", "inputPlaceholderWithMessages": "",
"generating": "生成中...", "generating": "",
"rateLimitRemaining": "本日の残りリクエスト回数: {{count}}", "rateLimitRemaining": "",
"role": { "role": {
"user": "あなた", "user": "",
"assistant": "AIアシスタント", "assistant": "",
"system": "システム" "system": ""
}, },
"aiBeta": "AI ベータ版", "aiBeta": "",
"label": "チャット", "label": "",
"menu": "メニュー", "menu": "",
"newChat": "新しいチャット", "newChat": "",
"deleteChat": "チャットを削除", "deleteChat": "",
"deleteMessage": "メッセージを削除", "deleteMessage": "",
"viewAsMermaid": "Mermaidで表示", "viewAsMermaid": "",
"placeholder": { "placeholder": {
"title": "ダイアグラムをデザインしよう", "title": "",
"description": "作りたいダイアグラムを説明してください。AIが生成します。", "description": "",
"hint": "" "hint": ""
}, },
"preview": "プレビュー", "preview": "",
"insert": "挿入", "insert": "",
"retry": "再試行", "retry": "",
"errors": { "errors": {
"promptTooShort": "プロンプトが短すぎます ({{min}} 文字以上)", "promptTooShort": "",
"promptTooLong": "プロンプトが長すぎます ({{max}} 文字以下)", "promptTooLong": "",
"generationFailed": "生成に失敗しました", "generationFailed": "",
"invalidDiagram": "無効なダイアグラムが生成されました。手動で編集、自動修正で再試行、または別のプロンプトをお試しください。", "invalidDiagram": "",
"fixInMermaid": "Mermaidで手動編集 →", "fixInMermaid": "",
"aiRepair": "再生成 (自動修正) →", "aiRepair": "",
"requestAborted": "リクエストが中断されました", "requestAborted": "",
"requestFailed": "リクエストに失敗しました", "requestFailed": "",
"mermaidParseError": "Mermaid構文エラー" "mermaidParseError": ""
}, },
"rateLimit": { "rateLimit": {
"messageLimit": "無料プランのAI利用上限に達しました。Excalidraw+をお試しいただくか、また明日ご利用ください。", "messageLimit": "",
"generalRateLimit": "操作が速すぎたようです。もう少し待ってから再度お試しください。", "generalRateLimit": "",
"messageLimitInputPlaceholder": "メッセージの送信上限に達しました" "messageLimitInputPlaceholder": ""
}, },
"upsellBtnLabel": "Plusにアップグレード" "upsellBtnLabel": ""
}, },
"quickSearch": { "quickSearch": {
"placeholder": "クイック検索" "placeholder": ""
}, },
"fontList": { "fontList": {
"badge": { "badge": {
"old": "" "old": ""
}, },
"sceneFonts": "このシーン内", "sceneFonts": "このシーン内",
"availableFonts": "利用可能フォント", "availableFonts": "利用可能フォント",
@@ -700,36 +680,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": "Esc", "escape": "",
"enter": "Enter", "enter": "",
"shift": "Shift", "shift": "",
"spacebar": "スペース", "spacebar": "",
"delete": "Delete", "delete": "",
"mmb": "スクロールホイール" "mmb": ""
} }
} }
+2 -22
View File
@@ -3,10 +3,6 @@
"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": "",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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": "",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"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ɣ",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "Қою", "paste": "Қою",
"pasteAsPlaintext": "", "pasteAsPlaintext": "",
"pasteCharts": "Диаграммаларды қою", "pasteCharts": "Диаграммаларды қою",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "Бәрін таңдау", "selectAll": "Бәрін таңдау",
"multiSelect": "", "multiSelect": "",
"moveCanvas": "", "moveCanvas": "",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "Қате" "title": "Қате"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "Сахна деректерін кейін қайта импорттауға болатын файлға экспорттаңыз.", "disk_details": "Сахна деректерін кейін қайта импорттауға болатын файлға экспорттаңыз.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "បិទភ្ជាប់", "paste": "បិទភ្ជាប់",
"pasteAsPlaintext": "បិទភ្ជាប់ជាអត្ថបទធម្មតា", "pasteAsPlaintext": "បិទភ្ជាប់ជាអត្ថបទធម្មតា",
"pasteCharts": "បិទភ្ជាប់តារាង", "pasteCharts": "បិទភ្ជាប់តារាង",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "ជ្រើសរើស​ទាំងអស់", "selectAll": "ជ្រើសរើស​ទាំងអស់",
"multiSelect": "បន្ថែមធាតុទៅលើការជ្រើសរើស", "multiSelect": "បន្ថែមធាតុទៅលើការជ្រើសរើស",
"moveCanvas": "ផ្លាស់ទីបាវ", "moveCanvas": "ផ្លាស់ទីបាវ",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "មានកំហុស" "title": "មានកំហុស"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "រក្សាទុកទៅថាស", "disk_title": "រក្សាទុកទៅថាស",
"disk_details": "នាំចេញទិន្នន័យរបស់ស៊ីនជាឯកសារដែលអ្នកអាចនាំចូលនៅពេលក្រោយ។", "disk_details": "នាំចេញទិន្នន័យរបស់ស៊ីនជាឯកសារដែលអ្នកអាចនាំចូលនៅពេលក្រោយ។",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "붙여넣기", "paste": "붙여넣기",
"pasteAsPlaintext": "일반 텍스트로 붙여넣기", "pasteAsPlaintext": "일반 텍스트로 붙여넣기",
"pasteCharts": "차트 붙여넣기", "pasteCharts": "차트 붙여넣기",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "전체 선택", "selectAll": "전체 선택",
"multiSelect": "선택 영역에 추가하기", "multiSelect": "선택 영역에 추가하기",
"moveCanvas": "캔버스 이동", "moveCanvas": "캔버스 이동",
@@ -53,14 +49,7 @@
"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": "곡선 화살표",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "오류" "title": "오류"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "디스크에 저장", "disk_title": "디스크에 저장",
"disk_details": "나중에 다시 불러올 수 있도록 화면 데이터를 내보냅니다.", "disk_details": "나중에 다시 불러올 수 있도록 화면 데이터를 내보냅니다.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "دانانەوە", "paste": "دانانەوە",
"pasteAsPlaintext": "دایبنێ وەک دەقی سادە", "pasteAsPlaintext": "دایبنێ وەک دەقی سادە",
"pasteCharts": "دانانەوەی خشتەکان", "pasteCharts": "دانانەوەی خشتەکان",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "دیاریکردنی هەموو", "selectAll": "دیاریکردنی هەموو",
"multiSelect": "زیادکردنی بۆ دیاریکراوەکان", "multiSelect": "زیادکردنی بۆ دیاریکراوەکان",
"moveCanvas": "تابلۆ بجوڵێنە", "moveCanvas": "تابلۆ بجوڵێنە",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "هه‌ڵه‌ ڕوویدا" "title": "هه‌ڵه‌ ڕوویدا"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "پاشەکەوت بکە لە دیسک", "disk_title": "پاشەکەوت بکە لە دیسک",
"disk_details": "هەناردەکردنی داتای دیمەنەکە بۆ فایلێک کە دواتر دەتوانیت لێی هاوردە بکەیت.", "disk_details": "هەناردەکردنی داتای دیمەنەکە بۆ فایلێک کە دواتر دەتوانیت لێی هاوردە بکەیت.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"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ę",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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": "",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"paste": "चिटकवा", "paste": "चिटकवा",
"pasteAsPlaintext": "साधा मजकूर च्या रुपात पेस्ट करा", "pasteAsPlaintext": "साधा मजकूर च्या रुपात पेस्ट करा",
"pasteCharts": "चार्ट चिकटवा", "pasteCharts": "चार्ट चिकटवा",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "समस्त निवडा", "selectAll": "समस्त निवडा",
"multiSelect": "निवडित तत्व जोडा", "multiSelect": "निवडित तत्व जोडा",
"moveCanvas": "पटल हलवा", "moveCanvas": "पटल हलवा",
@@ -53,14 +49,7 @@
"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": "वक्राकार तीर",
@@ -182,11 +171,7 @@
"linkToElement": "वस्तू ह्याचा दुवा", "linkToElement": "वस्तू ह्याचा दुवा",
"wrapSelectionInFrame": "चौकटित निवडलेले गुंडाळा", "wrapSelectionInFrame": "चौकटित निवडलेले गुंडाळा",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "वस्तू ह्याचा दुवा", "title": "वस्तू ह्याचा दुवा",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "त्रुटि" "title": "त्रुटि"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "डिस्क मधे जतन करा", "disk_title": "डिस्क मधे जतन करा",
"disk_details": "सीन डेटा बाहेर एक फ़ाइल मधे जतन करा, त्या फ़ाइल मधुम तो डेटा नंतर परत आणु शकता.", "disk_details": "सीन डेटा बाहेर एक फ़ाइल मधे जतन करा, त्या फ़ाइल मधुम तो डेटा नंतर परत आणु शकता.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "मर्मेड पासून एक्सकाली मधे", "title": "मर्मेड पासून एक्सकाली मधे",
"button": "शिरवा", "button": "शिरवा",
"description": "", "description": "सध्या फक्त <flowchartLink> प्रवाह चित्र (फ़्लो चार्ट) </flowchartLink> आणि <sequenceLink> क्रम चित्र (सिकवेंस ड़ायग्राम) </sequenceLink> करता येतात. बाक़ीचे चित्र प्रकार एक्सकाली चित्र पद्धति नी चित्रित होतील.",
"syntax": "मर्मेड संरचना नियम", "syntax": "मर्मेड संरचना नियम",
"preview": "पूर्वावलोकन", "preview": "पूर्वावलोकन",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "ထား", "paste": "ထား",
"pasteAsPlaintext": "", "pasteAsPlaintext": "",
"pasteCharts": "", "pasteCharts": "",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "အကုန်ရွေး", "selectAll": "အကုန်ရွေး",
"multiSelect": "ရွေးထားသည့်ထဲပုံထည့်", "multiSelect": "ရွေးထားသည့်ထဲပုံထည့်",
"moveCanvas": "ကားချပ်ရွှေ့", "moveCanvas": "ကားချပ်ရွှေ့",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "ချို့ယွင်းချက်" "title": "ချို့ယွင်းချက်"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "", "disk_title": "",
"disk_details": "", "disk_details": "",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid til Excalidraw", "title": "Mermaid til Excalidraw",
"button": "Sett inn", "button": "Sett inn",
"description": "", "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.",
"syntax": "Mermaid-syntaks", "syntax": "Mermaid-syntaks",
"preview": "Forhåndsvisning", "preview": "Forhåndsvisning",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+9 -29
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -576,9 +557,9 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "Je tekeningen worden bewaard in de opslag van je browser.", "center_heading": "",
"center_heading_line2": "Browser opslag kan onverwacht gewist worden.", "center_heading_line2": "",
"center_heading_line3": "Sla je werk regelmatig op in een bestand om te voorkomen dat je het kwijtraakt.", "center_heading_line3": "",
"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, ..."
}, },
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid naar Excalidraw", "title": "Mermaid naar Excalidraw",
"button": "Invoegen", "button": "Invoegen",
"description": "", "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.",
"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!"
@@ -644,7 +624,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": "Genereren...", "generating": "",
"rateLimitRemaining": "{{count}} verzoeken over vandaag", "rateLimitRemaining": "{{count}} verzoeken over vandaag",
"role": { "role": {
"user": "Jij", "user": "Jij",
@@ -661,7 +641,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": "" "hint": "Op dit moment kennen we Flowchart, Sequence, en Class diagrammen."
}, },
"preview": "Voorbeeld", "preview": "Voorbeeld",
"insert": "Invoegen", "insert": "Invoegen",
@@ -675,7 +655,7 @@
"aiRepair": "Opnieuw genereren (automatisch repareren) →", "aiRepair": "Opnieuw genereren (automatisch repareren) →",
"requestAborted": "Verzoek gestopt", "requestAborted": "Verzoek gestopt",
"requestFailed": "Verzoek mislukt", "requestFailed": "Verzoek mislukt",
"mermaidParseError": "Mermaid syntax fout" "mermaidParseError": ""
}, },
"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.",
+2 -22
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"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.",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"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": "",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "De Mermaid cap a Excalidraw", "title": "De Mermaid cap a Excalidraw",
"button": "Inserir", "button": "Inserir",
"description": "", "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.",
"syntax": "Sintaxi Mermaid", "syntax": "Sintaxi Mermaid",
"preview": "Apercebut", "preview": "Apercebut",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+2 -22
View File
@@ -3,10 +3,6 @@
"paste": "ਪੇਸਟ ਕਰੋ", "paste": "ਪੇਸਟ ਕਰੋ",
"pasteAsPlaintext": "ਸਾਦੇ ਪਾਠ ਵਜੋਂ ਪੇਸਟ ਕਰੋ", "pasteAsPlaintext": "ਸਾਦੇ ਪਾਠ ਵਜੋਂ ਪੇਸਟ ਕਰੋ",
"pasteCharts": "ਚਾਰਟ ਪੇਸਟ ਕਰੋ", "pasteCharts": "ਚਾਰਟ ਪੇਸਟ ਕਰੋ",
"chartType_bar": "",
"chartType_line": "",
"chartType_radar": "",
"chartType_plaintext": "",
"selectAll": "ਸਾਰੇ ਚੁਣੋ", "selectAll": "ਸਾਰੇ ਚੁਣੋ",
"multiSelect": "ਐਲੀਮੈਂਟ ਨੂੰ ਚੋਣ ਵਿੱਚ ਜੋੜੋ", "multiSelect": "ਐਲੀਮੈਂਟ ਨੂੰ ਚੋਣ ਵਿੱਚ ਜੋੜੋ",
"moveCanvas": "ਕੈਨਵਸ ਹਿਲਾਓ", "moveCanvas": "ਕੈਨਵਸ ਹਿਲਾਓ",
@@ -53,14 +49,7 @@
"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": "",
@@ -182,11 +171,7 @@
"linkToElement": "", "linkToElement": "",
"wrapSelectionInFrame": "", "wrapSelectionInFrame": "",
"tab": "", "tab": "",
"shapeSwitch": "", "shapeSwitch": ""
"preferences": "",
"preferences_toolLock": "",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "", "title": "",
@@ -410,10 +395,6 @@
"errorDialog": { "errorDialog": {
"title": "ਗਲਤੀ" "title": "ਗਲਤੀ"
}, },
"progressDialog": {
"title": "",
"defaultMessage": ""
},
"exportDialog": { "exportDialog": {
"disk_title": "ਡਿਸਕ ਵਿੱਚ ਸਾਂਭੋ", "disk_title": "ਡਿਸਕ ਵਿੱਚ ਸਾਂਭੋ",
"disk_details": "ਦ੍ਰਿਸ਼ ਦਾ ਡਾਟਾ ਫਾਈਲ ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ ਜਿੱਥੋਂ ਤੁਸੀਂ ਇਸਨੂੰ ਬਾਅਦ ਵਿੱਚ ਆਯਾਤ ਕਰ ਸਕਦੇ ਹੋ।", "disk_details": "ਦ੍ਰਿਸ਼ ਦਾ ਡਾਟਾ ਫਾਈਲ ਵਿੱਚ ਨਿਰਯਾਤ ਕਰੋ ਜਿੱਥੋਂ ਤੁਸੀਂ ਇਸਨੂੰ ਬਾਅਦ ਵਿੱਚ ਆਯਾਤ ਕਰ ਸਕਦੇ ਹੋ।",
@@ -635,8 +616,7 @@
"syntax": "", "syntax": "",
"preview": "", "preview": "",
"label": "", "label": "",
"inputPlaceholder": "", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "" "error": ""
+52 -52
View File
@@ -1,59 +1,59 @@
{ {
"ar-SA": 82, "ar-SA": 84,
"az-AZ": 24, "az-AZ": 25,
"bg-BG": 63, "bg-BG": 65,
"bn-BD": 38, "bn-BD": 39,
"bn-IN": 38, "bn-IN": 39,
"ca-ES": 82, "ca-ES": 84,
"cs-CZ": 74, "cs-CZ": 76,
"da-DK": 28, "da-DK": 28,
"de-CH": 83, "de-CH": 85,
"de-DE": 83, "de-DE": 85,
"el-GR": 77, "el-GR": 80,
"en": 100, "en": 100,
"es-ES": 82, "es-ES": 85,
"eu-ES": 70, "eu-ES": 72,
"fa-IR": 83, "fa-IR": 84,
"fi-FI": 60, "fi-FI": 62,
"fr-FR": 97, "fr-FR": 92,
"gl-ES": 61, "gl-ES": 63,
"he-IL": 82, "he-IL": 84,
"hi-IN": 83, "hi-IN": 86,
"hu-HU": 65, "hu-HU": 67,
"id-ID": 81, "id-ID": 84,
"it-IT": 98, "it-IT": 99,
"ja-JP": 96, "ja-JP": 81,
"kaa": 21, "kaa": 22,
"kab-KAB": 54, "kab-KAB": 56,
"kk-KZ": 13, "kk-KZ": 13,
"km-KH": 54, "km-KH": 56,
"ko-KR": 73, "ko-KR": 75,
"ku-TR": 58, "ku-TR": 59,
"lt-LT": 33, "lt-LT": 35,
"lv-LV": 51, "lv-LV": 53,
"mr-IN": 82, "mr-IN": 84,
"my-MM": 24, "my-MM": 25,
"nb-NO": 82, "nb-NO": 84,
"nl-NL": 96, "nl-NL": 99,
"nn-NO": 43, "nn-NO": 45,
"oc-FR": 76, "oc-FR": 79,
"pa-IN": 52, "pa-IN": 54,
"pl-PL": 90, "pl-PL": 93,
"pt-BR": 86, "pt-BR": 84,
"pt-PT": 82, "pt-PT": 84,
"ro-RO": 98, "ro-RO": 100,
"ru-RU": 98, "ru-RU": 99,
"si-LK": 67, "si-LK": 70,
"sk-SK": 90, "sk-SK": 93,
"sl-SI": 82, "sl-SI": 84,
"sv-SE": 82, "sv-SE": 85,
"ta-IN": 78, "ta-IN": 81,
"th-TH": 65, "th-TH": 59,
"tr-TR": 92, "tr-TR": 88,
"uk-UA": 86, "uk-UA": 84,
"uz-UZ": 0, "uz-UZ": 0,
"vi-VN": 68, "vi-VN": 70,
"zh-CN": 91, "zh-CN": 93,
"zh-HK": 16, "zh-HK": 16,
"zh-TW": 98 "zh-TW": 84
} }
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Konwertuj diagram Mermaid do Excalidraw", "title": "Konwertuj diagram Mermaid do Excalidraw",
"button": "Wstaw", "button": "Wstaw",
"description": "", "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.",
"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": ""
+26 -46
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -146,15 +135,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": "Ir para o elemento alvo" "goToElement": ""
}, },
"lineEditor": { "lineEditor": {
"edit": "Editar linha", "edit": "Editar linha",
"editArrow": "Editar seta" "editArrow": "Editar seta"
}, },
"polygon": { "polygon": {
"breakPolygon": "Parar polígono", "breakPolygon": "",
"convertToPolygon": "Converter para polígono" "convertToPolygon": ""
}, },
"elementLock": { "elementLock": {
"lock": "Bloquear", "lock": "Bloquear",
@@ -181,15 +170,11 @@
"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": "Aba", "tab": "",
"shapeSwitch": "Trocar forma", "shapeSwitch": ""
"preferences": "Preferências",
"preferences_toolLock": "Bloqueio de ferramenta",
"arrowBinding": "",
"midpointSnapping": ""
}, },
"elementLink": { "elementLink": {
"title": "Links para o objeto", "title": "",
"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."
}, },
@@ -199,9 +184,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": "Correspondências da biblioteca", "heading": "",
"noResults": "Nenhum resultado encontrado...", "noResults": "",
"clearSearch": "Limpar pesquisa" "clearSearch": ""
} }
}, },
"search": { "search": {
@@ -210,8 +195,8 @@
"singleResult": "resultado", "singleResult": "resultado",
"multipleResults": "resultados", "multipleResults": "resultados",
"placeholder": "Procurar texto na tela...", "placeholder": "Procurar texto na tela...",
"frames": "Quadros", "frames": "",
"texts": "Textos" "texts": ""
}, },
"buttons": { "buttons": {
"clearReset": "Limpar a tela", "clearReset": "Limpar a tela",
@@ -245,7 +230,7 @@
"objectsSnapMode": "Encaixar em objetos", "objectsSnapMode": "Encaixar em objetos",
"exitZenMode": "Sair do modo zen", "exitZenMode": "Sair do modo zen",
"cancel": "Cancelar", "cancel": "Cancelar",
"saveLibNames": "Salvar nome(s) e sair", "saveLibNames": "",
"clear": "Limpar", "clear": "Limpar",
"remove": "Remover", "remove": "Remover",
"embed": "Alternar incorporação", "embed": "Alternar incorporação",
@@ -276,7 +261,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": "A cota de armazenamento do navegador excedida. As alterações não serão salvas." "localStorageQuotaExceeded": ""
}, },
"errors": { "errors": {
"unsupportedFileType": "Tipo de arquivo não suportado.", "unsupportedFileType": "Tipo de arquivo não suportado.",
@@ -328,7 +313,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": "Alternar tipo de forma" "convertElementType": ""
}, },
"element": { "element": {
"rectangle": "Retângulo", "rectangle": "Retângulo",
@@ -352,7 +337,7 @@
"shapes": "Formas" "shapes": "Formas"
}, },
"hints": { "hints": {
"dismissSearch": "{{shortcut}} para descartar a pesquisa", "dismissSearch": "",
"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": "",
@@ -372,8 +357,8 @@
"lineEditor_pointSelected": "", "lineEditor_pointSelected": "",
"lineEditor_nothingSelected": "", "lineEditor_nothingSelected": "",
"publishLibrary": "Publicar sua própria biblioteca", "publishLibrary": "Publicar sua própria biblioteca",
"bindTextToElement": "{{shortcut}} para adicionar texto", "bindTextToElement": "",
"createFlowchart": "{{shortcut}} para criar um fluxo", "createFlowchart": "",
"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\".",
@@ -410,10 +395,6 @@
"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.",
@@ -555,7 +536,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": "Link copiado para a área de transferência" "elementLinkCopied": ""
}, },
"colors": { "colors": {
"transparent": "Transparente", "transparent": "Transparente",
@@ -576,8 +557,8 @@
}, },
"welcomeScreen": { "welcomeScreen": {
"app": { "app": {
"center_heading": "Seus desenhos estão salvos no armazenamento do seu navegador.", "center_heading": "",
"center_heading_line2": "O armazenamento do navegador pode ser apagado inesperadamente.", "center_heading_line2": "",
"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..."
@@ -590,7 +571,7 @@
} }
}, },
"colorPicker": { "colorPicker": {
"color": "Cor", "color": "",
"mostUsedCustomColors": "Cores personalizadas mais usadas", "mostUsedCustomColors": "Cores personalizadas mais usadas",
"colors": "Cores", "colors": "Cores",
"shades": "Tons", "shades": "Tons",
@@ -631,19 +612,18 @@
"mermaid": { "mermaid": {
"title": "Mermaid para Excalidraw", "title": "Mermaid para Excalidraw",
"button": "Inserir", "button": "Inserir",
"description": "", "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.",
"syntax": "Sintaxe em Mermaid", "syntax": "Sintaxe em Mermaid",
"preview": "Visualizar", "preview": "Visualizar",
"label": "Mermaid", "label": "",
"inputPlaceholder": "Escreva definição de diagrama Mermaid aqui...", "inputPlaceholder": ""
"autoFixAvailable": ""
}, },
"ttd": { "ttd": {
"error": "Erro!" "error": ""
}, },
"chat": { "chat": {
"inputPlaceholder": "", "inputPlaceholder": "",
"inputPlaceholderWithMessages": "Continuar refinando seu diagrama...", "inputPlaceholderWithMessages": "",
"generating": "", "generating": "",
"rateLimitRemaining": "", "rateLimitRemaining": "",
"role": { "role": {
+3 -23
View File
@@ -3,10 +3,6 @@
"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",
@@ -53,14 +49,7 @@
"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",
@@ -182,11 +171,7 @@
"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",
@@ -410,10 +395,6 @@
"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.",
@@ -631,12 +612,11 @@
"mermaid": { "mermaid": {
"title": "Mermaid para Excalidraw", "title": "Mermaid para Excalidraw",
"button": "Inserir", "button": "Inserir",
"description": "", "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.",
"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