feat: localization service and frontend integration
- Add localization-service microservice (locale resolution, translations) - Add frontend API routes /api/locale and /api/translations/[locale] - Add LocalizationProvider and localization context - Integrate localization into layout, EmptyChat, MessageInput components - Update MICROSERVICES.md architecture docs - Add localization-service to workspaces Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
133
apps/frontend/src/lib/localization/context.tsx
Normal file
133
apps/frontend/src/lib/localization/context.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
fetchLocaleWithClient,
|
||||
fetchTranslations,
|
||||
type LocalizationContext as LocaleContext,
|
||||
} from '@/lib/localization';
|
||||
|
||||
type Translations = Record<string, string>;
|
||||
|
||||
interface LocalizationState {
|
||||
locale: LocaleContext | null;
|
||||
translations: Translations | null;
|
||||
loading: boolean;
|
||||
error: boolean;
|
||||
}
|
||||
|
||||
interface LocalizationContextValue extends LocalizationState {
|
||||
t: (key: string) => string;
|
||||
localeCode: string;
|
||||
}
|
||||
|
||||
const defaultTranslations: Translations = {
|
||||
'app.title': 'GooSeek',
|
||||
'app.searchPlaceholder': 'Search...',
|
||||
'empty.subtitle': 'Research begins here.',
|
||||
'input.askAnything': 'Ask anything...',
|
||||
'input.askFollowUp': 'Ask a follow-up',
|
||||
'chat.send': 'Send',
|
||||
'chat.newChat': 'New Chat',
|
||||
'chat.suggestions': 'Suggestions',
|
||||
'common.loading': 'Loading...',
|
||||
'common.error': 'Error',
|
||||
'common.retry': 'Retry',
|
||||
};
|
||||
|
||||
const LocalizationContext = createContext<LocalizationContextValue | null>(null);
|
||||
|
||||
export function LocalizationProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [state, setState] = useState<LocalizationState>({
|
||||
locale: null,
|
||||
translations: null,
|
||||
loading: true,
|
||||
error: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
async function load() {
|
||||
try {
|
||||
const loc = await fetchLocaleWithClient();
|
||||
if (cancelled) return;
|
||||
const trans = await fetchTranslations(loc.locale);
|
||||
if (cancelled) return;
|
||||
setState({
|
||||
locale: loc,
|
||||
translations: trans,
|
||||
loading: false,
|
||||
error: false,
|
||||
});
|
||||
if (typeof document !== 'undefined') {
|
||||
document.documentElement.lang = loc.locale;
|
||||
}
|
||||
} catch {
|
||||
if (cancelled) return;
|
||||
setState({
|
||||
locale: null,
|
||||
translations: null,
|
||||
loading: false,
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
load();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const t = useCallback(
|
||||
(key: string): string => {
|
||||
if (state.translations && key in state.translations) {
|
||||
return state.translations[key] ?? key;
|
||||
}
|
||||
return defaultTranslations[key] ?? key;
|
||||
},
|
||||
[state.translations],
|
||||
);
|
||||
|
||||
const localeCode = state.locale?.locale ?? 'en';
|
||||
|
||||
const value = useMemo<LocalizationContextValue>(
|
||||
() => ({
|
||||
...state,
|
||||
t,
|
||||
localeCode,
|
||||
}),
|
||||
[state, t, localeCode],
|
||||
);
|
||||
|
||||
return (
|
||||
<LocalizationContext.Provider value={value}>
|
||||
{children}
|
||||
</LocalizationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranslation(): LocalizationContextValue {
|
||||
const ctx = useContext(LocalizationContext);
|
||||
if (!ctx) {
|
||||
return {
|
||||
locale: null,
|
||||
translations: null,
|
||||
loading: false,
|
||||
error: true,
|
||||
t: (key: string) => defaultTranslations[key] ?? key,
|
||||
localeCode: 'en',
|
||||
};
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user