mirror of
https://github.com/chartdb/chartdb.git
synced 2026-01-06 03:39:57 -06:00
add en translations
This commit is contained in:
committed by
Guy Ben-Aharon
parent
89e0cddd42
commit
cf3a10961d
65
package-lock.json
generated
65
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 ?? '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
19
src/i18n/i18n.ts
Normal 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
182
src/i18n/locales/en.ts
Normal 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',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user