SurfSense/surfsense_web/contexts/LocaleContext.tsx
Differ f58c7e4602 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
2025-10-26 17:18:57 +08:00

70 lines
2 KiB
TypeScript

'use client';
import React, { createContext, useContext, useState, useEffect } from 'react';
import enMessages from '../messages/en.json';
import zhMessages from '../messages/zh.json';
type Locale = 'en' | 'zh';
interface LocaleContextType {
locale: Locale;
messages: typeof enMessages;
setLocale: (locale: Locale) => void;
}
const LocaleContext = createContext<LocaleContextType | undefined>(undefined);
const LOCALE_STORAGE_KEY = 'surfsense-locale';
export function LocaleProvider({ children }: { children: React.ReactNode }) {
// Always start with 'en' to avoid hydration mismatch
// Then sync with localStorage after mount
const [locale, setLocaleState] = useState<Locale>('en');
const [mounted, setMounted] = useState(false);
// Get messages based on current locale
const messages = locale === 'zh' ? zhMessages : enMessages;
// Load locale from localStorage after component mounts (client-side only)
useEffect(() => {
setMounted(true);
if (typeof window !== 'undefined') {
const stored = localStorage.getItem(LOCALE_STORAGE_KEY);
if (stored === 'zh') {
setLocaleState('zh');
}
}
}, []);
// Update locale and persist to localStorage
const setLocale = (newLocale: Locale) => {
setLocaleState(newLocale);
if (typeof window !== 'undefined') {
localStorage.setItem(LOCALE_STORAGE_KEY, newLocale);
// Update HTML lang attribute
document.documentElement.lang = newLocale;
}
};
// Set HTML lang attribute when locale changes
useEffect(() => {
if (typeof window !== 'undefined' && mounted) {
document.documentElement.lang = locale;
}
}, [locale, mounted]);
return (
<LocaleContext.Provider value={{ locale, messages, setLocale }}>
{children}
</LocaleContext.Provider>
);
}
export function useLocaleContext() {
const context = useContext(LocaleContext);
if (context === undefined) {
throw new Error('useLocaleContext must be used within a LocaleProvider');
}
return context;
}