mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 08:46:22 +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 { Trash2 } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { AppSidebar } from "@/components/sidebar/app-sidebar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -55,6 +56,8 @@ export function AppSidebarProvider({
|
|||
navSecondary,
|
||||
navMain,
|
||||
}: AppSidebarProviderProps) {
|
||||
const t = useTranslations('dashboard');
|
||||
const tCommon = useTranslations('common');
|
||||
const [recentChats, setRecentChats] = useState<
|
||||
{
|
||||
name: string;
|
||||
|
|
@ -196,14 +199,14 @@ export function AppSidebarProvider({
|
|||
if (chatError) {
|
||||
return [
|
||||
{
|
||||
name: "Error loading chats",
|
||||
name: t('error_loading_chats'),
|
||||
url: "#",
|
||||
icon: "AlertCircle",
|
||||
id: 0,
|
||||
search_space_id: Number(searchSpaceId),
|
||||
actions: [
|
||||
{
|
||||
name: "Retry",
|
||||
name: tCommon('retry'),
|
||||
icon: "RefreshCw",
|
||||
onClick: retryFetch,
|
||||
},
|
||||
|
|
@ -215,7 +218,7 @@ export function AppSidebarProvider({
|
|||
if (!isLoadingChats && recentChats.length === 0) {
|
||||
return [
|
||||
{
|
||||
name: "No recent chats",
|
||||
name: t('no_recent_chats'),
|
||||
url: "#",
|
||||
icon: "MessageCircleMore",
|
||||
id: 0,
|
||||
|
|
@ -226,7 +229,7 @@ export function AppSidebarProvider({
|
|||
}
|
||||
|
||||
return [];
|
||||
}, [chatError, isLoadingChats, recentChats.length, searchSpaceId, retryFetch]);
|
||||
}, [chatError, isLoadingChats, recentChats.length, searchSpaceId, retryFetch, t, tCommon]);
|
||||
|
||||
// Use fallback chats if there's an error or no chats
|
||||
const displayChats = recentChats.length > 0 ? recentChats : fallbackChats;
|
||||
|
|
@ -240,14 +243,14 @@ export function AppSidebarProvider({
|
|||
title:
|
||||
searchSpace?.name ||
|
||||
(isLoadingSearchSpace
|
||||
? "Loading..."
|
||||
? tCommon('loading')
|
||||
: searchSpaceError
|
||||
? "Error loading search space"
|
||||
: "Unknown Search Space"),
|
||||
? t('error_loading_space')
|
||||
: t('unknown_search_space')),
|
||||
};
|
||||
}
|
||||
return updated;
|
||||
}, [navSecondary, isClient, searchSpace?.name, isLoadingSearchSpace, searchSpaceError]);
|
||||
}, [navSecondary, isClient, searchSpace?.name, isLoadingSearchSpace, searchSpaceError, t, tCommon]);
|
||||
|
||||
// Show loading state if not client-side
|
||||
if (!isClient) {
|
||||
|
|
@ -264,12 +267,11 @@ export function AppSidebarProvider({
|
|||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Trash2 className="h-5 w-5 text-destructive" />
|
||||
<span>Delete Chat</span>
|
||||
<span>{t('delete_chat')}</span>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete{" "}
|
||||
<span className="font-medium">{chatToDelete?.name}</span>? This action cannot be
|
||||
undone.
|
||||
{t('delete_chat_confirm')}{" "}
|
||||
<span className="font-medium">{chatToDelete?.name}</span>? {t('action_cannot_undone')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex gap-2 sm:justify-end">
|
||||
|
|
@ -278,7 +280,7 @@ export function AppSidebarProvider({
|
|||
onClick={() => setShowDeleteDialog(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
Cancel
|
||||
{tCommon('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
|
|
@ -289,12 +291,12 @@ export function AppSidebarProvider({
|
|||
{isDeleting ? (
|
||||
<>
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
Deleting...
|
||||
{t('deleting')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Delete
|
||||
{tCommon('delete')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue