add en translations

This commit is contained in:
Guy Ben-Aharon
2024-09-02 13:18:19 +03:00
committed by Guy Ben-Aharon
parent 89e0cddd42
commit cf3a10961d
22 changed files with 576 additions and 139 deletions

65
package-lock.json generated
View File

@@ -38,12 +38,14 @@
"dexie": "^4.0.8",
"eslint-config-airbnb-typescript": "^18.0.0",
"html-to-image": "^1.11.11",
"i18next": "^23.14.0",
"lucide-react": "^0.424.0",
"nanoid": "^5.0.7",
"react": "^18.3.1",
"react-code-blocks": "^0.1.6",
"react-dom": "^18.3.1",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22",
"react-responsive": "^10.0.0",
"react-router-dom": "^6.26.0",
@@ -6329,6 +6331,15 @@
"node": "*"
}
},
"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==",
"license": "MIT",
"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",
@@ -6357,6 +6368,29 @@
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
"license": "BSD-3-Clause"
},
"node_modules/i18next": {
"version": "23.14.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.14.0.tgz",
"integrity": "sha512-Y5GL4OdA8IU2geRrt2+Uc1iIhsjICdHZzT9tNwQ3TVqdNzgxHToGCKf/TPRP80vTCAP6svg2WbbJL+Gx5MFQVA==",
"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"
}
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@@ -8030,6 +8064,28 @@
"react-dom": ">=16.8.1"
}
},
"node_modules/react-i18next": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.1.tgz",
"integrity": "sha512-NwxLqNM6CLbeGA9xPsjits0EnXdKgCRSS6cgkgOdNcPXqL+1fYNl8fBg1wmnnHvFy812Bt4IWTPE9zjoPmFj3w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.8",
"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",
@@ -9683,6 +9739,15 @@
}
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vue": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz",

View File

@@ -42,12 +42,14 @@
"dexie": "^4.0.8",
"eslint-config-airbnb-typescript": "^18.0.0",
"html-to-image": "^1.11.11",
"i18next": "^23.14.0",
"lucide-react": "^0.424.0",
"nanoid": "^5.0.7",
"react": "^18.3.1",
"react-code-blocks": "^0.1.6",
"react-dom": "^18.3.1",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22",
"react-responsive": "^10.0.0",
"react-router-dom": "^6.26.0",

View File

@@ -68,9 +68,11 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
const config = await getConfig();
if (!config) {
const diagrams = await db.diagrams.toArray();
await db.config.add({
id: 1,
defaultDiagramId: '',
defaultDiagramId: diagrams?.[0]?.id ?? '',
});
}
});

View File

@@ -25,6 +25,7 @@ import {
} from '@/components/avatar/avatar';
import { CreateDiagramDialogStep } from '../create-diagram-dialog-step';
import { SSMSInfo } from './ssms-info/ssms-info';
import { useTranslation } from 'react-i18next';
export interface CreateDiagramDialogImportDatabaseProps {
setStep: React.Dispatch<React.SetStateAction<CreateDiagramDialogStep>>;
@@ -51,6 +52,7 @@ export const CreateDiagramDialogImportDatabase: React.FC<
setDatabaseEdition,
errorMessage,
}) => {
const { t } = useTranslation();
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const inputValue = e.target.value;
@@ -62,10 +64,12 @@ export const CreateDiagramDialogImportDatabase: React.FC<
const renderHeader = useCallback(() => {
return (
<DialogHeader>
<DialogTitle>Import your Database</DialogTitle>
<DialogTitle>
{t('new_diagram_dialog.import_database.title')}
</DialogTitle>
</DialogHeader>
);
}, []);
}, [t]);
const renderContent = useCallback(() => {
return (
@@ -73,7 +77,9 @@ export const CreateDiagramDialogImportDatabase: React.FC<
{databaseTypeToEditionMap[databaseType].length > 0 ? (
<div className="flex flex-col gap-1 md:flex-row">
<p className="text-sm leading-6 text-muted-foreground">
Database edition:
{t(
'new_diagram_dialog.import_database.database_edition'
)}
</p>
<ToggleGroup
type="single"
@@ -145,7 +151,9 @@ export const CreateDiagramDialogImportDatabase: React.FC<
) : null}
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:justify-between">
<div>1. Run this script in your database:</div>
<div>
1. {t('new_diagram_dialog.import_database.step_1')}
</div>
{databaseType === DatabaseType.SQL_SERVER && (
<SSMSInfo />
)}
@@ -159,11 +167,13 @@ export const CreateDiagramDialogImportDatabase: React.FC<
</div>
<div className="flex h-48 flex-col gap-1">
<p className="text-sm text-muted-foreground">
2. Paste the script result here:
2. {t('new_diagram_dialog.import_database.step_2')}
</p>
<Textarea
className="w-full flex-1 rounded-md bg-muted p-2 text-sm"
placeholder="Script result here..."
placeholder={t(
'new_diagram_dialog.import_database.script_results_placeholder'
)}
value={scriptResult}
onChange={handleInputChange}
/>
@@ -182,6 +192,7 @@ export const CreateDiagramDialogImportDatabase: React.FC<
handleInputChange,
scriptResult,
setDatabaseEdition,
t,
]);
const renderFooter = useCallback(() => {
@@ -195,7 +206,7 @@ export const CreateDiagramDialogImportDatabase: React.FC<
setStep(CreateDiagramDialogStep.SELECT_DATABASE)
}
>
Back
{t('new_diagram_dialog.back')}
</Button>
</div>
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end sm:space-x-2">
@@ -205,7 +216,7 @@ export const CreateDiagramDialogImportDatabase: React.FC<
variant="outline"
onClick={createNewDiagram}
>
Empty diagram
{t('new_diagram_dialog.empty_diagram')}
</Button>
</DialogClose>
<DialogClose asChild>
@@ -218,13 +229,13 @@ export const CreateDiagramDialogImportDatabase: React.FC<
}
onClick={createNewDiagram}
>
Import
{t('new_diagram_dialog.import')}
</Button>
</DialogClose>
</div>
</DialogFooter>
);
}, [createNewDiagram, errorMessage.length, scriptResult, setStep]);
}, [createNewDiagram, errorMessage.length, scriptResult, setStep, t]);
return (
<>

View File

@@ -8,6 +8,7 @@ import { Info } from 'lucide-react';
import React from 'react';
import SSMSInstructions from '@/assets/ssms-instructions.png';
import { ZoomableImage } from '@/components/zoomable-image/zoomable-image';
import { useTranslation } from 'react-i18next';
export interface SSMSInfoProps {}
@@ -16,6 +17,7 @@ export const SSMSInfo = React.forwardRef<
SSMSInfoProps
>((props, ref) => {
const [open, setOpen] = React.useState(false);
const { t } = useTranslation();
return (
<HoverCard
open={open}
@@ -31,22 +33,32 @@ export const SSMSInfo = React.forwardRef<
}}
>
<Info size={14} />
<Label className="text-xs">SSMS Instructions</Label>
<Label className="text-xs">
{t(
'new_diagram_dialog.import_database.ssms_instructions.button_text'
)}
</Label>
</div>
</HoverCardTrigger>
<HoverCardContent className="w-80">
<div className="flex">
<div className="space-y-1">
<h4 className="text-sm font-semibold">Instructions</h4>
<h4 className="text-sm font-semibold">
{t(
'new_diagram_dialog.import_database.ssms_instructions.title'
)}
</h4>
<p className="text-xs text-muted-foreground">
<span className="font-semibold">1. </span>
{
'Go to Tools > Options > Query Results > SQL Server.'
}
{t(
'new_diagram_dialog.import_database.ssms_instructions.step_1'
)}
</p>
<p className="text-xs text-muted-foreground">
<span className="font-semibold">2. </span>
{`If you're using "Results to Grid," change the Maximum Characters Retrieved for Non-XML data (set to 9999999).`}
{t(
'new_diagram_dialog.import_database.ssms_instructions.step_2'
)}
</p>
<div className="flex items-center pt-2">
<ZoomableImage src={SSMSInstructions} />

View File

@@ -14,6 +14,7 @@ import { databaseLogoMap } from '@/lib/databases';
import { Link } from '@/components/link/link';
import { LayoutGrid } from 'lucide-react';
import { CreateDiagramDialogStep } from '../create-diagram-dialog-step';
import { useTranslation } from 'react-i18next';
export interface CreateDiagramDialogSelectDatabaseProps {
setStep: React.Dispatch<React.SetStateAction<CreateDiagramDialogStep>>;
@@ -32,6 +33,7 @@ export const CreateDiagramDialogSelectDatabase: React.FC<
hasExistingDiagram,
createNewDiagram,
}) => {
const { t } = useTranslation();
const renderDatabaseOption = useCallback((type: DatabaseType) => {
const logo = databaseLogoMap[type];
return (
@@ -54,28 +56,34 @@ export const CreateDiagramDialogSelectDatabase: React.FC<
</div>
<div className="flex flex-col-reverse">
<div className="hidden text-sm text-primary md:flex">
Check Examples
{t(
'new_diagram_dialog.database_selection.check_examples_long'
)}
</div>
<div className="flex text-xs text-primary md:hidden">
Examples
{t(
'new_diagram_dialog.database_selection.check_examples_short'
)}
</div>
</div>
</div>
</Link>
),
[]
[t]
);
const renderHeader = useCallback(() => {
return (
<DialogHeader>
<DialogTitle>What is your Database?</DialogTitle>
<DialogTitle>
{t('new_diagram_dialog.database_selection.title')}
</DialogTitle>
<DialogDescription>
Each database has its own unique features and capabilities.
{t('new_diagram_dialog.database_selection.description')}
</DialogDescription>
</DialogHeader>
);
}, []);
}, [t]);
const renderContent = useCallback(() => {
return (
@@ -116,7 +124,7 @@ export const CreateDiagramDialogSelectDatabase: React.FC<
{hasExistingDiagram ? (
<DialogClose asChild>
<Button type="button" variant="secondary">
Cancel
{t('new_diagram_dialog.cancel')}
</Button>
</DialogClose>
) : (
@@ -128,7 +136,7 @@ export const CreateDiagramDialogSelectDatabase: React.FC<
variant="outline"
onClick={createNewDiagram}
>
Empty diagram
{t('new_diagram_dialog.empty_diagram')}
</Button>
<Button
type="button"
@@ -138,12 +146,12 @@ export const CreateDiagramDialogSelectDatabase: React.FC<
setStep(CreateDiagramDialogStep.IMPORT_DATABASE)
}
>
Continue
{t('new_diagram_dialog.continue')}
</Button>
</div>
</DialogFooter>
);
}, [createNewDiagram, databaseType, hasExistingDiagram, setStep]);
}, [createNewDiagram, databaseType, hasExistingDiagram, setStep, t]);
return (
<>

View File

@@ -22,6 +22,7 @@ import { DatabaseType } from '@/lib/domain/database-type';
import { DialogProps } from '@radix-ui/react-dialog';
import { Annoyed, Sparkles } from 'lucide-react';
import React, { useCallback, useEffect } from 'react';
import { Trans, useTranslation } from 'react-i18next';
export interface ExportSQLDialogProps {
dialog: DialogProps;
@@ -34,6 +35,7 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
}) => {
const { closeExportSQLDialog } = useDialog();
const { currentDiagram } = useChartDB();
const { t } = useTranslation();
const [script, setScript] = React.useState<string>();
const [error, setError] = React.useState<boolean>(false);
@@ -66,26 +68,32 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
<div className="flex flex-col items-center justify-center gap-1 text-sm">
<Annoyed className="size-10" />
<Label className="text-sm">
Error generating SQL script. Please try again later or{' '}
<a
href="mailto:chartdb.io@gmail.com"
target="_blank"
className="text-pink-600 hover:underline"
rel="noreferrer"
>
contact us.
</a>
<Trans
i18nKey="export_sql_dialog.error.message" // optional -> fallbacks to defaults if not provided
components={[
<a
key={0}
href="mailto:chartdb.io@gmail.com"
target="_blank"
className="text-pink-600 hover:underline"
rel="noreferrer"
/>,
]}
/>
</Label>
<div>
Feel free to use your OPENAI_TOKEN, see the manual{' '}
<a
href="https://github.com/chartdb/chartdb"
target="_blank"
rel="noreferrer"
className="text-pink-600 hover:underline"
>
here.
</a>
<Trans
i18nKey="export_sql_dialog.error.description" // optional -> fallbacks to defaults if not provided
components={[
<a
key={0}
href="https://github.com/chartdb/chartdb"
target="_blank"
rel="noreferrer"
className="text-pink-600 hover:underline"
/>,
]}
/>
</div>
</div>
</div>
@@ -100,18 +108,20 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
<div className="flex items-center justify-center gap-1">
<Sparkles className="h-5" />
<Label className="text-lg">
AI is generating SQL for{' '}
{databaseTypeToLabelMap[targetDatabaseType]}...
{t('export_sql_dialog.loading.text', {
databaseType:
databaseTypeToLabelMap[targetDatabaseType],
})}
</Label>
</div>
<div className="flex items-center justify-center gap-1">
<Label className="text-sm">
This should take up to 30 seconds.
{t('export_sql_dialog.loading.description')}
</Label>
</div>
</div>
),
[targetDatabaseType]
[targetDatabaseType, t]
);
return (
<Dialog
@@ -127,13 +137,16 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
showClose
>
<DialogHeader>
<DialogTitle>Export SQL</DialogTitle>
<DialogTitle>{t('export_sql_dialog.title')}</DialogTitle>
<DialogDescription>
{`Export your diagram schema to ${
targetDatabaseType === DatabaseType.GENERIC
? 'SQL'
: databaseTypeToLabelMap[targetDatabaseType]
} script`}
{t('export_sql_dialog.description', {
databaseType:
targetDatabaseType === DatabaseType.GENERIC
? 'SQL'
: databaseTypeToLabelMap[
targetDatabaseType
],
})}
</DialogDescription>
</DialogHeader>
<div className="flex flex-1 items-center justify-center">
@@ -154,7 +167,9 @@ export const ExportSQLDialog: React.FC<ExportSQLDialogProps> = ({
<DialogFooter className="flex !justify-between gap-2">
<div />
<DialogClose asChild>
<Button type="button">Close</Button>
<Button type="button">
{t('export_sql_dialog.close')}
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>

View File

@@ -24,6 +24,7 @@ import { useStorage } from '@/hooks/use-storage';
import { Diagram } from '@/lib/domain/diagram';
import { DialogProps } from '@radix-ui/react-dialog';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
export interface OpenDiagramDialogProps {
@@ -34,6 +35,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
dialog,
}) => {
const { closeOpenDiagramDialog } = useDialog();
const { t } = useTranslation();
const { updateConfig } = useConfig();
const navigate = useNavigate();
const { listDiagrams } = useStorage();
@@ -78,9 +80,9 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
showClose
>
<DialogHeader>
<DialogTitle>Open Diagram</DialogTitle>
<DialogTitle>{t('open_diagram_dialog.title')}</DialogTitle>
<DialogDescription>
Select a diagram to open from the list below.
{t('open_diagram_dialog.description')}
</DialogDescription>
</DialogHeader>
<div className="flex flex-1 items-center justify-center">
@@ -89,13 +91,25 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
<TableHeader className="sticky top-0 bg-background">
<TableRow>
<TableHead />
<TableHead>Name</TableHead>
<TableHead className="hidden items-center sm:inline-flex">
Created at
<TableHead>
{t(
'open_diagram_dialog.table_columns.name'
)}
</TableHead>
<TableHead className="hidden items-center sm:inline-flex">
{t(
'open_diagram_dialog.table_columns.created_at'
)}
</TableHead>
<TableHead>
{t(
'open_diagram_dialog.table_columns.last_modified'
)}
</TableHead>
<TableHead>Last modified</TableHead>
<TableHead className="text-center">
Tables
{t(
'open_diagram_dialog.table_columns.tables_count'
)}
</TableHead>
</TableRow>
</TableHeader>
@@ -145,7 +159,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
<DialogFooter className="flex !justify-between gap-2">
<DialogClose asChild>
<Button type="button" variant="secondary">
Cancel
{t('open_diagram_dialog.cancel')}
</Button>
</DialogClose>
<DialogClose asChild>
@@ -154,7 +168,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
disabled={!selectedDiagramId}
onClick={() => openDiagram(selectedDiagramId ?? '')}
>
Open
{t('open_diagram_dialog.open')}
</Button>
</DialogClose>
</DialogFooter>

19
src/i18n/i18n.ts Normal file
View File

@@ -0,0 +1,19 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { en } from './locales/en';
const resources = {
en,
};
i18n.use(initReactI18next).init({
resources,
lng: 'en',
interpolation: {
escapeValue: false,
},
fallbackLng: 'en',
debug: false,
});
export { i18n };

182
src/i18n/locales/en.ts Normal file
View File

@@ -0,0 +1,182 @@
import { ResourceLanguage } from 'i18next';
export const en: ResourceLanguage = {
translation: {
menu: {
file: {
file: 'File',
new: 'New',
open: 'Open',
export_sql: 'Export SQL',
export_as: 'Export as',
delete_diagram: 'Delete Diagram',
exit: 'Exit',
},
edit: {
edit: 'Edit',
undo: 'Undo',
redo: 'Redo',
clear: 'Clear',
},
help: {
help: 'Help',
visit_website: 'Visit ChartDB',
join_discord: 'Join us on Discord',
},
},
delete_diagram_alert: {
title: 'Delete Diagram',
description:
'This action cannot be undone. This will permanently delete the diagram.',
cancel: 'Cancel',
delete: 'Delete',
},
clear_diagram_alert: {
title: 'Clear Diagram',
description:
'This action cannot be undone. This will permanently delete all the data in the diagram.',
cancel: 'Cancel',
clear: 'Clear',
},
last_saved: 'Last saved',
saved: 'Saved',
diagrams: 'Diagrams',
side_panel: {
tables_section: {
tables: 'Tables',
add_table: 'Add Table',
filter: 'Filter',
table: {
fields: 'Fields',
nullable: 'Nullable?',
primary_key: 'Primary Key',
indexes: 'Indexes',
add_field: 'Add Field',
add_index: 'Add Index',
index_select_fields: 'Select fields',
field_name: 'Name',
field_type: 'Type',
field_actions: {
title: 'Field Attributes',
unique: 'Unique',
delete_field: 'Delete Field',
},
index_actions: {
title: 'Index Attributes',
name: 'Name',
unique: 'Unique',
delete_index: 'Delete Index',
},
table_actions: {
title: 'Table Actions',
add_field: 'Add Field',
add_index: 'Add Index',
delete_table: 'Delete Table',
},
},
empty_state: {
title: 'No tables',
description: 'Create a table to get started',
},
},
relationships_section: {
relationships: 'Relationships',
filter: 'Filter',
relationship: {
primary: 'Primary',
foreign: 'Foreign',
cardinality: 'Cardinality',
delete_relationship: 'Delete',
relationship_actions: {
title: 'Actions',
delete_relationship: 'Delete',
},
},
empty_state: {
title: 'No relationships',
description: 'Create a relationship to connect tables',
},
},
},
toolbar: {
zoom_in: 'Zoom In',
zoom_out: 'Zoom Out',
save: 'Save',
show_all: 'Show All',
undo: 'Undo',
redo: 'Redo',
},
new_diagram_dialog: {
database_selection: {
title: 'What is your Database?',
description:
'Each database has its own unique features and capabilities.',
check_examples_long: 'Check Examples',
check_examples_short: 'Examples',
},
import_database: {
title: 'Import your Database',
database_edition: 'Database Edition:',
step_1: 'Run this script in your database:',
step_2: 'Paste the script result here:',
script_results_placeholder: 'Script results here...',
ssms_instructions: {
button_text: 'SSMS Instructions',
title: 'Instructions',
step_1: 'Go to Tools > Options > Query Results > SQL Server.',
step_2: 'If you\'re using "Results to Grid," change the Maximum Characters Retrieved for Non-XML data (set to 9999999).',
},
},
cancel: 'Cancel',
back: 'Back',
empty_diagram: 'Empty diagram',
continue: 'Continue',
import: 'Import',
},
open_diagram_dialog: {
title: 'Open Diagram',
description: 'Select a diagram to open from the list below.',
table_columns: {
name: 'Name',
created_at: 'Created at',
last_modified: 'Last modified',
tables_count: 'Tables',
},
cancel: 'Cancel',
open: 'Open',
},
export_sql_dialog: {
title: 'Export SQL',
description:
'Export your diagram schema to {{databaseType}} script',
close: 'Close',
loading: {
text: 'AI is generating SQL for {{databaseType}}...',
description: 'This should take up to 30 seconds.',
},
error: {
message:
'Error generating SQL script. Please try again later or <0>contact us</0>.',
description:
'Feel free to use your OPENAI_TOKEN, see the manual <0>here</0>.',
},
},
relationship_type: {
one_to_one: 'One to One',
one_to_many: 'One to Many',
many_to_one: 'Many to One',
},
},
};

View File

@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client';
import './index.css';
import './globals.css';
import { App } from './app';
import './i18n/i18n';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>

View File

@@ -11,6 +11,7 @@ import {
TooltipTrigger,
TooltipContent,
} from '@/components/tooltip/tooltip';
import { useTranslation } from 'react-i18next';
const convertToPercentage = (value: number) => `${Math.round(value * 100)}%`;
@@ -18,6 +19,7 @@ export interface ToolbarProps {}
export const Toolbar: React.FC<ToolbarProps> = () => {
const { updateDiagramUpdatedAt } = useChartDB();
const { t } = useTranslation();
const { redo, undo, hasRedo, hasUndo } = useHistory();
const { getZoom, zoomIn, zoomOut, fitView } = useReactFlow();
const [zoom, setZoom] = useState<string>(convertToPercentage(getZoom()));
@@ -60,7 +62,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
</ToolbarButton>
</span>
</TooltipTrigger>
<TooltipContent>Save</TooltipContent>
<TooltipContent>{t('toolbar.save')}</TooltipContent>
</Tooltip>
<Separator orientation="vertical" />
<Tooltip>
@@ -71,7 +73,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
</ToolbarButton>
</span>
</TooltipTrigger>
<TooltipContent>Show All</TooltipContent>
<TooltipContent>{t('toolbar.show_all')}</TooltipContent>
</Tooltip>
<Separator orientation="vertical" />
<Tooltip>
@@ -82,7 +84,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
</ToolbarButton>
</span>
</TooltipTrigger>
<TooltipContent>Zoom Out</TooltipContent>
<TooltipContent>{t('toolbar.zoom_out')}</TooltipContent>
</Tooltip>
<ToolbarButton onClick={resetZoom}>{zoom}</ToolbarButton>
<Tooltip>
@@ -93,7 +95,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
</ToolbarButton>
</span>
</TooltipTrigger>
<TooltipContent>Zoom In</TooltipContent>
<TooltipContent>{t('toolbar.zoom_in')}</TooltipContent>
</Tooltip>
<Separator orientation="vertical" />
<Tooltip>
@@ -107,7 +109,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
</ToolbarButton>
</span>
</TooltipTrigger>
<TooltipContent>Undo</TooltipContent>
<TooltipContent>{t('toolbar.undo')}</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
@@ -120,7 +122,7 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
</ToolbarButton>
</span>
</TooltipTrigger>
<TooltipContent>Redo</TooltipContent>
<TooltipContent>{t('toolbar.redo')}</TooltipContent>
</Tooltip>
</CardContent>
</Card>

View File

@@ -17,6 +17,7 @@ import { DBRelationship, RelationshipType } from '@/lib/domain/db-relationship';
import { useReactFlow } from '@xyflow/react';
import { FileMinus2, FileOutput, Trash2 } from 'lucide-react';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export interface RelationshipListItemContentProps {
relationship: DBRelationship;
@@ -28,6 +29,7 @@ export const RelationshipListItemContent: React.FC<
const { getTable, getField, updateRelationship, removeRelationship } =
useChartDB();
const { deleteElements } = useReactFlow();
const { t } = useTranslation();
const targetTable = getTable(relationship.targetTableId);
const targetField = getField(
@@ -56,7 +58,9 @@ export const RelationshipListItemContent: React.FC<
<div className="flex flex-row items-center gap-1">
<FileMinus2 className="size-4 text-slate-700" />
<div className="font-bold text-slate-700">
Primary
{t(
'side_panel.relationships_section.relationship.primary'
)}
</div>
</div>
<Tooltip>
@@ -74,7 +78,9 @@ export const RelationshipListItemContent: React.FC<
<div className="flex flex-row items-center gap-1">
<FileOutput className="size-4 text-slate-700" />
<div className="font-bold text-slate-700">
Foreign
{t(
'side_panel.relationships_section.relationship.foreign'
)}
</div>
</div>
<Tooltip>
@@ -93,7 +99,9 @@ export const RelationshipListItemContent: React.FC<
<div className="flex flex-row items-center gap-1">
<FileOutput className="size-4 text-slate-700" />
<div className="font-bold text-slate-700">
Cardinality
{t(
'side_panel.relationships_section.relationship.cardinality'
)}
</div>
</div>
@@ -109,13 +117,13 @@ export const RelationshipListItemContent: React.FC<
<SelectContent>
<SelectGroup>
<SelectItem value="one_to_one">
One to One
{t('relationship_type.one_to_one')}
</SelectItem>
<SelectItem value="one_to_many">
One to Many
{t('relationship_type.one_to_many')}
</SelectItem>
<SelectItem value="many_to_one">
Many to One
{t('relationship_type.many_to_one')}
</SelectItem>
</SelectGroup>
</SelectContent>
@@ -129,7 +137,11 @@ export const RelationshipListItemContent: React.FC<
onClick={deleteRelationshipHandler}
>
<Trash2 className="mr-1 size-3.5 text-red-700" />
<div className="text-red-700">Delete</div>
<div className="text-red-700">
{t(
'side_panel.relationships_section.relationship.delete_relationship'
)}
</div>
</Button>
</div>
</div>

View File

@@ -23,6 +23,7 @@ import {
import { Input } from '@/components/input/input';
import { useLayout } from '@/hooks/use-layout';
import { useBreakpoint } from '@/hooks/use-breakpoint';
import { useTranslation } from 'react-i18next';
export interface RelationshipListItemHeaderProps {
relationship: DBRelationship;
@@ -33,6 +34,7 @@ export const RelationshipListItemHeader: React.FC<
> = ({ relationship }) => {
const { updateRelationship, removeRelationship } = useChartDB();
const { fitView, deleteElements, setEdges } = useReactFlow();
const { t } = useTranslation();
const { hideSidePanel } = useLayout();
const [editMode, setEditMode] = React.useState(false);
const { isMd: isDesktop } = useBreakpoint('md');
@@ -125,21 +127,27 @@ export const RelationshipListItemHeader: React.FC<
</ListItemHeaderButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-40">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuLabel>
{t(
'side_panel.relationships_section.relationship.relationship_actions.title'
)}
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
onClick={deleteRelationshipHandler}
className="flex justify-between !text-red-700"
>
Delete
{t(
'side_panel.relationships_section.relationship.relationship_actions.delete_relationship'
)}
<Trash2 className="size-3.5 text-red-700" />
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
),
[deleteRelationshipHandler]
[deleteRelationshipHandler, t]
);
return (

View File

@@ -8,6 +8,7 @@ import { DBRelationship } from '@/lib/domain/db-relationship';
import { useLayout } from '@/hooks/use-layout';
import { EmptyState } from '@/components/empty-state/empty-state';
import { ScrollArea } from '@/components/scroll-area/scroll-area';
import { useTranslation } from 'react-i18next';
export interface RelationshipsSectionProps {}
@@ -15,6 +16,7 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
const { relationships } = useChartDB();
const [filterText, setFilterText] = React.useState('');
const { closeAllRelationshipsInSidebar } = useLayout();
const { t } = useTranslation();
const filteredRelationships = useMemo(() => {
const filter: (relationship: DBRelationship) => boolean = (
@@ -41,7 +43,9 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
<div className="flex-1">
<Input
type="text"
placeholder="Filter"
placeholder={t(
'side_panel.relationships_section.filter'
)}
className="h-8 w-full focus-visible:ring-0"
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
@@ -52,8 +56,12 @@ export const RelationshipsSection: React.FC<RelationshipsSectionProps> = () => {
<ScrollArea className="h-full">
{relationships.length === 0 ? (
<EmptyState
title="No relationships"
description="Create a relationship to connect tables"
title={t(
'side_panel.relationships_section.empty_state.title'
)}
description={t(
'side_panel.relationships_section.empty_state.description'
)}
className="mt-20"
/>
) : (

View File

@@ -11,11 +11,12 @@ import { TablesSection } from './tables-section/tables-section';
import { RelationshipsSection } from './relationships-section/relationships-section';
import { useLayout } from '@/hooks/use-layout';
import { SidebarSection } from '@/context/layout-context/layout-context';
import { useTranslation } from 'react-i18next';
export interface SidePanelProps {}
export const SidePanel: React.FC<SidePanelProps> = () => {
// const [selected, setSelected] = React.useState('tables');
const { t } = useTranslation();
const { selectSidebarSection, selectedSidebarSection } = useLayout();
return (
<aside className="flex h-full flex-col overflow-hidden">
@@ -31,9 +32,13 @@ export const SidePanel: React.FC<SidePanelProps> = () => {
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="tables">Tables</SelectItem>
<SelectItem value="tables">
{t('side_panel.tables_section.tables')}
</SelectItem>
<SelectItem value="relationships">
Relationships
{t(
'side_panel.relationships_section.relationships'
)}
</SelectItem>
</SelectGroup>
</SelectContent>

View File

@@ -22,6 +22,7 @@ import {
} from '@/components/popover/popover';
import { Label } from '@/components/label/label';
import { Checkbox } from '@/components/checkbox/checkbox';
import { useTranslation } from 'react-i18next';
export interface TableFieldProps {
field: DBField;
@@ -35,6 +36,7 @@ export const TableField: React.FC<TableFieldProps> = ({
removeField,
}) => {
const { databaseType } = useChartDB();
const { t } = useTranslation();
const dataFieldOptions = dataTypeMap[databaseType].map((type) => ({
label: type.name,
@@ -50,7 +52,9 @@ export const TableField: React.FC<TableFieldProps> = ({
<Input
className="h-8 w-full !truncate focus-visible:ring-0"
type="text"
placeholder="Name"
placeholder={t(
'side_panel.tables_section.table.field_name'
)}
value={field.name}
onChange={(e) =>
updateField({
@@ -69,7 +73,9 @@ export const TableField: React.FC<TableFieldProps> = ({
className="flex h-8 w-full"
mode="single"
options={dataFieldOptions}
placeholder="Type"
placeholder={t(
'side_panel.tables_section.table.field_type'
)}
selected={field.type.id}
onChange={(value) =>
updateField({
@@ -103,7 +109,9 @@ export const TableField: React.FC<TableFieldProps> = ({
</Toggle>
</span>
</TooltipTrigger>
<TooltipContent>Nullable?</TooltipContent>
<TooltipContent>
{t('side_panel.tables_section.table.nullable')}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
@@ -123,7 +131,9 @@ export const TableField: React.FC<TableFieldProps> = ({
</Toggle>
</span>
</TooltipTrigger>
<TooltipContent>Primary Key</TooltipContent>
<TooltipContent>
{t('side_panel.tables_section.table.primary_key')}
</TooltipContent>
</Tooltip>
<Popover>
<PopoverTrigger asChild>
@@ -137,7 +147,9 @@ export const TableField: React.FC<TableFieldProps> = ({
<PopoverContent className="w-52">
<div className="flex flex-col gap-2">
<div className="text-sm font-semibold">
Field Attributes
{t(
'side_panel.tables_section.table.field_actions.title'
)}
</div>
<Separator orientation="horizontal" />
<div className="flex items-center justify-between">
@@ -145,7 +157,9 @@ export const TableField: React.FC<TableFieldProps> = ({
htmlFor="width"
className="text-gray-700"
>
Unique
{t(
'side_panel.tables_section.table.field_actions.unique'
)}
</Label>
<Checkbox
checked={field.unique}
@@ -164,7 +178,9 @@ export const TableField: React.FC<TableFieldProps> = ({
onClick={removeField}
>
<Trash2 className="size-3.5 text-red-700" />
Delete field
{t(
'side_panel.tables_section.table.field_actions.delete_field'
)}
</Button>
</div>
</PopoverContent>

View File

@@ -13,6 +13,7 @@ import { Separator } from '@/components/separator/separator';
import { Checkbox } from '@/components/checkbox/checkbox';
import { Label } from '@/components/label/label';
import { Input } from '@/components/input/input';
import { useTranslation } from 'react-i18next';
export interface TableIndexProps {
index: DBIndex;
@@ -27,6 +28,7 @@ export const TableIndex: React.FC<TableIndexProps> = ({
updateIndex,
removeIndex,
}) => {
const { t } = useTranslation();
const fieldOptions = fields.map((field) => ({
label: field.name,
value: field.id,
@@ -42,7 +44,9 @@ export const TableIndex: React.FC<TableIndexProps> = ({
popoverClassName="w-48"
mode="multiple"
options={fieldOptions}
placeholder="Select fields"
placeholder={t(
'side_panel.tables_section.table.index_select_fields'
)}
selected={index.fieldIds}
onChange={updateIndexFields}
emptyText="No types found."
@@ -60,7 +64,9 @@ export const TableIndex: React.FC<TableIndexProps> = ({
<PopoverContent className="w-52">
<div className="flex flex-col gap-2">
<div className="text-sm font-semibold">
Index Attributes
{t(
'side_panel.tables_section.table.index_actions.title'
)}
</div>
<Separator orientation="horizontal" />
<div className="flex flex-col gap-2">
@@ -68,7 +74,9 @@ export const TableIndex: React.FC<TableIndexProps> = ({
htmlFor="width"
className="text-gray-700"
>
Name
{t(
'side_panel.tables_section.table.index_actions.name'
)}
</Label>
<Input
value={index.name}
@@ -84,7 +92,9 @@ export const TableIndex: React.FC<TableIndexProps> = ({
htmlFor="width"
className="text-gray-700"
>
Unique
{t(
'side_panel.tables_section.table.index_actions.unique'
)}
</Label>
<Checkbox
checked={index.unique}
@@ -102,7 +112,9 @@ export const TableIndex: React.FC<TableIndexProps> = ({
onClick={removeIndex}
>
<Trash2 className="size-3.5 text-red-700" />
Delete index
{t(
'side_panel.tables_section.table.index_actions.delete_index'
)}
</Button>
</div>
</PopoverContent>

View File

@@ -14,6 +14,7 @@ import { useChartDB } from '@/hooks/use-chartdb';
import { TableField } from './table-field/table-field';
import { TableIndex } from './table-index/table-index';
import { DBIndex } from '@/lib/domain/db-index';
import { useTranslation } from 'react-i18next';
type AccordionItemValue = 'fields' | 'indexes';
@@ -32,6 +33,7 @@ export const TableListItemContent: React.FC<TableListItemContentProps> = ({
removeIndex,
updateIndex,
} = useChartDB();
const { t } = useTranslation();
const { color } = table;
const [selectedItems, setSelectedItems] = React.useState<
AccordionItemValue[]
@@ -73,7 +75,7 @@ export const TableListItemContent: React.FC<TableListItemContentProps> = ({
<div className="flex flex-1 items-center justify-between">
<div className="flex flex-row items-center gap-1">
<FileType2 className="size-4" />
Fields
{t('side_panel.tables_section.table.fields')}
</div>
<div className="flex flex-row-reverse">
<div className="hidden flex-row-reverse group-hover:flex">
@@ -116,7 +118,7 @@ export const TableListItemContent: React.FC<TableListItemContentProps> = ({
<div className="flex flex-1 items-center justify-between">
<div className="flex flex-row items-center gap-1">
<FileKey2 className="size-4" />
Indexes
{t('side_panel.tables_section.table.indexes')}
</div>
<div className="flex flex-row-reverse">
<div className="hidden flex-row-reverse group-hover:flex">
@@ -160,7 +162,7 @@ export const TableListItemContent: React.FC<TableListItemContentProps> = ({
onClick={createIndexHandler}
>
<FileKey2 className="h-4" />
Add Index
{t('side_panel.tables_section.table.add_index')}
</Button>
</div>
<div>
@@ -170,7 +172,7 @@ export const TableListItemContent: React.FC<TableListItemContentProps> = ({
onClick={() => createField(table.id)}
>
<FileType2 className="h-4" />
Add Field
{t('side_panel.tables_section.table.add_field')}
</Button>
</div>
</div>

View File

@@ -25,6 +25,7 @@ import {
import { useReactFlow } from '@xyflow/react';
import { useLayout } from '@/hooks/use-layout';
import { useBreakpoint } from '@/hooks/use-breakpoint';
import { useTranslation } from 'react-i18next';
export interface TableListItemHeaderProps {
table: DBTable;
@@ -34,6 +35,7 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
table,
}) => {
const { updateTable, removeTable, createIndex, createField } = useChartDB();
const { t } = useTranslation();
const { fitView, setNodes } = useReactFlow();
const { hideSidePanel } = useLayout();
const [editMode, setEditMode] = React.useState(false);
@@ -107,7 +109,11 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
</ListItemHeaderButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-40">
<DropdownMenuLabel>Table Actions</DropdownMenuLabel>
<DropdownMenuLabel>
{t(
'side_panel.tables_section.table.table_actions.title'
)}
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
@@ -117,7 +123,9 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
createField(table.id);
}}
>
Add field
{t(
'side_panel.tables_section.table.table_actions.add_field'
)}
<FileType2 className="size-3.5" />
</DropdownMenuItem>
<DropdownMenuItem
@@ -127,7 +135,9 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
createIndex(table.id);
}}
>
Add index
{t(
'side_panel.tables_section.table.table_actions.add_index'
)}
<FileKey2 className="size-3.5" />
</DropdownMenuItem>
</DropdownMenuGroup>
@@ -137,14 +147,16 @@ export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
onClick={deleteTableHandler}
className="flex justify-between !text-red-700"
>
Delete table
{t(
'side_panel.tables_section.table.table_actions.delete_table'
)}
<Trash2 className="size-3.5 text-red-700" />
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
),
[table.id, createField, createIndex, deleteTableHandler]
[table.id, createField, createIndex, deleteTableHandler, t]
);
return (

View File

@@ -9,11 +9,13 @@ import { useChartDB } from '@/hooks/use-chartdb';
import { useLayout } from '@/hooks/use-layout';
import { EmptyState } from '@/components/empty-state/empty-state';
import { ScrollArea } from '@/components/scroll-area/scroll-area';
import { useTranslation } from 'react-i18next';
export interface TablesSectionProps {}
export const TablesSection: React.FC<TablesSectionProps> = () => {
const { createTable, tables } = useChartDB();
const { t } = useTranslation();
const { closeAllTablesInSidebar } = useLayout();
const [filterText, setFilterText] = React.useState('');
@@ -40,7 +42,7 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
<div className="flex-1">
<Input
type="text"
placeholder="Filter"
placeholder={t('side_panel.tables_section.filter')}
className="h-8 w-full focus-visible:ring-0"
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
@@ -52,15 +54,19 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
onClick={createTable}
>
<Table className="h-4" />
Add Table
{t('side_panel.tables_section.add_table')}
</Button>
</div>
<div className="flex flex-1 flex-col overflow-hidden">
<ScrollArea className="h-full">
{tables.length === 0 ? (
<EmptyState
title="No tables"
description="Create a table to get started"
title={t(
'side_panel.tables_section.empty_state.title'
)}
description={t(
'side_panel.tables_section.empty_state.description'
)}
className="mt-20"
/>
) : (

View File

@@ -38,6 +38,7 @@ import {
keyboardShortcutsForOS,
} from '@/context/keyboard-shortcuts-context/keyboard-shortcuts';
import { useHistory } from '@/hooks/use-history';
import { useTranslation } from 'react-i18next';
export interface TopNavbarProps {}
@@ -55,6 +56,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
openExportSQLDialog,
showAlert,
} = useDialog();
const { t } = useTranslation();
const { redo, undo, hasRedo, hasUndo } = useHistory();
const { isMd: isDesktop } = useBreakpoint('md');
const { config, updateConfig } = useConfig();
@@ -195,7 +197,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
<Tooltip>
<TooltipTrigger>
<Badge variant="secondary" className="flex gap-1">
{isDesktop ? 'Last saved' : 'Saved'}
{isDesktop ? t('last_saved') : t('saved')}
<TimeAgo datetime={currentDiagram.updatedAt} />
</Badge>
</TooltipTrigger>
@@ -204,14 +206,14 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
</TooltipContent>
</Tooltip>
);
}, [currentDiagram.updatedAt, isDesktop]);
}, [currentDiagram.updatedAt, isDesktop, t]);
const renderDiagramName = useCallback(() => {
return (
<>
<DiagramIcon diagram={currentDiagram} />
<div className="flex">
{isDesktop ? <Label>Diagrams/</Label> : null}
{isDesktop ? <Label>{t('diagrams')}/</Label> : null}
</div>
<div className="flex flex-row items-center gap-1">
{editMode ? (
@@ -258,6 +260,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
editMode,
editedDiagramName,
isDesktop,
t,
]);
const emojiAI = '✨';
@@ -281,18 +284,20 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
<div>
<Menubar className="border-none shadow-none">
<MenubarMenu>
<MenubarTrigger>File</MenubarTrigger>
<MenubarTrigger>
{t('menu.file.file')}
</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={createNewDiagram}>
New
{t('menu.file.new')}
</MenubarItem>
<MenubarItem onClick={openDiagram}>
Open
{t('menu.file.open')}
</MenubarItem>
<MenubarSeparator />
<MenubarSub>
<MenubarSubTrigger>
Export SQL
{t('menu.file.export_sql')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem
@@ -368,7 +373,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
</MenubarSub>
<MenubarSub>
<MenubarSubTrigger>
Export as
{t('menu.file.export_as')}
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem onClick={exportPNG}>
@@ -386,26 +391,35 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
<MenubarItem
onClick={() =>
showAlert({
title: 'Delete Diagram',
description:
'This action cannot be undone. This will permanently delete the diagram.',
actionLabel: 'Delete',
closeLabel: 'Cancel',
title: t(
'delete_diagram_alert.title'
),
description: t(
'delete_diagram_alert.description'
),
actionLabel: t(
'delete_diagram_alert.delete'
),
closeLabel: t(
'delete_diagram_alert.cancel'
),
onAction: deleteDiagram,
})
}
>
Delete Diagram
{t('menu.file.delete_diagram')}
</MenubarItem>
<MenubarSeparator />
<MenubarItem>Exit</MenubarItem>
<MenubarItem>{t('menu.file.exit')}</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Edit</MenubarTrigger>
<MenubarTrigger>
{t('menu.edit.edit')}
</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={undo} disabled={!hasUndo}>
Undo
{t('menu.edit.undo')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
@@ -415,7 +429,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
</MenubarShortcut>
</MenubarItem>
<MenubarItem onClick={redo} disabled={!hasRedo}>
Redo
{t('menu.edit.redo')}
<MenubarShortcut>
{
keyboardShortcutsForOS[
@@ -428,16 +442,23 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
<MenubarItem
onClick={() =>
showAlert({
title: 'Clear Diagram',
description:
'This action cannot be undone. This will permanently delete all the data in the diagram.',
actionLabel: 'Clear',
closeLabel: 'Cancel',
title: t(
'clear_diagram_alert.title'
),
description: t(
'clear_diagram_alert.description'
),
actionLabel: t(
'clear_diagram_alert.clear'
),
closeLabel: t(
'clear_diagram_alert.cancel'
),
onAction: clearDiagramData,
})
}
>
Clear
{t('menu.edit.clear')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>
@@ -473,13 +494,15 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
</MenubarContent>
</MenubarMenu> */}
<MenubarMenu>
<MenubarTrigger>Help</MenubarTrigger>
<MenubarTrigger>
{t('menu.help.help')}
</MenubarTrigger>
<MenubarContent>
<MenubarItem onClick={openChartDBIO}>
Visit ChartDB
{t('menu.help.visit_website')}
</MenubarItem>
<MenubarItem onClick={openJoinDiscord}>
Join us on Discord
{t('menu.help.join_discord')}
</MenubarItem>
</MenubarContent>
</MenubarMenu>