diff --git a/package-lock.json b/package-lock.json
index 7939650..54398b7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,8 @@
"file-saver": "^2.0.5",
"framer-motion": "^10.18.0",
"html-to-image": "^1.11.11",
+ "i18next": "^23.11.4",
+ "i18next-browser-languagedetector": "^8.0.0",
"jsonschema": "^1.4.1",
"jspdf": "^2.5.1",
"lexical": "^0.12.5",
@@ -29,6 +31,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.4.1",
+ "react-i18next": "^14.1.1",
"react-router-dom": "^6.21.0",
"url": "^0.11.1"
},
@@ -361,9 +364,9 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.23.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz",
- "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
+ "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -3611,6 +3614,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
"node_modules/html-to-image": {
"version": "1.11.11",
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz",
@@ -3629,6 +3640,36 @@
"node": ">=8.0.0"
}
},
+ "node_modules/i18next": {
+ "version": "23.11.4",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.4.tgz",
+ "integrity": "sha512-CCUjtd5TfaCl+mLUzAA0uPSN+AVn4fP/kWCYt/hocPUwusTpMVczdrRyOBUwk6N05iH40qiKx6q1DoNJtBIwdg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
+ "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
@@ -4978,6 +5019,27 @@
"react-dom": ">=16.8.1"
}
},
+ "node_modules/react-i18next": {
+ "version": "14.1.1",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.1.tgz",
+ "integrity": "sha512-QSiKw+ihzJ/CIeIYWrarCmXJUySHDwQr5y8uaNIkbxoGRm/5DukkxZs+RPla79IKyyDPzC/DRlgQCABHtrQuQQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "peerDependencies": {
+ "i18next": ">= 23.2.3",
+ "react": ">= 16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -5898,6 +5960,14 @@
}
}
},
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
diff --git a/package.json b/package.json
index fa19917..cff6fa5 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,8 @@
"file-saver": "^2.0.5",
"framer-motion": "^10.18.0",
"html-to-image": "^1.11.11",
+ "i18next": "^23.11.4",
+ "i18next-browser-languagedetector": "^8.0.0",
"jsonschema": "^1.4.1",
"jspdf": "^2.5.1",
"lexical": "^0.12.5",
@@ -31,6 +33,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.4.1",
+ "react-i18next": "^14.1.1",
"react-router-dom": "^6.21.0",
"url": "^0.11.1"
},
diff --git a/src/components/ColorPalette.jsx b/src/components/ColorPicker.jsx
similarity index 86%
rename from src/components/ColorPalette.jsx
rename to src/components/ColorPicker.jsx
index d24a3f4..0e15948 100644
--- a/src/components/ColorPalette.jsx
+++ b/src/components/ColorPicker.jsx
@@ -1,18 +1,20 @@
import { Button } from "@douyinfe/semi-ui";
import { IconCheckboxTick } from "@douyinfe/semi-icons";
import { tableThemes } from "../data/constants";
+import { useTranslation } from "react-i18next";
export default function ColorPalette({
currentColor,
onClearColor,
onPickColor,
}) {
+ const { t } = useTranslation();
return (
-
Theme
+
{t("theme")}
diff --git a/src/components/EditorCanvas/Area.jsx b/src/components/EditorCanvas/Area.jsx
index 0d5ecd5..5480217 100644
--- a/src/components/EditorCanvas/Area.jsx
+++ b/src/components/EditorCanvas/Area.jsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { Button, Popover, Input, Toast } from "@douyinfe/semi-ui";
+import { Button, Popover, Input } from "@douyinfe/semi-ui";
import { IconEdit, IconDeleteStroked } from "@douyinfe/semi-icons";
import {
Tab,
@@ -17,7 +17,8 @@ import {
useSaveState,
useTransform,
} from "../../hooks";
-import ColorPalette from "../ColorPalette";
+import ColorPalette from "../ColorPicker";
+import { useTranslation } from "react-i18next";
export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
const [hovered, setHovered] = useState(false);
@@ -191,14 +192,15 @@ function EditPopoverContent({ data }) {
const { setSaveState } = useSaveState();
const { updateArea, deleteArea } = useAreas();
const { setUndoStack, setRedoStack } = useUndoRedo();
+ const { t } = useTranslation();
return (
-
Edit subject area
+
{t("edit")}
updateArea(data.id, { name: value })}
onFocus={(e) => setEditField({ name: e.target.value })}
@@ -212,7 +214,10 @@ function EditPopoverContent({ data }) {
aid: data.id,
undo: editField,
redo: { name: e.target.value },
- message: `Edit area name to ${e.target.value}`,
+ message: t("edit_area", {
+ areaName: e.target.value,
+ extra: "[name]",
+ }),
},
]);
setRedoStack([]);
@@ -232,7 +237,10 @@ function EditPopoverContent({ data }) {
aid: data.id,
undo: { color: data.color },
redo: { color: c },
- message: `Edit area color to ${c}`,
+ message: t("edit_area", {
+ areaName: data.name,
+ extra: "[color]",
+ }),
},
]);
setRedoStack([]);
@@ -263,12 +271,9 @@ function EditPopoverContent({ data }) {
icon={}
type="danger"
block
- onClick={() => {
- Toast.success(`Area deleted!`);
- deleteArea(data.id, true);
- }}
+ onClick={() => deleteArea(data.id, true)}
>
- Delete
+ {t("delete")}
diff --git a/src/components/EditorCanvas/Canvas.jsx b/src/components/EditorCanvas/Canvas.jsx
index 90febb7..581a7b6 100644
--- a/src/components/EditorCanvas/Canvas.jsx
+++ b/src/components/EditorCanvas/Canvas.jsx
@@ -20,8 +20,11 @@ import {
useNotes,
useLayout,
} from "../../hooks";
+import { useTranslation } from "react-i18next";
+import { diagram } from "../../data/heroDiagram";
export default function Canvas() {
+ const { t } = useTranslation();
const { tables, updateTable, relationships, addRelationship } = useTables();
const { areas, updateArea } = useAreas();
const { notes, updateNote } = useNotes();
@@ -278,7 +281,10 @@ export default function Canvas() {
toX: info.x,
toY: info.y,
id: dragging.id,
- message: `Move ${info.name} to (${info.x}, ${info.y})`,
+ message: t("move_element", {
+ coords: `(${info.x}, ${info.y})`,
+ name: info.name,
+ }),
},
]);
setRedoStack([]);
@@ -291,7 +297,10 @@ export default function Canvas() {
action: Action.PAN,
undo: { x: panning.x, y: panning.y },
redo: transform.pan,
- message: `Move diagram to (${transform.pan?.x}, ${transform.pan?.y})`,
+ message: t("move_element", {
+ coords: `(${transform?.pan.x}, ${transform?.pan.y})`,
+ name: diagram,
+ }),
},
]);
setRedoStack([]);
@@ -321,7 +330,10 @@ export default function Canvas() {
height: initCoords.height,
},
redo: areas[areaResize.id],
- message: `Resize area`,
+ message: t("edit_area", {
+ areaName: areas[areaResize.id].name,
+ extra: "[resize]",
+ }),
},
]);
setRedoStack([]);
@@ -350,7 +362,7 @@ export default function Canvas() {
tables[linkingLine.startTableId].fields[linkingLine.startFieldId].type !==
tables[hoveredTable.tableId].fields[hoveredTable.field].type
) {
- Toast.info("Cannot connect");
+ Toast.info(t("connot_connect"));
return;
}
if (
diff --git a/src/components/EditorCanvas/Note.jsx b/src/components/EditorCanvas/Note.jsx
index f3abc28..e2d67fc 100644
--- a/src/components/EditorCanvas/Note.jsx
+++ b/src/components/EditorCanvas/Note.jsx
@@ -6,7 +6,7 @@ import {
State,
noteThemes,
} from "../../data/constants";
-import { Input, Button, Popover, Toast } from "@douyinfe/semi-ui";
+import { Input, Button, Popover } from "@douyinfe/semi-ui";
import {
IconEdit,
IconDeleteStroked,
@@ -19,6 +19,7 @@ import {
useNotes,
useSaveState,
} from "../../hooks";
+import { useTranslation } from "react-i18next";
export default function Note({ data, onMouseDown }) {
const w = 180;
@@ -27,6 +28,7 @@ export default function Note({ data, onMouseDown }) {
const [editField, setEditField] = useState({});
const [hovered, setHovered] = useState(false);
const { layout } = useLayout();
+ const { t } = useTranslation();
const { setSaveState } = useSaveState();
const { updateNote, deleteNote } = useNotes();
const { setUndoStack, setRedoStack } = useUndoRedo();
@@ -54,7 +56,10 @@ export default function Note({ data, onMouseDown }) {
nid: data.id,
undo: editField,
redo: { content: e.target.value, height: newHeight },
- message: `Edit note content to "${e.target.value}"`,
+ message: t("edit_note", {
+ noteTitle: e.target.value,
+ extra: "[content]",
+ }),
},
]);
setRedoStack([]);
@@ -168,11 +173,11 @@ export default function Note({ data, onMouseDown }) {
stopPropagation
content={
-
Edit note
+
{t("edit")}
updateNote(data.id, { title: value })
@@ -190,7 +195,10 @@ export default function Note({ data, onMouseDown }) {
nid: data.id,
undo: editField,
redo: { title: e.target.value },
- message: `Edit note title to "${e.target.value}"`,
+ message: t("edit_note", {
+ noteTitle: e.target.value,
+ extra: "[title]",
+ }),
},
]);
setRedoStack([]);
@@ -199,7 +207,9 @@ export default function Note({ data, onMouseDown }) {
- Theme
+
+ {t("theme")}
+
{noteThemes.map((c) => (
@@ -216,7 +226,10 @@ export default function Note({ data, onMouseDown }) {
nid: data.id,
undo: { color: data.color },
redo: { color: c },
- message: `Edit note color to ${c}`,
+ message: t("edit_note", {
+ noteTitle: data.title,
+ extra: "[color]",
+ }),
},
]);
setRedoStack([]);
@@ -249,12 +262,9 @@ export default function Note({ data, onMouseDown }) {
icon={}
type="danger"
block
- onClick={() => {
- Toast.success(`Note deleted!`);
- deleteNote(data.id, true);
- }}
+ onClick={() => deleteNote(data.id, true)}
>
- Delete
+ {t("delete")}
diff --git a/src/components/EditorCanvas/Table.jsx b/src/components/EditorCanvas/Table.jsx
index 12b6727..248fb7d 100644
--- a/src/components/EditorCanvas/Table.jsx
+++ b/src/components/EditorCanvas/Table.jsx
@@ -13,9 +13,10 @@ import {
IconDeleteStroked,
IconKeyStroked,
} from "@douyinfe/semi-icons";
-import { Popover, Tag, Button, Toast, SideSheet } from "@douyinfe/semi-ui";
+import { Popover, Tag, Button, SideSheet } from "@douyinfe/semi-ui";
import { useLayout, useSettings, useTables, useSelect } from "../../hooks";
import TableInfo from "../EditorSidePanel/TablesTab/TableInfo";
+import { useTranslation } from "react-i18next";
export default function Table(props) {
const [hoveredField, setHoveredField] = useState(-1);
@@ -29,6 +30,7 @@ export default function Table(props) {
const { layout } = useLayout();
const { deleteTable, deleteField } = useTables();
const { settings } = useSettings();
+ const { t } = useTranslation();
const { selectedElement, setSelectedElement } = useSelect();
const height =
@@ -110,9 +112,9 @@ export default function Table(props) {
content={
-
Comment :{" "}
+
{t("comment")}:{" "}
{tableData.comment === "" ? (
- "No comment"
+ t("not_set")
) : (
{tableData.comment}
)}
@@ -123,10 +125,10 @@ export default function Table(props) {
tableData.indices.length === 0 ? "" : "block"
}`}
>
- Indices :
+ {t("indices")}:
{" "}
{tableData.indices.length === 0 ? (
- "No indices"
+ t("not_set")
) : (
{tableData.indices.map((index, k) => (
@@ -156,12 +158,9 @@ export default function Table(props) {
type="danger"
block
style={{ marginTop: "8px" }}
- onClick={() => {
- Toast.success(`Table deleted!`);
- deleteTable(tableData.id);
- }}
+ onClick={() => deleteTable(tableData.id)}
>
- Delete table
+ {t("delete")}
}
@@ -196,37 +195,31 @@ export default function Table(props) {
{e.primary && (
- Primary
+ {t("primary")}
)}
{e.unique && (
- Unique
+ {t("unique")}
)}
{e.notNull && (
- Not null
+ {t("not_null")}
)}
{e.increment && (
- Increment
+ {t("autoincrement")}
)}
- Default:
- {e.default === "" ? "Not set" : e.default}
+ {t("default_value")}:
+ {e.default === "" ? t("not_set") : e.default}
- Comment:
- {e.comment === "" ? (
- "No comment"
- ) : (
-
- {e.comment}
-
- )}
+
{t("comment")}:
+ {e.comment === "" ? t("not_set") : e.comment}
}
@@ -242,7 +235,7 @@ export default function Table(props) {
@@ -460,16 +461,12 @@ export default function ControlPanel({
setTransform((prev) => ({ ...prev, zoom: prev.zoom / 1.2 }));
const viewStrictMode = () => {
setSettings((prev) => ({ ...prev, strictMode: !prev.strictMode }));
- Toast.success(`Stict mode is ${settings.strictMode ? "on" : "off"}.`);
};
const viewFieldSummary = () => {
setSettings((prev) => ({
...prev,
showFieldSummary: !prev.showFieldSummary,
}));
- Toast.success(
- `Field summary is ${settings.showFieldSummary ? "off" : "on"}.`,
- );
};
const copyAsImage = () => {
toPng(document.getElementById("canvas")).then(function (dataUrl) {
@@ -477,10 +474,10 @@ export default function ControlPanel({
navigator.clipboard
.write([new ClipboardItem({ "image/png": blob })])
.then(() => {
- Toast.success("Copied to clipboard.");
+ Toast.success(t("copied_to_clipboard"));
})
.catch(() => {
- Toast.error("Could not copy to clipboard.");
+ Toast.error(t("oops_smth_went_wrong"));
});
});
};
@@ -607,23 +604,17 @@ export default function ControlPanel({
case ObjectType.TABLE:
navigator.clipboard
.writeText(JSON.stringify({ ...tables[selectedElement.id] }))
- .catch(() => {
- Toast.error("Could not copy");
- });
+ .catch(() => Toast.error(t("oops_smth_went_wrong")));
break;
case ObjectType.NOTE:
navigator.clipboard
.writeText(JSON.stringify({ ...notes[selectedElement.id] }))
- .catch(() => {
- Toast.error("Could not copy");
- });
+ .catch(() => Toast.error(t("oops_smth_went_wrong")));
break;
case ObjectType.AREA:
navigator.clipboard
.writeText(JSON.stringify({ ...areas[selectedElement.id] }))
- .catch(() => {
- Toast.error("Could not copy");
- });
+ .catch(() => Toast.error(t("oops_smth_went_wrong")));
break;
default:
break;
@@ -671,29 +662,29 @@ export default function ControlPanel({
const saveDiagramAs = () => setModal(MODAL.SAVEAS);
const menu = {
- File: {
- New: {
+ file: {
+ new: {
function: () => setModal(MODAL.NEW),
},
- "New window": {
+ new_window: {
function: () => {
const newWindow = window.open("/editor", "_blank");
newWindow.name = window.name;
},
},
- Open: {
+ open: {
function: open,
shortcut: "Ctrl+O",
},
- Save: {
+ save: {
function: save,
shortcut: "Ctrl+S",
},
- "Save as": {
+ save_as: {
function: saveDiagramAs,
shortcut: "Ctrl+Shift+S",
},
- "Save as template": {
+ save_as_template: {
function: () => {
db.templates
.add({
@@ -706,21 +697,20 @@ export default function ControlPanel({
custom: 1,
})
.then(() => {
- Toast.success("Template saved!");
+ Toast.success(t("template_saved"));
});
},
},
- Rename: {
+ rename: {
function: () => {
setModal(MODAL.RENAME);
setPrevTitle(title);
},
},
- "Delete diagram": {
+ delete_diagram: {
warning: {
- title: "Delete diagram",
- message:
- "Are you sure you want to delete this diagram? This operation is irreversible.",
+ title: t("delete_diagram"),
+ message: t("are_you_sure_delete_diagram"),
},
function: async () => {
await db.diagrams
@@ -736,17 +726,17 @@ export default function ControlPanel({
setUndoStack([]);
setRedoStack([]);
})
- .catch(() => Toast.error("Oops! Something went wrong."));
+ .catch(() => Toast.error(t("oops_smth_went_wrong")));
},
},
- "Import diagram": {
+ import_diagram: {
function: fileImport,
shortcut: "Ctrl+I",
},
- "Import from source": {
+ import_from_source: {
function: () => setModal(MODAL.IMPORT_SRC),
},
- "Export as": {
+ export_as: {
children: [
{
PNG: () => {
@@ -856,7 +846,7 @@ export default function ControlPanel({
],
function: () => {},
},
- "Export source": {
+ export_source: {
children: [
{
MySQL: () => {
@@ -936,23 +926,27 @@ export default function ControlPanel({
],
function: () => {},
},
- Exit: {
+ exit: {
function: () => {
save();
if (saveState === State.SAVED) navigate("/");
},
},
},
- Edit: {
- Undo: {
+ edit: {
+ undo: {
function: undo,
shortcut: "Ctrl+Z",
},
- Redo: {
+ redo: {
function: redo,
shortcut: "Ctrl+Y",
},
- Clear: {
+ clear: {
+ warning: {
+ title: t("clear"),
+ message: t("are_you_sure_clear"),
+ },
function: () => {
setTables([]);
setRelationships([]);
@@ -962,57 +956,73 @@ export default function ControlPanel({
setRedoStack([]);
},
},
- Edit: {
+ edit: {
function: edit,
shortcut: "Ctrl+E",
},
- Cut: {
+ cut: {
function: cut,
shortcut: "Ctrl+X",
},
- Copy: {
+ copy: {
function: copy,
shortcut: "Ctrl+C",
},
- Paste: {
+ paste: {
function: paste,
shortcut: "Ctrl+V",
},
- Duplicate: {
+ duplicate: {
function: duplicate,
shortcut: "Ctrl+D",
},
- Delete: {
+ delete: {
function: del,
shortcut: "Del",
},
- "Copy as image": {
+ copy_as_image: {
function: copyAsImage,
shortcut: "Ctrl+Alt+C",
},
},
- View: {
- Header: {
- state: layout.header ? "on" : "off",
+ view: {
+ header: {
+ state: layout.header ? (
+
+ ) : (
+
+ ),
function: () =>
setLayout((prev) => ({ ...prev, header: !prev.header })),
},
- Sidebar: {
- state: layout.sidebar ? "on" : "off",
+ sidebar: {
+ state: layout.sidebar ? (
+
+ ) : (
+
+ ),
function: () =>
setLayout((prev) => ({ ...prev, sidebar: !prev.sidebar })),
},
- Issues: {
- state: layout.issues ? "on" : "off",
+ issues: {
+ state: layout.issues ? (
+
+ ) : (
+
+ ),
function: () =>
setLayout((prev) => ({ ...prev, issues: !prev.issues })),
},
- "Strict mode": {
- state: settings.strictMode ? "off" : "on",
+ strict_mode: {
+ state: settings.strictMode ? (
+
+ ) : (
+
+ ),
function: viewStrictMode,
shortcut: "Ctrl+Shift+M",
},
- "Presentation mode": {
+ presentation_mode: {
function: () => {
setLayout((prev) => ({
...prev,
@@ -1023,32 +1033,44 @@ export default function ControlPanel({
enterFullscreen();
},
},
- "Field details": {
- state: settings.showFieldSummary ? "on" : "off",
+ field_details: {
+ state: settings.showFieldSummary ? (
+
+ ) : (
+
+ ),
function: viewFieldSummary,
shortcut: "Ctrl+Shift+F",
},
- "Reset view": {
+ reset_view: {
function: resetView,
shortcut: "Ctrl+R",
},
- "Show grid": {
- state: settings.showGrid ? "on" : "off",
+ show_grid: {
+ state: settings.showGrid ? (
+
+ ) : (
+
+ ),
function: viewGrid,
shortcut: "Ctrl+Shift+G",
},
- "Show cardinality": {
- state: settings.showCardinality ? "on" : "off",
+ show_cardinality: {
+ state: settings.showCardinality ? (
+
+ ) : (
+
+ ),
function: () =>
setSettings((prev) => ({
...prev,
showCardinality: !prev.showCardinality,
})),
},
- Theme: {
+ theme: {
children: [
{
- Light: () => {
+ light: () => {
const body = document.body;
if (body.hasAttribute("theme-mode")) {
body.setAttribute("theme-mode", "light");
@@ -1058,7 +1080,7 @@ export default function ControlPanel({
},
},
{
- Dark: () => {
+ dark: () => {
const body = document.body;
if (body.hasAttribute("theme-mode")) {
body.setAttribute("theme-mode", "dark");
@@ -1070,71 +1092,75 @@ export default function ControlPanel({
],
function: () => {},
},
- "Zoom in": {
+ zoom_in: {
function: zoomIn,
shortcut: "Ctrl+Up/Wheel",
},
- "Zoom out": {
+ zoom_out: {
function: zoomOut,
shortcut: "Ctrl+Down/Wheel",
},
- Fullscreen: {
+ fullscreen: {
function: enterFullscreen,
},
},
- Settings: {
- "Show timeline": {
+ settings: {
+ show_timeline: {
function: () => setSidesheet(SIDESHEET.TIMELINE),
},
- Autosave: {
- state: settings.autosave ? "on" : "off",
+ autosave: {
+ state: settings.autosave ? (
+
+ ) : (
+
+ ),
function: () =>
- setSettings((prev) => {
- Toast.success(`Autosave is ${settings.autosave ? "off" : "on"}`);
- return { ...prev, autosave: !prev.autosave };
- }),
+ setSettings((prev) => ({ ...prev, autosave: !prev.autosave })),
},
- Panning: {
- state: settings.panning ? "on" : "off",
+ panning: {
+ state: settings.panning ? (
+
+ ) : (
+
+ ),
function: () =>
- setSettings((prev) => {
- Toast.success(`Panning is ${settings.panning ? "off" : "on"}`);
- return { ...prev, panning: !prev.panning };
- }),
+ setSettings((prev) => ({ ...prev, panning: !prev.panning })),
},
- "Table width": {
+ table_width: {
function: () => setModal(MODAL.TABLE_WIDTH),
},
- "Flush storage": {
+ language: {
+ function: () => setModal(MODAL.LANGUAGE),
+ },
+ flush_storage: {
warning: {
- title: "Flush storage",
- message:
- "Are you sure you want to flush the storage? This will irreversibly delete all your diagrams and custom templates.",
+ title: t("flush_storage"),
+ message: t("are_you_sure_flush_storage"),
},
function: async () => {
db.delete()
.then(() => {
- Toast.success("Storage flushed");
+ Toast.success(t("storage_flushed"));
window.location.reload(false);
})
.catch(() => {
- Toast.error("Oops! Something went wrong.");
+ Toast.error(t("oops_smth_went_wrong"));
});
},
},
},
- Help: {
- Shortcuts: {
+ help: {
+ shortcuts: {
function: () => window.open("/shortcuts", "_blank"),
shortcut: "Ctrl+H",
},
- "Ask us on discord": {
+ ask_on_discord: {
function: () => window.open("https://discord.gg/BrjZgNrmR6", "_blank"),
},
- "Report a bug": {
+ report_bug: {
function: () => window.open("/bug-report", "_blank"),
},
- "Give feedback": {
+ feedback: {
function: () => window.open("/survey", "_blank"),
},
},
@@ -1207,7 +1233,7 @@ export default function ControlPanel({
onClick={fitWindow}
style={{ display: "flex", justifyContent: "space-between" }}
>
- Fit window / Reset
+ {t("fit_window_reset")}
Ctrl+Alt+W
@@ -1225,8 +1251,8 @@ export default function ControlPanel({
%}
onChange={(v) =>
setTransform((prev) => ({
@@ -1249,7 +1275,7 @@ export default function ControlPanel({
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+