mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-05 05:42:39 +02:00
feat(i18n): Add next-intl framework with full bilingual support (EN/ZH)
- Implement next-intl framework for scalable i18n - Add complete Chinese (Simplified) localization - Support 400+ translated strings across all pages - Add language switcher with persistent preference - Zero breaking changes to existing functionality Framework additions: - i18n routing and middleware - LocaleContext for client-side state - LanguageSwitcher component - Translation files (en.json, zh.json) Translated components: - Homepage: Hero, features, CTA, navbar - Auth: Login, register - Dashboard: Main page, layout - Connectors: Management, add page (all categories) - Documents: Upload, manage, filters - Settings: LLM configs, role assignments - Onboarding: Add provider, assign roles - Logs: Task logs viewer Adding a new language now requires only: 1. Create messages/<locale>.json 2. Add locale to i18n/routing.ts
This commit is contained in:
parent
8aeaf419d0
commit
f58c7e4602
37 changed files with 2267 additions and 542 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import {
|
||||
|
|
@ -28,57 +29,87 @@ interface NavItem {
|
|||
}
|
||||
|
||||
export function NavMain({ items }: { items: NavItem[] }) {
|
||||
const t = useTranslations('nav_menu');
|
||||
|
||||
// Translation function that handles both exact matches and fallback to original
|
||||
const translateTitle = (title: string): string => {
|
||||
const titleMap: Record<string, string> = {
|
||||
'Researcher': 'researcher',
|
||||
'Manage LLMs': 'manage_llms',
|
||||
'Documents': 'documents',
|
||||
'Upload Documents': 'upload_documents',
|
||||
'Add Webpages': 'add_webpages',
|
||||
'Add Youtube Videos': 'add_youtube',
|
||||
'Manage Documents': 'manage_documents',
|
||||
'Connectors': 'connectors',
|
||||
'Add Connector': 'add_connector',
|
||||
'Manage Connectors': 'manage_connectors',
|
||||
'Podcasts': 'podcasts',
|
||||
'Logs': 'logs',
|
||||
'Platform': 'platform',
|
||||
};
|
||||
|
||||
const key = titleMap[title];
|
||||
return key ? t(key) : title;
|
||||
};
|
||||
|
||||
// Memoize items to prevent unnecessary re-renders
|
||||
const memoizedItems = useMemo(() => items, [items]);
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarGroupLabel>{translateTitle('Platform')}</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{memoizedItems.map((item, index) => (
|
||||
<Collapsible key={`${item.title}-${index}`} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip={item.title}
|
||||
isActive={item.isActive}
|
||||
aria-label={`${item.title}${item.items?.length ? " with submenu" : ""}`}
|
||||
>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{memoizedItems.map((item, index) => {
|
||||
const translatedTitle = translateTitle(item.title);
|
||||
return (
|
||||
<Collapsible key={`${item.title}-${index}`} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
tooltip={translatedTitle}
|
||||
isActive={item.isActive}
|
||||
aria-label={`${translatedTitle}${item.items?.length ? " with submenu" : ""}`}
|
||||
>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{translatedTitle}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction
|
||||
className="data-[state=open]:rotate-90 transition-transform duration-200"
|
||||
aria-label={`Toggle ${item.title} submenu`}
|
||||
>
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle submenu</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 duration-200">
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem, subIndex) => (
|
||||
<SidebarMenuSubItem key={`${subItem.title}-${subIndex}`}>
|
||||
<SidebarMenuSubButton asChild aria-label={subItem.title}>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction
|
||||
className="data-[state=open]:rotate-90 transition-transform duration-200"
|
||||
aria-label={`Toggle ${translatedTitle} submenu`}
|
||||
>
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle submenu</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 duration-200">
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem, subIndex) => {
|
||||
const translatedSubTitle = translateTitle(subItem.title);
|
||||
return (
|
||||
<SidebarMenuSubItem key={`${subItem.title}-${subIndex}`}>
|
||||
<SidebarMenuSubButton asChild aria-label={translatedSubTitle}>
|
||||
<a href={subItem.url}>
|
||||
<span>{translatedSubTitle}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
);
|
||||
})}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
);
|
||||
})}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue