Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@
"clsx": "^2.0.0",
"cmdk": "^1.0.0",
"dotenv": "^16.4.5",
"i18next": "^25.7.3",
"i18next-browser-languagedetector": "^8.2.0",
"lucide-react": "^0.371.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^16.5.0",
"react-query": "^3.39.3",
"react-router-dom": "^6.16.0",
"react-syntax-highlighter": "^16.1.0",
Expand Down Expand Up @@ -70,7 +73,7 @@
"postcss": "^8.4.31",
"prettier": "3.2.5",
"tailwindcss": "^3.3.2",
"typescript": "^4.9.5",
"typescript": "^5.0.0",
"vite": "6.3.4",
"vite-plugin-eslint": "^1.8.1",
"yargs": "^17.7.2"
Expand Down
7 changes: 7 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ import { useEffect } from "react";
import useAuth from "@/modules/authentication/hooks/useAuth";
import { Route, Routes } from "react-router-dom";
import { routes } from "@/routes";
import { useTranslation } from "react-i18next";

export default function App() {
const { i18n } = useTranslation();

useEffect(() => {
document.documentElement.dir = i18n.language === "ar" ? "rtl" : "ltr";
document.documentElement.lang = i18n.language;
}, [i18n.language]);
const queryClient = new QueryClient();
const { authenticate } = useAuth();
queryClient.setDefaultOptions({
Expand Down
1 change: 1 addition & 0 deletions src/api-client/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const API_ENDPOINTS = {
MCP_INSTALLATION: "/installation",
MCP_CLUSTER_DEBUG: "/debugCluster",
MCP_PROFILE_CLUSTER_DEBUG: "/debugProfileCluster",
MCP_EVENT_PIPELINE_DEBUG: "/analyzeEventPipeline",
EVENTS: "/events",
EVENT: "/event",
};
12 changes: 0 additions & 12 deletions src/api-client/util/GetPathFromType.ts

This file was deleted.

28 changes: 28 additions & 0 deletions src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";

import en from "./locales/en.json";
import fr from "./locales/fr.json";
import ar from "./locales/ar.json";

i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: { translation: en },
fr: { translation: fr },
ar: { translation: ar },
},
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
detection: {
order: ["localStorage", "navigator"],
caches: ["localStorage"],
},
});

export default i18n;
5 changes: 4 additions & 1 deletion src/lib/components/ui/inputs/SearchQueryParamInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ interface SearchInputProps {
onSearch?: (values: Record<string, string>) => void;
}

import { useTranslation } from "react-i18next";

export const SearchQueryParamInput: FC<SearchInputProps> = memo(
({ searchConfig, onSearch }) => {
const { t } = useTranslation();
const [searchParams, setSearchParams] = useSearchParams();

// Memoize initial values to avoid recalculating on every render
Expand Down Expand Up @@ -86,7 +89,7 @@ export const SearchQueryParamInput: FC<SearchInputProps> = memo(
{searchConfig.map(({ key, placeholder }) => (
<InputGroup key={key} className="max-w-sm my-2">
<InputGroupInput
placeholder={placeholder}
placeholder={t(placeholder)}
value={values[key]}
onChange={(e) => handleChange(key, e.target.value)}
/>
Expand Down
48 changes: 48 additions & 0 deletions src/lib/components/ui/inputs/language-switcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useTranslation } from "react-i18next";
import { Languages } from "lucide-react";

import { Button } from "@/lib/components/ui/inputs/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/lib/components/ui/inputs/dropdown-menu";

export function LanguageSwitcher() {
const { i18n, t } = useTranslation();

const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
document.documentElement.dir = lng === "ar" ? "rtl" : "ltr";
document.documentElement.lang = lng;
};

const languages = [
{ code: "en", name: "English" },
{ code: "fr", name: "Français" },
{ code: "ar", name: "العربية" },
];

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-9 px-0">
<Languages className="h-[1.2rem] w-[1.2rem]" />
<span className="sr-only">{t("common.language")}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{languages.map((lang) => (
<DropdownMenuItem
key={lang.code}
className="cursor-pointer"
onClick={() => changeLanguage(lang.code)}
>
{lang.name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
11 changes: 7 additions & 4 deletions src/lib/components/ui/inputs/mode-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,39 @@ import {

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

import { useTranslation } from "react-i18next";

export function ModeToggle() {
const { setTheme } = useTheme();
const { t } = useTranslation();

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-9 px-0">
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
<span className="sr-only">{t("common.toggle_theme")}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
className="cursor-pointer"
onClick={() => setTheme("light")}
>
Light
{t("common.light")}
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => setTheme("dark")}
>
Dark
{t("common.dark")}
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => setTheme("system")}
>
System
{t("common.system")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Expand Down
24 changes: 16 additions & 8 deletions src/lib/components/ui/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ import {
AccordionTrigger,
} from "@/lib/components/ui/navigation/accordion";
import { ModeToggle } from "@/lib/components/ui/inputs/mode-toggle";
import { LanguageSwitcher } from "@/lib/components/ui/inputs/language-switcher";
import { Badge } from "@/lib/components/ui/data-display/badge";
import { LogOutIcon } from "lucide-react";
import useAuth from "@/modules/authentication/hooks/useAuth";
import { VerifyInstallation } from "@/modules/common/components/actions/VerifyInstallation";
import { useTranslation } from "react-i18next";

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

const { logout } = useAuth();
const { t } = useTranslation();
return (
<header className="supports-backdrop-blur:bg-background/60 sticky top-0 z-50 w-full border-b bg-background/90 backdrop-blur">
<div className="container px-4 md:px-8 flex h-14 items-center">
Expand Down Expand Up @@ -71,7 +74,7 @@ export function Header() {
"bg-muted": subitem.to === location.pathname,
})}
>
{subitem.title}
{t(`common.${subitem.title.toLowerCase()}`)}
</DropdownMenuItem>
</NavLink>
) : subitem.label ? (
Expand Down Expand Up @@ -103,7 +106,9 @@ export function Header() {
}
>
{menu.icon && menu.icon}
<span className="ml-1">{menu.title}</span>
<span className="ml-1">
{t(`common.${menu.title.toLowerCase()}`)}
</span>
</NavLink>
),
)}
Expand All @@ -117,7 +122,7 @@ export function Header() {
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"
>
<HamburgerMenuIcon className="h-5 w-5" />
<span className="sr-only">Toggle Menu</span>
<span className="sr-only">{t("common.toggle_menu")}</span>
</Button>
</SheetTrigger>
<SheetContent side="left" className="pr-0 sm:max-w-xs">
Expand Down Expand Up @@ -164,7 +169,9 @@ export function Header() {
: "text-foreground/60",
)}
>
<div className="flex">{menu.title}</div>
<div className="flex">
{t(`common.${menu.title.toLowerCase()}`)}
</div>
</AccordionTrigger>
<AccordionContent className="pb-1 pl-4">
<div className="mt-1">
Expand All @@ -183,7 +190,7 @@ export function Header() {
)
}
>
{submenu.title}
{t(`common.${submenu.title.toLowerCase()}`)}
</NavLink>
) : submenu.label !== "" ? null : (
<div className="px-3">
Expand All @@ -206,7 +213,7 @@ export function Header() {
)
}
>
{menu.title}
{t(`common.${menu.title.toLowerCase()}`)}
</NavLink>
),
)}
Expand All @@ -223,7 +230,8 @@ export function Header() {
<div className="w-full flex-1 md:w-auto md:flex-none">
{/* <CommandMenu /> */}
</div>
<div className="hidden md:block">
<div className="hidden md:flex items-center space-x-2">
<LanguageSwitcher />
<ModeToggle />
</div>

Expand Down Expand Up @@ -251,7 +259,7 @@ export function Header() {

<Button variant={"outline"} onClick={logout} size={"sm"}>
<LogOutIcon className={"h-4 w-4 mx-1"} />
Logout
{t("common.logout")}
</Button>
</nav>
</div>
Expand Down
95 changes: 95 additions & 0 deletions src/locales/ar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"common": {
"logout": "تسجيل الخروج",
"public_preview": "معاينة عامة",
"toggle_menu": "تبديل القائمة",
"toggle_theme": "تبديل المظهر",
"light": "فاتح",
"dark": "داكن",
"system": "النظام",
"language": "اللغة",
"clusters": "الكلاسترز",
"profiles": "البروفايلات",
"events": "الأحداث",
"version": "الإصدار",
"namespace": "نيم سبيس",
"paused": "متوقف مؤقتا",
"labels": "الملصقات",
"kubernetes_version": "إصدار Kubernetes",
"event_trigger": "مشغل الحدث",
"matching_clusters": "الكلاسترز المتطابقة",
"description_clusters": "يمكنك عرض جميع الكلاسترز، وإعادة محاولة عمليات النشر الفاشلة، والعثور على أدلة استكشاف الأخطاء وإصلاحها لأي كلاستر.",
"description_profiles": "يمكنك عرض جميع المستويات والبروفايلات وتصور التابعين والتبعيات",
"description_events": "عرض وإدارة مشغلات الأحداث عبر الكلاسترز.",
"reset": "إعادة تعيين",
"name": "الاسم",
"manage_addons": "إدارة إضافات وموارد الكلاستر",
"addons": "الإضافات",
"export": "تصدير",
"add_addon": "إضافة إضافة",
"search_release_namespace": "البحث حسب نيم سبيس الإصدار",
"search_release_name": "البحث حسب اسم الإصدار",
"search_resource_namespace": "البحث حسب نيم سبيس المورد",
"search_resource_name": "البحث حسب اسم المورد",
"search_resource_kind": "البحث حسب نوع المورد",
"search_profile_kind": "البحث حسب نوع البروفايل",
"filter_name": "تصفية الكلاسترز حسب الاسم...",
"filter_namespace": "تصفية الكلاسترز حسب نيم سبيس...",
"filter_labels": "تصفية الكلاسترز حسب الملصقات...",
"back": "رجوع",
"tier": "المستوى",
"kind": "النوع",
"list_matching_clusters": "قائمة الكلاسترز المتطابقة مع البروفايل المحدد",
"no_matching_clusters": "لم يتم العثور على كلاسترز متطابقة.",
"cluster_name": "اسم الكلاستر",
"status": "الحالة",
"failed": "فشل",
"provisioned": "تم توفيره",
"feature_id": "معرف الميزة",
"failure_message": "رسالة الفشل",
"trigger_event": "تشغيل الحدث",
"resources": "الموارد",
"no_resources": "لم يتم العثور على موارد",
"running": "جاري التشغيل",
"no_resource_selectors": "لم يتم تحديد محددات الموارد.",
"resource_selector": "محدد الموارد",
"group": "المجموعة",
"api_version": "إصدار API",
"resource_collection_enabled": "تم تمكين جمع الموارد",
"resource_collection_disabled": "تم تعطيل جمع الموارد",
"target_scope": "النطاق المستهدف",
"yaml_definition": "تعريف YAML",
"clusters_linked": "كلاسترز مرتبطة",
"profile_associations": "ارتباطات البروفايل مع التابعين والاعتمادات المرتبطة داخل الكلاستر.",
"total_dependents": "إجمالي التوابع",
"total_dependencies": "إجمالي الاعتمادات",
"drag_and_drop": "سحب وإفلات",
"spec": "المواصفات",
"profile_specifications": "مواصفات البروفايل المحدد",
"cluster_selector": "محدد الكلاستر",
"sync_mode": "وضع المزامنة",
"policy_refs": "مراجع السياسة",
"helm_charts": "Helm Charts",
"debug": "تحقيق",
"ready": "Ready",
"healthy": "Healthy",
"last_applied": "Last Applied",
"refresh": "تحديث",
"failed_only": "الفاشلة فقط",
"feature": "الميزة",
"profile": "البروفايل",
"error": "خطأ",
"no_debug_data": "لا تتوفر بيانات للتحقيق.",
"no_debug_data_relax": "استرخ، لا يوجد شيء للتحقيق فيه هنا!",
"verify_installation": "تحقق من التثبيت",
"no_verification_data": "لا تتوفر بيانات التحقق.",
"search_profile_namespace": "البحث حسب نيم سبيس البروفايل",
"search_profile_name": "البحث حسب اسم البروفايل",
"search_event_name": "البحث حسب اسم الحدث",
"search_cluster_namespace": "البحث حسب نيم سبيس الكلاستر",
"search_cluster_name": "البحث حسب اسم الكلاستر",
"analyze_pipeline": "تحليل الانابيب",
"relax_no_errors": "استرخ، لا توجد أخطاء",
"correctly_installed": "تثبيت صحيح"
}
}
Loading