Skip to content

Commit f2f96f5

Browse files
authored
Merge pull request #122 from projectsveltos/events
feat : add event mcp debug button , add i18n implementation
2 parents c3957b0 + a5b94d6 commit f2f96f5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+697
-155
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@
3636
"clsx": "^2.0.0",
3737
"cmdk": "^1.0.0",
3838
"dotenv": "^16.4.5",
39+
"i18next": "^25.7.3",
40+
"i18next-browser-languagedetector": "^8.2.0",
3941
"lucide-react": "^0.371.0",
4042
"react": "^18.2.0",
4143
"react-dom": "^18.2.0",
44+
"react-i18next": "^16.5.0",
4245
"react-query": "^3.39.3",
4346
"react-router-dom": "^6.16.0",
4447
"react-syntax-highlighter": "^16.1.0",
@@ -70,7 +73,7 @@
7073
"postcss": "^8.4.31",
7174
"prettier": "3.2.5",
7275
"tailwindcss": "^3.3.2",
73-
"typescript": "^4.9.5",
76+
"typescript": "^5.0.0",
7477
"vite": "6.3.4",
7578
"vite-plugin-eslint": "^1.8.1",
7679
"yargs": "^17.7.2"

src/App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ import { useEffect } from "react";
77
import useAuth from "@/modules/authentication/hooks/useAuth";
88
import { Route, Routes } from "react-router-dom";
99
import { routes } from "@/routes";
10+
import { useTranslation } from "react-i18next";
1011

1112
export default function App() {
13+
const { i18n } = useTranslation();
14+
15+
useEffect(() => {
16+
document.documentElement.dir = i18n.language === "ar" ? "rtl" : "ltr";
17+
document.documentElement.lang = i18n.language;
18+
}, [i18n.language]);
1219
const queryClient = new QueryClient();
1320
const { authenticate } = useAuth();
1421
queryClient.setDefaultOptions({

src/api-client/endpoints.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const API_ENDPOINTS = {
99
MCP_INSTALLATION: "/installation",
1010
MCP_CLUSTER_DEBUG: "/debugCluster",
1111
MCP_PROFILE_CLUSTER_DEBUG: "/debugProfileCluster",
12+
MCP_EVENT_PIPELINE_DEBUG: "/analyzeEventPipeline",
1213
EVENTS: "/events",
1314
EVENT: "/event",
1415
};

src/api-client/util/GetPathFromType.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/i18n.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import i18n from "i18next";
2+
import { initReactI18next } from "react-i18next";
3+
import LanguageDetector from "i18next-browser-languagedetector";
4+
5+
import en from "./locales/en.json";
6+
import fr from "./locales/fr.json";
7+
import ar from "./locales/ar.json";
8+
9+
i18n
10+
.use(LanguageDetector)
11+
.use(initReactI18next)
12+
.init({
13+
resources: {
14+
en: { translation: en },
15+
fr: { translation: fr },
16+
ar: { translation: ar },
17+
},
18+
fallbackLng: "en",
19+
interpolation: {
20+
escapeValue: false,
21+
},
22+
detection: {
23+
order: ["localStorage", "navigator"],
24+
caches: ["localStorage"],
25+
},
26+
});
27+
28+
export default i18n;

src/lib/components/ui/inputs/SearchQueryParamInput.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ interface SearchInputProps {
1919
onSearch?: (values: Record<string, string>) => void;
2020
}
2121

22+
import { useTranslation } from "react-i18next";
23+
2224
export const SearchQueryParamInput: FC<SearchInputProps> = memo(
2325
({ searchConfig, onSearch }) => {
26+
const { t } = useTranslation();
2427
const [searchParams, setSearchParams] = useSearchParams();
2528

2629
// Memoize initial values to avoid recalculating on every render
@@ -86,7 +89,7 @@ export const SearchQueryParamInput: FC<SearchInputProps> = memo(
8689
{searchConfig.map(({ key, placeholder }) => (
8790
<InputGroup key={key} className="max-w-sm my-2">
8891
<InputGroupInput
89-
placeholder={placeholder}
92+
placeholder={t(placeholder)}
9093
value={values[key]}
9194
onChange={(e) => handleChange(key, e.target.value)}
9295
/>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useTranslation } from "react-i18next";
2+
import { Languages } from "lucide-react";
3+
4+
import { Button } from "@/lib/components/ui/inputs/button";
5+
import {
6+
DropdownMenu,
7+
DropdownMenuContent,
8+
DropdownMenuItem,
9+
DropdownMenuTrigger,
10+
} from "@/lib/components/ui/inputs/dropdown-menu";
11+
12+
export function LanguageSwitcher() {
13+
const { i18n, t } = useTranslation();
14+
15+
const changeLanguage = (lng: string) => {
16+
i18n.changeLanguage(lng);
17+
document.documentElement.dir = lng === "ar" ? "rtl" : "ltr";
18+
document.documentElement.lang = lng;
19+
};
20+
21+
const languages = [
22+
{ code: "en", name: "English" },
23+
{ code: "fr", name: "Français" },
24+
{ code: "ar", name: "العربية" },
25+
];
26+
27+
return (
28+
<DropdownMenu>
29+
<DropdownMenuTrigger asChild>
30+
<Button variant="ghost" className="w-9 px-0">
31+
<Languages className="h-[1.2rem] w-[1.2rem]" />
32+
<span className="sr-only">{t("common.language")}</span>
33+
</Button>
34+
</DropdownMenuTrigger>
35+
<DropdownMenuContent align="end">
36+
{languages.map((lang) => (
37+
<DropdownMenuItem
38+
key={lang.code}
39+
className="cursor-pointer"
40+
onClick={() => changeLanguage(lang.code)}
41+
>
42+
{lang.name}
43+
</DropdownMenuItem>
44+
))}
45+
</DropdownMenuContent>
46+
</DropdownMenu>
47+
);
48+
}

src/lib/components/ui/inputs/mode-toggle.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,39 @@ import {
1010

1111
import { useTheme } from "@/hooks/useTheme";
1212

13+
import { useTranslation } from "react-i18next";
14+
1315
export function ModeToggle() {
1416
const { setTheme } = useTheme();
17+
const { t } = useTranslation();
1518

1619
return (
1720
<DropdownMenu>
1821
<DropdownMenuTrigger asChild>
1922
<Button variant="ghost" className="w-9 px-0">
2023
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
2124
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
22-
<span className="sr-only">Toggle theme</span>
25+
<span className="sr-only">{t("common.toggle_theme")}</span>
2326
</Button>
2427
</DropdownMenuTrigger>
2528
<DropdownMenuContent align="end">
2629
<DropdownMenuItem
2730
className="cursor-pointer"
2831
onClick={() => setTheme("light")}
2932
>
30-
Light
33+
{t("common.light")}
3134
</DropdownMenuItem>
3235
<DropdownMenuItem
3336
className="cursor-pointer"
3437
onClick={() => setTheme("dark")}
3538
>
36-
Dark
39+
{t("common.dark")}
3740
</DropdownMenuItem>
3841
<DropdownMenuItem
3942
className="cursor-pointer"
4043
onClick={() => setTheme("system")}
4144
>
42-
System
45+
{t("common.system")}
4346
</DropdownMenuItem>
4447
</DropdownMenuContent>
4548
</DropdownMenu>

src/lib/components/ui/layout/Header.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ import {
2727
AccordionTrigger,
2828
} from "@/lib/components/ui/navigation/accordion";
2929
import { ModeToggle } from "@/lib/components/ui/inputs/mode-toggle";
30+
import { LanguageSwitcher } from "@/lib/components/ui/inputs/language-switcher";
3031
import { Badge } from "@/lib/components/ui/data-display/badge";
3132
import { LogOutIcon } from "lucide-react";
3233
import useAuth from "@/modules/authentication/hooks/useAuth";
3334
import { VerifyInstallation } from "@/modules/common/components/actions/VerifyInstallation";
35+
import { useTranslation } from "react-i18next";
3436

3537
export function Header() {
3638
const [open, setOpen] = useState<boolean>(false);
@@ -39,6 +41,7 @@ export function Header() {
3941
const isPublicPreview = (version?.split(".")[0] ?? "") === "0" || true;
4042

4143
const { logout } = useAuth();
44+
const { t } = useTranslation();
4245
return (
4346
<header className="supports-backdrop-blur:bg-background/60 sticky top-0 z-50 w-full border-b bg-background/90 backdrop-blur">
4447
<div className="container px-4 md:px-8 flex h-14 items-center">
@@ -71,7 +74,7 @@ export function Header() {
7174
"bg-muted": subitem.to === location.pathname,
7275
})}
7376
>
74-
{subitem.title}
77+
{t(`common.${subitem.title.toLowerCase()}`)}
7578
</DropdownMenuItem>
7679
</NavLink>
7780
) : subitem.label ? (
@@ -103,7 +106,9 @@ export function Header() {
103106
}
104107
>
105108
{menu.icon && menu.icon}
106-
<span className="ml-1">{menu.title}</span>
109+
<span className="ml-1">
110+
{t(`common.${menu.title.toLowerCase()}`)}
111+
</span>
107112
</NavLink>
108113
),
109114
)}
@@ -117,7 +122,7 @@ export function Header() {
117122
className="mr-4 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
118123
>
119124
<HamburgerMenuIcon className="h-5 w-5" />
120-
<span className="sr-only">Toggle Menu</span>
125+
<span className="sr-only">{t("common.toggle_menu")}</span>
121126
</Button>
122127
</SheetTrigger>
123128
<SheetContent side="left" className="pr-0 sm:max-w-xs">
@@ -164,7 +169,9 @@ export function Header() {
164169
: "text-foreground/60",
165170
)}
166171
>
167-
<div className="flex">{menu.title}</div>
172+
<div className="flex">
173+
{t(`common.${menu.title.toLowerCase()}`)}
174+
</div>
168175
</AccordionTrigger>
169176
<AccordionContent className="pb-1 pl-4">
170177
<div className="mt-1">
@@ -183,7 +190,7 @@ export function Header() {
183190
)
184191
}
185192
>
186-
{submenu.title}
193+
{t(`common.${submenu.title.toLowerCase()}`)}
187194
</NavLink>
188195
) : submenu.label !== "" ? null : (
189196
<div className="px-3">
@@ -206,7 +213,7 @@ export function Header() {
206213
)
207214
}
208215
>
209-
{menu.title}
216+
{t(`common.${menu.title.toLowerCase()}`)}
210217
</NavLink>
211218
),
212219
)}
@@ -223,7 +230,8 @@ export function Header() {
223230
<div className="w-full flex-1 md:w-auto md:flex-none">
224231
{/* <CommandMenu /> */}
225232
</div>
226-
<div className="hidden md:block">
233+
<div className="hidden md:flex items-center space-x-2">
234+
<LanguageSwitcher />
227235
<ModeToggle />
228236
</div>
229237

@@ -251,7 +259,7 @@ export function Header() {
251259

252260
<Button variant={"outline"} onClick={logout} size={"sm"}>
253261
<LogOutIcon className={"h-4 w-4 mx-1"} />
254-
Logout
262+
{t("common.logout")}
255263
</Button>
256264
</nav>
257265
</div>

src/locales/ar.json

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"common": {
3+
"logout": "تسجيل الخروج",
4+
"public_preview": "معاينة عامة",
5+
"toggle_menu": "تبديل القائمة",
6+
"toggle_theme": "تبديل المظهر",
7+
"light": "فاتح",
8+
"dark": "داكن",
9+
"system": "النظام",
10+
"language": "اللغة",
11+
"clusters": "الكلاسترز",
12+
"profiles": "البروفايلات",
13+
"events": "الأحداث",
14+
"version": "الإصدار",
15+
"namespace": "نيم سبيس",
16+
"paused": "متوقف مؤقتا",
17+
"labels": "الملصقات",
18+
"kubernetes_version": "إصدار Kubernetes",
19+
"event_trigger": "مشغل الحدث",
20+
"matching_clusters": "الكلاسترز المتطابقة",
21+
"description_clusters": "يمكنك عرض جميع الكلاسترز، وإعادة محاولة عمليات النشر الفاشلة، والعثور على أدلة استكشاف الأخطاء وإصلاحها لأي كلاستر.",
22+
"description_profiles": "يمكنك عرض جميع المستويات والبروفايلات وتصور التابعين والتبعيات",
23+
"description_events": "عرض وإدارة مشغلات الأحداث عبر الكلاسترز.",
24+
"reset": "إعادة تعيين",
25+
"name": "الاسم",
26+
"manage_addons": "إدارة إضافات وموارد الكلاستر",
27+
"addons": "الإضافات",
28+
"export": "تصدير",
29+
"add_addon": "إضافة إضافة",
30+
"search_release_namespace": "البحث حسب نيم سبيس الإصدار",
31+
"search_release_name": "البحث حسب اسم الإصدار",
32+
"search_resource_namespace": "البحث حسب نيم سبيس المورد",
33+
"search_resource_name": "البحث حسب اسم المورد",
34+
"search_resource_kind": "البحث حسب نوع المورد",
35+
"search_profile_kind": "البحث حسب نوع البروفايل",
36+
"filter_name": "تصفية الكلاسترز حسب الاسم...",
37+
"filter_namespace": "تصفية الكلاسترز حسب نيم سبيس...",
38+
"filter_labels": "تصفية الكلاسترز حسب الملصقات...",
39+
"back": "رجوع",
40+
"tier": "المستوى",
41+
"kind": "النوع",
42+
"list_matching_clusters": "قائمة الكلاسترز المتطابقة مع البروفايل المحدد",
43+
"no_matching_clusters": "لم يتم العثور على كلاسترز متطابقة.",
44+
"cluster_name": "اسم الكلاستر",
45+
"status": "الحالة",
46+
"failed": "فشل",
47+
"provisioned": "تم توفيره",
48+
"feature_id": "معرف الميزة",
49+
"failure_message": "رسالة الفشل",
50+
"trigger_event": "تشغيل الحدث",
51+
"resources": "الموارد",
52+
"no_resources": "لم يتم العثور على موارد",
53+
"running": "جاري التشغيل",
54+
"no_resource_selectors": "لم يتم تحديد محددات الموارد.",
55+
"resource_selector": "محدد الموارد",
56+
"group": "المجموعة",
57+
"api_version": "إصدار API",
58+
"resource_collection_enabled": "تم تمكين جمع الموارد",
59+
"resource_collection_disabled": "تم تعطيل جمع الموارد",
60+
"target_scope": "النطاق المستهدف",
61+
"yaml_definition": "تعريف YAML",
62+
"clusters_linked": "كلاسترز مرتبطة",
63+
"profile_associations": "ارتباطات البروفايل مع التابعين والاعتمادات المرتبطة داخل الكلاستر.",
64+
"total_dependents": "إجمالي التوابع",
65+
"total_dependencies": "إجمالي الاعتمادات",
66+
"drag_and_drop": "سحب وإفلات",
67+
"spec": "المواصفات",
68+
"profile_specifications": "مواصفات البروفايل المحدد",
69+
"cluster_selector": "محدد الكلاستر",
70+
"sync_mode": "وضع المزامنة",
71+
"policy_refs": "مراجع السياسة",
72+
"helm_charts": "Helm Charts",
73+
"debug": "تحقيق",
74+
"ready": "Ready",
75+
"healthy": "Healthy",
76+
"last_applied": "Last Applied",
77+
"refresh": "تحديث",
78+
"failed_only": "الفاشلة فقط",
79+
"feature": "الميزة",
80+
"profile": "البروفايل",
81+
"error": "خطأ",
82+
"no_debug_data": "لا تتوفر بيانات للتحقيق.",
83+
"no_debug_data_relax": "استرخ، لا يوجد شيء للتحقيق فيه هنا!",
84+
"verify_installation": "تحقق من التثبيت",
85+
"no_verification_data": "لا تتوفر بيانات التحقق.",
86+
"search_profile_namespace": "البحث حسب نيم سبيس البروفايل",
87+
"search_profile_name": "البحث حسب اسم البروفايل",
88+
"search_event_name": "البحث حسب اسم الحدث",
89+
"search_cluster_namespace": "البحث حسب نيم سبيس الكلاستر",
90+
"search_cluster_name": "البحث حسب اسم الكلاستر",
91+
"analyze_pipeline": "تحليل الانابيب",
92+
"relax_no_errors": "استرخ، لا توجد أخطاء",
93+
"correctly_installed": "تثبيت صحيح"
94+
}
95+
}

0 commit comments

Comments
 (0)