From 2b4b01c358b2bd00b02b41995978b2a9b4ef2881 Mon Sep 17 00:00:00 2001 From: 1ilit Date: Thu, 16 May 2024 06:44:39 +0300 Subject: [PATCH 1/7] Configure i18n and add simplified chinese (#99) --- package-lock.json | 76 ++++- package.json | 3 + .../{ColorPalette.jsx => ColorPicker.jsx} | 6 +- src/components/EditorCanvas/Area.jsx | 27 +- src/components/EditorCanvas/Canvas.jsx | 20 +- src/components/EditorCanvas/Note.jsx | 34 ++- src/components/EditorCanvas/Table.jsx | 43 ++- src/components/EditorHeader/ControlPanel.jsx | 277 ++++++++++-------- .../EditorHeader/LayoutDropdown.jsx | 11 +- .../EditorHeader/Modal/ImportDiagram.jsx | 6 +- .../EditorHeader/Modal/ImportSource.jsx | 11 +- .../EditorHeader/Modal/Language.jsx | 30 ++ src/components/EditorHeader/Modal/Modal.jsx | 15 +- src/components/EditorHeader/Modal/New.jsx | 4 +- src/components/EditorHeader/Modal/Open.jsx | 8 +- src/components/EditorHeader/Modal/Rename.jsx | 5 +- .../EditorHeader/SideSheet/Sidesheet.jsx | 6 +- .../EditorHeader/SideSheet/Timeline.jsx | 9 +- .../EditorHeader/SideSheet/Todo.jsx | 57 ++-- .../EditorSidePanel/AreasTab/AreaDetails.jsx | 23 +- .../EditorSidePanel/AreasTab/AreasTab.jsx | 8 +- .../EditorSidePanel/AreasTab/SearchBar.jsx | 10 +- src/components/EditorSidePanel/Issues.jsx | 12 +- .../EditorSidePanel/NotesTab/NoteInfo.jsx | 39 +-- .../EditorSidePanel/NotesTab/NotesTab.jsx | 6 +- .../EditorSidePanel/NotesTab/SearchBar.jsx | 11 +- .../RelationshipsTab/RelationshipInfo.jsx | 51 ++-- .../RelationshipsTab/RelationshipsTab.jsx | 6 +- .../RelationshipsTab/SearchBar.jsx | 9 +- src/components/EditorSidePanel/SidePanel.jsx | 29 +- .../TablesTab/FieldDetails.jsx | 76 +++-- .../TablesTab/IndexDetails.jsx | 31 +- .../EditorSidePanel/TablesTab/SearchBar.jsx | 6 +- .../EditorSidePanel/TablesTab/TableField.jsx | 31 +- .../EditorSidePanel/TablesTab/TableInfo.jsx | 54 ++-- .../EditorSidePanel/TablesTab/TablesTab.jsx | 6 +- .../EditorSidePanel/TypesTab/SearchBar.jsx | 6 +- .../EditorSidePanel/TypesTab/TypeField.jsx | 71 +++-- .../EditorSidePanel/TypesTab/TypeInfo.jsx | 35 ++- .../EditorSidePanel/TypesTab/TypesTab.jsx | 28 +- src/components/FloatingControls.jsx | 4 +- src/context/AreasContext.jsx | 12 +- src/context/NotesContext.jsx | 12 +- src/context/TablesContext.jsx | 31 +- src/context/TypesContext.jsx | 12 +- src/data/constants.js | 11 +- src/i18n/i18n.js | 24 ++ src/i18n/locales/en.js | 218 ++++++++++++++ src/i18n/locales/zh.js | 213 ++++++++++++++ src/main.jsx | 3 +- src/utils/issues.js | 98 +++++-- src/utils/modalTitles.js | 33 ++- 52 files changed, 1339 insertions(+), 528 deletions(-) rename src/components/{ColorPalette.jsx => ColorPicker.jsx} (86%) create mode 100644 src/components/EditorHeader/Modal/Language.jsx create mode 100644 src/i18n/i18n.js create mode 100644 src/i18n/locales/en.js create mode 100644 src/i18n/locales/zh.js 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({
- + - + - + - + - + - + - + - + - + + ))} + + ); +} diff --git a/src/components/EditorHeader/Modal/Modal.jsx b/src/components/EditorHeader/Modal/Modal.jsx index f2f870b..409f017 100644 --- a/src/components/EditorHeader/Modal/Modal.jsx +++ b/src/components/EditorHeader/Modal/Modal.jsx @@ -27,11 +27,13 @@ import New from "./New"; import ImportDiagram from "./ImportDiagram"; import ImportSource from "./ImportSource"; import SetTableWidth from "./SetTableWidth"; +import Language from "./Language"; import CodeMirror from "@uiw/react-codemirror"; import { sql } from "@codemirror/lang-sql"; import { vscodeDark } from "@uiw/codemirror-theme-vscode"; import { json } from "@codemirror/lang-json"; import { githubLight } from "@uiw/codemirror-theme-github"; +import { useTranslation } from "react-i18next"; const languageExtension = { sql: [sql()], @@ -49,6 +51,7 @@ export default function Modal({ exportData, setExportData, }) { + const { t } = useTranslation(); const { setTables, setRelationships } = useTables(); const { setNotes } = useNotes(); const { setAreas } = useAreas(); @@ -239,7 +242,7 @@ export default function Modal({ case MODAL.SAVEAS: return ( setSaveAsTitle(v)} /> @@ -261,10 +264,10 @@ export default function Modal({ theme={settings.mode === "dark" ? vscodeDark : githubLight} /> )} -
Filename:
+
{t("filename")}:
{`.${exportData.extension}`}} onChange={(value) => setExportData((prev) => ({ ...prev, filename: value })) @@ -276,12 +279,14 @@ export default function Modal({ } else { return (
- +
); } case MODAL.TABLE_WIDTH: return ; + case MODAL.LANGUAGE: + return ; default: return <>; } @@ -326,7 +331,7 @@ export default function Modal({ (modal === MODAL.SAVEAS && saveAsTitle === "") || (modal === MODAL.IMPORT_SRC && importSource.src === ""), }} - cancelText="Cancel" + cancelText={t("cancel")} width={modal === MODAL.NEW ? 740 : 600} > {getModalBody()} diff --git a/src/components/EditorHeader/Modal/New.jsx b/src/components/EditorHeader/Modal/New.jsx index 0733ef7..48151fb 100644 --- a/src/components/EditorHeader/Modal/New.jsx +++ b/src/components/EditorHeader/Modal/New.jsx @@ -2,9 +2,11 @@ import { db } from "../../../data/db"; import { useSettings } from "../../../hooks"; import { useLiveQuery } from "dexie-react-hooks"; import Thumbnail from "../../Thumbnail"; +import { useTranslation } from "react-i18next"; export default function New({ selectedTemplateId, setSelectedTemplateId }) { const { settings } = useSettings(); + const { t } = useTranslation(); const templates = useLiveQuery(() => db.templates.toArray()); return ( @@ -17,7 +19,7 @@ export default function New({ selectedTemplateId, setSelectedTemplateId }) { > -
Blank
+
{t("blank")}
{templates?.map((temp, i) => (
setSelectedTemplateId(temp.id)}> diff --git a/src/components/EditorHeader/Modal/Open.jsx b/src/components/EditorHeader/Modal/Open.jsx index e138b92..cf7a526 100644 --- a/src/components/EditorHeader/Modal/Open.jsx +++ b/src/components/EditorHeader/Modal/Open.jsx @@ -1,9 +1,11 @@ import { db } from "../../../data/db"; import { Banner } from "@douyinfe/semi-ui"; import { useLiveQuery } from "dexie-react-hooks"; +import { useTranslation } from "react-i18next"; export default function Open({ selectedDiagramId, setSelectedDiagramId }) { const diagrams = useLiveQuery(() => db.diagrams.toArray()); + const { t } = useTranslation(); const getDiagramSize = (d) => { const size = JSON.stringify(d).length; @@ -32,9 +34,9 @@ export default function Open({ selectedDiagramId, setSelectedDiagramId }) { - - - + + + diff --git a/src/components/EditorHeader/Modal/Rename.jsx b/src/components/EditorHeader/Modal/Rename.jsx index 9042859..d876746 100644 --- a/src/components/EditorHeader/Modal/Rename.jsx +++ b/src/components/EditorHeader/Modal/Rename.jsx @@ -1,9 +1,12 @@ import { Input } from "@douyinfe/semi-ui"; +import { useTranslation } from "react-i18next"; export default function Rename({ title, setTitle }) { + const { t } = useTranslation(); + return ( setTitle(v)} /> diff --git a/src/components/EditorHeader/SideSheet/Sidesheet.jsx b/src/components/EditorHeader/SideSheet/Sidesheet.jsx index 0dce650..c01c642 100644 --- a/src/components/EditorHeader/SideSheet/Sidesheet.jsx +++ b/src/components/EditorHeader/SideSheet/Sidesheet.jsx @@ -6,8 +6,10 @@ import timeLineDark from "../../../assets/process_dark.png"; import todo from "../../../assets/calendar.png"; import Timeline from "./Timeline"; import Todo from "./Todo"; +import { useTranslation } from "react-i18next"; export default function Sidesheet({ type, onClose }) { + const { t } = useTranslation(); const { settings } = useSettings(); function getTitle(type) { @@ -20,14 +22,14 @@ export default function Sidesheet({ type, onClose }) { className="w-7" alt="chat icon" /> -
Timeline
+
{t("timeline")}
); case SIDESHEET.TODO: return (
todo icon -
To-do list
+
{t("to_do")}
); default: diff --git a/src/components/EditorHeader/SideSheet/Timeline.jsx b/src/components/EditorHeader/SideSheet/Timeline.jsx index 4f46757..bd962ec 100644 --- a/src/components/EditorHeader/SideSheet/Timeline.jsx +++ b/src/components/EditorHeader/SideSheet/Timeline.jsx @@ -1,8 +1,10 @@ +import { useTranslation } from "react-i18next"; import { useUndoRedo } from "../../../hooks"; import { List } from "@douyinfe/semi-ui"; export default function Timeline() { const { undoStack } = useUndoRedo(); + const { t } = useTranslation(); if (undoStack.length > 0) { return ( @@ -22,11 +24,6 @@ export default function Timeline() { ); } else { - return ( -
- No activity was recorded. You have not added anything to your diagram - yet. -
- ); + return
{t("no_activity")}
; } } diff --git a/src/components/EditorHeader/SideSheet/Todo.jsx b/src/components/EditorHeader/SideSheet/Todo.jsx index 0aea674..fd53196 100644 --- a/src/components/EditorHeader/SideSheet/Todo.jsx +++ b/src/components/EditorHeader/SideSheet/Todo.jsx @@ -21,6 +21,7 @@ import { } from "@douyinfe/semi-icons"; import { State } from "../../../data/constants"; import { useTasks, useSaveState } from "../../../hooks"; +import { useTranslation } from "react-i18next"; const Priority = { NONE: 0, @@ -30,10 +31,10 @@ const Priority = { }; const SortOrder = { - ORIGINAL: "My order", - PRIORITY: "Priority", - COMPLETED: "Completed", - ALPHABETICALLY: "Alphabetically", + ORIGINAL: "my_order", + PRIORITY: "priority", + COMPLETED: "completed", + ALPHABETICALLY: "alphabetically", }; export default function Todo() { @@ -41,17 +42,18 @@ export default function Todo() { const [, setSortOrder] = useState(SortOrder.ORIGINAL); const { tasks, setTasks, updateTask } = useTasks(); const { setSaveState } = useSaveState(); + const { t } = useTranslation(); const priorityLabel = (p) => { switch (p) { case Priority.NONE: - return "None"; + return t("none"); case Priority.LOW: - return "Low"; + return t("low"); case Priority.MEDIUM: - return "Medium"; + return t("medium"); case Priority.HIGH: - return "High"; + return t("high"); default: return ""; } @@ -91,7 +93,7 @@ export default function Todo() { } else { return 0; } - }) + }), ); break; case SortOrder.ALPHABETICALLY: @@ -116,7 +118,7 @@ export default function Todo() { sort(order); }} > - {order} + {t(order)} ))} @@ -128,7 +130,7 @@ export default function Todo() { theme="borderless" type="tertiary" > - Sort by + {t("sort_by")} {tasks.length > 0 ? ( - {tasks.map((t, i) => ( + {tasks.map((task, i) => ( { updateTask(i, { complete: e.target.checked }); setSaveState(State.SAVING); @@ -172,25 +174,25 @@ export default function Todo() { updateTask(i, { title: v })} - value={t.title} + value={task.title} onBlur={() => setSaveState(State.SAVING)} - > + />
- Set priority: + {t("priority")}:
{ updateTask(i, { priority: e.target.value }); setSaveState(State.SAVING); }} - value={t.priority} + value={task.priority} direction="vertical" > @@ -221,12 +223,12 @@ export default function Todo() { style={{ marginTop: "12px" }} onClick={() => { setTasks((prev) => - prev.filter((task, j) => i !== j) + prev.filter((_, j) => i !== j), ); setSaveState(State.SAVING); }} > - Delete + {t("delete")} } @@ -243,7 +245,7 @@ export default function Todo() {
NameLast ModifiedSize{t("name")}{t("last_modified")}{t("size")}