Translate the database diagram to Frnech & Arabic

This commit is contained in:
KarimTamani
2025-05-24 20:26:47 +01:00
parent 057353156a
commit 158e4ef0f3
8 changed files with 360 additions and 219 deletions
+77 -108
View File
@@ -1,8 +1,9 @@
import { useTheme } from "next-themes";
import DropdownMenu, { MenuDropdownProps } from "./menu-dropdown";
import { useMemo } from "react";
import React, { useMemo } from "react";
import { menuItem } from "@heroui/react";
import { useTranslation } from "react-i18next";
@@ -17,119 +18,87 @@ interface MenuProps {
const Menu: React.FC<MenuProps> = ({ }) => {
const { resolvedTheme, setTheme } = useTheme()
const { setTheme } = useTheme()
const { t } = useTranslation();
const menu: MenuDropdownProps[] = useMemo(() => [
{
title: "File",
children: [{
title: "New"
},
{
title: "Open",
shortcut: "Ctnl + O"
}, {
title: "Save",
shortcut: "Ctnl + S",
divide: true
}, {
title: "Import",
divide: true,
children: [{
title: ".json",
}, {
title: ".dbml"
}, {
title: "MySql"
}, {
title: "Postgresql"
}]
}, {
title: "Export SQL",
children: [{
title: "Generic",
}, {
title: "MySql"
}, {
title: "Postgresql"
}]
}, {
title: "Export ORM Models",
divide: true
}, {
title: "Delete Project",
theme: "danger"
}]
}, {
title: "Edit",
clickHandler: () => {
console.log("hello wo")
},
children: [{
title: "Undo",
isDisabled: true,
},
{
title: "Redo",
isDisabled: true,
}, {
title: "Clear",
}]
}, {
title: "View",
children: [{
title: "Hide Controller",
shortcut: "Ctnl + B",
divide: true
}, {
title: "Zoom on scroll",
divide: true,
children: [{
title: "On"
}, {
title: "Off"
}],
}, {
title: "Theme",
clickHandler: () => {
console.log("hello wo")
title: t("menu.file"),
children: [
{ title: t("menu.new") },
{ title: t("menu.open"), shortcut: "Ctnl + O" },
{ title: t("menu.save"), shortcut: "Ctnl + S", divide: true },
{
title: t("menu.import"),
divide: true,
children: [
{ title: t("menu.json") },
{ title: t("menu.dbml") },
{ title: t("menu.mysql") },
{ title: t("menu.postgresql") },
],
},
children: [{
title: "Light",
clickHandler: () => {
setTheme("light");
}
}, {
title: "Dark",
clickHandler: () => {
setTheme("dark");
}
}]
}]
}, {
title: "Help",
children: [{
title: "Show Docs",
}, {
title: "Join Discord"
}]
}
], []);
{
title: t("menu.export_sql"),
children: [
{ title: t("menu.generic") },
{ title: t("menu.mysql") },
{ title: t("menu.postgresql") },
],
},
{ title: t("menu.export_orm_models"), divide: true },
{ title: t("menu.delete_project"), theme: "danger" },
],
},
{
title: t("menu.edit"),
clickHandler: () => console.log("hello wo"),
children: [
{ title: t("menu.undo"), isDisabled: true },
{ title: t("menu.redo"), isDisabled: true },
{ title: t("menu.clear") },
],
},
{
title: t("menu.view"),
children: [
{ title: t("menu.hide_controller"), shortcut: "Ctnl + B", divide: true },
{
title: t("menu.zoom_on_scroll"),
divide: true,
children: [
{ title: t("menu.on") },
{ title: t("menu.off") },
],
},
{
title: t("menu.theme"),
clickHandler: () => console.log("hello wo"),
children: [
{
title: t("menu.light"),
clickHandler: () => setTheme("light"),
},
{
title: t("menu.dark"),
clickHandler: () => setTheme("dark"),
},
],
},
],
},
{
title: t("menu.help"),
children: [
{ title: t("menu.show_docs") },
{ title: t("menu.join_discord") },
],
},
], [t]);
return <div className="gap-1 flex">
{
menu.map((menuItem , index ) => (
menu.map((menuItem, index) => (
<DropdownMenu {...menuItem} key={index} />
))
}
@@ -137,4 +106,4 @@ const Menu: React.FC<MenuProps> = ({ }) => {
}
export default Menu;
export default React.memo(Menu);
+1 -1
View File
@@ -28,7 +28,7 @@ i18n.use(LanguageDetector)
.use(initReactI18next)
.init({
resources ,
lng: 'en', // 👈 set default language to French
lng: 'en',
interpolation: {
escapeValue: false,
+99 -35
View File
@@ -1,48 +1,112 @@
import { Language } from "../types";
export const ar = {
translation: {
sidebar: {
tables: "الجداول",
relationships: "العلاقات"
},
db_controller: {
filter : "تصفية" ,
add_table : ضافة جدول" ,
add_field : "إضافة حقل" ,
add_index : "إضافة فهرس" ,
add_relationship : "إضافة علاقة" ,
show_code : "عرض الكود" ,
fields : "الحقول" ,
indexes : "الفهارس" ,
note : "ملاحظة" ,
name : "الاسم" ,
type : "النوع" ,
primary_key : "المفتاح الأساسي" ,
nullable : "يسمح بالقيمة الفارغة" ,
select_fields : "اختر الحقول" ,
unique : "فريد" ,
table_note : "ملاحظة الجدول" ,
collapse : "إخفاء الكل" ,
primary_table : "الجدول الأساسي" ,
referenced_table : "الجدول المشار إليه" ,
cardinality :{
name : "العلاقة" ,
one_to_one : "واحد إلى واحد" ,
one_to_many : "واحد إلى متعدد" ,
many_to_one : "متعدد إلى واحد" ,
many_to_many : "متعدد إلى متعدد" ,
},
delete : "حذف" ,
} ,
table: {
double_click: "انقر مرتين للتعديل"
modal: {
close: "إغلاق",
create: نشاء"
},
}
color_picker: {
default_color: "اللون الافتراضي"
},
db_controller: {
filter: "تصفية",
add_table: "إضافة جدول",
add_field: "إضافة حقل",
add_index: "إضافة فهرس",
add_relationship: "إضافة علاقة",
show_code: "عرض الكود",
fields: "الحقول",
indexes: "الفهارس",
note: "ملاحظة",
name: "الاسم",
type: "النوع",
nullable: "يمكن أن يكون فارغًا",
select_fields: "اختر الحقول",
unique: "فريد",
table_note: "ملاحظة الجدول",
collapse: "طي الكل",
}
primary_key: "المفتاح الأساسي",
foreign_key: "المفتاح الخارجي",
source_table: "جدول المصدر",
target_table: "جدول الهدف",
select_table: "اختر الجدول",
select_field: "اختر الحقل",
cardinality: {
name: "التعددية",
one_to_one: "واحد إلى واحد",
one_to_many: "واحد إلى متعدد",
many_to_one: "متعدد إلى واحد",
many_to_many: "متعدد إلى متعدد"
},
delete: "حذف",
field_setting: "إعدادات الحقل",
table_actions: "إجراءات الحقل",
actions: "إجراءات",
field_note: "ملاحظة الحقل",
delete_field: "حذف الحقل",
create_relationship: "إنشاء علاقة",
relationship_error: "لإنشاء علاقة، يجب أن يكون نوع مفتاح المصدر ونوع المفتاح المرجعي متماثلين.",
invalid_relationship: {
title: "علاقة غير صالحة",
description: "نوع مفتاح المصدر لا يتطابق مع نوع المفتاح المرجعي. يرجى التأكد من أن كلا المفتاحين لهما نفس نوع البيانات."
}
},
table: {
double_click: "انقر مزدوجًا للتعديل" ,
overlapping_tables: "الجداول المتداخلة",
},
control_buttons: {
redo: "إعادة",
undo: "تراجع",
zoom_in: "تكبير",
zoom_out: "تصغير",
adjust_positions: "تعديل المواقع",
show_all: "عرض الكل"
},
menu: {
file: "ملف",
new: "جديد",
open: "فتح",
save: "حفظ",
import: "استيراد",
json: ".json",
dbml: ".dbml",
mysql: "MySql",
postgresql: "Postgresql",
export_sql: "تصدير SQL",
generic: "عام",
export_orm_models: "تصدير ORM Models",
delete_project: "حذف المشروع",
edit: "تحرير",
undo: "تراجع",
redo: "إعادة",
clear: "مسح",
view: "عرض",
hide_controller: "إخفاء المتحكم",
zoom_on_scroll: "التكبير عند التمرير",
on: "تشغيل",
off: "إيقاف",
theme: "الثيم",
light: "فاتح",
dark: "داكن",
help: "مساعدة",
show_docs: "عرض المستندات",
join_discord: "الانضمام إلى ديسكورد"
}
}
};
export const arLanguage: Language = {
name: 'Arabic',
+51 -15
View File
@@ -12,9 +12,9 @@ export const en = {
close: "Close",
create: "Create"
},
color_picker : {
default_color : "Default color"
} ,
color_picker: {
default_color: "Default color"
},
db_controller: {
filter: "Filter",
add_table: "Add Table",
@@ -51,29 +51,65 @@ export const en = {
one_to_many: "One to Many",
many_to_one: "Many to One",
many_to_many: "Many to Many",
},
delete: "Delete",
field_setting: "Field Setting",
table_actions: "Field Actions",
actions : "Actions" ,
actions: "Actions",
field_note: "Field note",
delete_field: "Delete Field",
create_relationship: "Create Relationship",
relationship_error: "To create a relationship, the primary key and foreign key must be of the same type.",
invalid_relationship: {
title: "Invalid Relationship",
description: "The source key type does not match the referenced key type. Please ensure both keys have the same data type."
}
},
table: {
double_click: "Double click to edit"
} ,
control_buttons : {
redo : "Redo" ,
undo : "Undo" ,
zoom_in : "Zoom In" ,
zoom_out : "Zoom Out" ,
adjust_positions : "Adjust Positions" ,
show_all : "Show all"
double_click: "Double click to edit" ,
overlapping_tables : "Overlapping Tables"
},
control_buttons: {
redo: "Redo",
undo: "Undo",
zoom_in: "Zoom In",
zoom_out: "Zoom Out",
adjust_positions: "Adjust Positions",
show_all: "Show all"
},
menu: {
file: "File",
new: "New",
open: "Open",
save: "Save",
import: "Import",
json: ".json",
dbml: ".dbml",
mysql: "MySql",
postgresql: "Postgresql",
export_sql: "Export SQL",
generic: "Generic",
export_orm_models: "Export ORM Models",
delete_project: "Delete Project",
edit: "Edit",
undo: "Undo",
redo: "Redo",
clear: "Clear",
view: "View",
hide_controller: "Hide Controller",
zoom_on_scroll: "Zoom on scroll",
on: "On",
off: "Off",
theme: "Theme",
light: "Light",
dark: "Dark",
help: "Help",
show_docs: "Show Docs",
join_discord: "Join Discord"
}
}
+101 -36
View File
@@ -4,45 +4,110 @@ import { Language } from "../types";
export const fr = {
translation: {
sidebar: {
tables: "Tableaux",
tables: "Tables",
relationships: "Relations"
} ,
db_controller : {
filter : "Filtrer" ,
add_table : "Ajouter une table" ,
add_field : "Ajouter un champ" ,
add_index : "Ajouter un index" ,
add_relationship : "Ajouter une relation" ,
show_code : "Afficher le code" ,
fields : "Champs" ,
indexes : "Index" ,
note : "Note" ,
name : "Nom" ,
type : "Type" ,
primary_key : "Clé primaire" ,
nullable : "Nullable" ,
select_fields : "Sélectionner des champs" ,
unique : "Unique" ,
table_note : "Note de la table" ,
collapse : "Tout réduire" ,
primary_table : "Table primaire" ,
referenced_table : "Table référencée" ,
cardinality :{
name : "Cardinalité" ,
one_to_one : "Un à un" ,
one_to_many : "Un à plusieurs" ,
many_to_one : "Plusieurs à un" ,
many_to_many : "Plusieurs à plusieurs" ,
},
delete : "Supprimer" ,
} ,
table: {
double_click: "Double-cliquez pour modifier"
},
}
modal: {
close: "Fermer",
create: "Créer"
},
color_picker: {
default_color: "Couleur par défaut"
},
db_controller: {
filter: "Filtrer",
add_table: "Ajouter une Table",
add_field: "Ajouter un Champ",
add_index: "Ajouter un Index",
add_relationship: "Ajouter une Relation",
show_code: "Afficher le code",
fields: "Champs",
indexes: "Indexes",
note: "Note",
name: "Nom",
type: "Type",
nullable: "Nullable",
select_fields: "Sélectionner les champs",
unique: "Unique",
table_note: "Note de la Table",
collapse: "Tout réduire",
}
primary_key: "Clé primaire",
foreign_key: "Clé étrangère",
source_table: "Table source",
target_table: "Table cible",
select_table: "Sélectionner la table",
select_field: "Sélectionner le champ",
cardinality: {
name: "Cardinalité",
one_to_one: "Un à Un",
one_to_many: "Un à Plusieurs",
many_to_one: "Plusieurs à Un",
many_to_many: "Plusieurs à Plusieurs"
},
delete: "Supprimer",
field_setting: "Paramètres du champ",
table_actions: "Actions sur le champ",
actions: "Actions",
field_note: "Note sur le champ",
delete_field: "Supprimer le champ",
create_relationship: "Créer une Relation",
relationship_error: "Pour créer une relation, la primary key et la foreign key doivent être du même type.",
invalid_relationship: {
title: "Relation invalide",
description: "Le type de la source key ne correspond pas au type de la referenced key. Veuillez vous assurer que les deux clés ont le même type de données."
}
},
table: {
double_click: "Double-cliquez pour éditer" ,
overlapping_tables: "Tables qui se chevauchent",
},
control_buttons: {
redo: "Refaire",
undo: "Annuler",
zoom_in: "Zoom avant",
zoom_out: "Zoom arrière",
adjust_positions: "Ajuster les positions",
show_all: "Tout afficher"
},
menu: {
file: "Fichier",
new: "Nouveau",
open: "Ouvrir",
save: "Enregistrer",
import: "Importer",
json: ".json",
dbml: ".dbml",
mysql: "MySql",
postgresql: "Postgresql",
export_sql: "Exporter SQL",
generic: "Générique",
export_orm_models: "Exporter les ORM Models",
delete_project: "Supprimer le projet",
edit: "Éditer",
undo: "Annuler",
redo: "Refaire",
clear: "Effacer",
view: "Affichage",
hide_controller: "Masquer le contrôleur",
zoom_on_scroll: "Zoom au défilement",
on: "Activé",
off: "Désactivé",
theme: "Thème",
light: "Clair",
dark: "Sombre",
help: "Aide",
show_docs: "Afficher la documentation",
join_discord: "Rejoindre Discord"
}
}
};
export const frLanguage: Language = {
name: "French",
+8 -7
View File
@@ -35,10 +35,11 @@ import DatabaseControlButtons from "./database-control-buttons";
import { FieldType } from "@/lib/schemas/field-schema";
import useHighlightedEdges from "@/hooks/use-highlighted-edges";
import useOverlappingNodes from "@/hooks/use-overlapping-nodes";
import { useTranslation } from "react-i18next";
// Main functional component
const DatabasePage: React.FC<never> = () => {
const { t } = useTranslation() ;
// Extract database state and operations
const { database, getField } = useDatabase();
const { updateTablePositions, deleteMultiTables, deleteMultiRelationships, createRelationship } = useDatabaseOperations();
@@ -83,14 +84,14 @@ const DatabasePage: React.FC<never> = () => {
} else {
// Show error toast if invalid relationship
addToast({
title: "Invalid Relationship",
description: "Relationship should be between primary key and foreign key of the same time",
title: t("db_controller.invalid_relationship.title"),
description: t("db_controller.invalid_relationship.description"),
color: "danger",
});
}
setIsConnectionInProgress(false);
}, [database]);
}, [database , t ]);
// Called when nodes are updated (position changes or removed)
const handleNodesChanges: OnNodesChange<never> = useCallback(async (changes: NodeChange<never>[]) => {
@@ -196,7 +197,7 @@ const DatabasePage: React.FC<never> = () => {
<Background className="bg-default/40 dark:bg-black" />
</ReactFlow>
<div
className="absolute left-[12px] top-[64px] "
className="absolute left-[24px] bottom-[24px] "
>
{
isOverlapping &&
@@ -216,7 +217,7 @@ const DatabasePage: React.FC<never> = () => {
</span>
</TooltipTrigger>
<TooltipContent>
Overlapping Tables
{t("table.overlapping_tables")}
</TooltipContent>
</Tooltip>
}
@@ -1,3 +1,4 @@
import React from "react"
import { Outlet } from "react-router-dom"
@@ -8,6 +9,7 @@ interface Props {
const DBController : React.FC<Props> = ({ }) => {
console.log ("re-render db controller")
return(
<div className="w-[640px] h-full bg-background border-r p-2 pt-[52px] border-default-100">
<Outlet/>
@@ -15,6 +17,6 @@ const DBController : React.FC<Props> = ({ }) => {
)
}
export default DBController
export default React.memo(DBController)
@@ -9,47 +9,47 @@ import { useDatabase, useDatabaseOperations } from "@/providers/database-provide
import { TableInsertType, TableType } from "@/lib/schemas/table-schema";
import { v4 } from "uuid";
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
import { useViewport } from "@xyflow/react";
import { useReactFlow, useViewport } from "@xyflow/react";
interface Props { }
const PADDING_X = 40 ;
const PADDING_Y = 80 ;
const PADDING_X = 40;
const PADDING_Y = 80;
const TablesController: React.FC<Props> = ({ }) => {
const { database } = useDatabase();
const { createTable } = useDatabaseOperations();
const { getViewport } = useReactFlow();
const { tables } = database;
const viewport = useViewport();
//const viewport = useViewport();
const { t } = useTranslation();
const [selectedTable, setSelectedTable] = useState(new Set([]));
const { focusedTableId } = useDiagram();
const addNewTable = useCallback(async () => {
const newTableId: string = v4();
const viewport = getViewport();
const { x, y, zoom } = viewport;
// Convert screen (0,0) to flow coordinates using viewport values
const posX = -x / zoom + (PADDING_X / zoom);
const posY = -y / zoom + (PADDING_Y / zoom);
await createTable({
id: newTableId,
name: `table_${tables.length + 1}`,
posX ,
posY ,
fields : [{
id : v4() ,
name : "id" ,
isPrimary : true ,
unique : true
name: `table_${tables.length + 1}`,
posX,
posY,
fields: [{
id: v4(),
name: "id",
isPrimary: true,
unique: true
}]
} as TableInsertType);
setSelectedTable(new Set([newTableId]) as any);
}, [tables, viewport]);
}, [tables, getViewport]);
useEffect(() => {
@@ -59,6 +59,10 @@ const TablesController: React.FC<Props> = ({ }) => {
}, [focusedTableId]);
const selectedTableId = selectedTable.values().next().value;
useEffect(() => {
console.log("tables controller re-rendered")
}, [getViewport]);
return (