diff --git a/src/App.tsx b/src/App.tsx index afbb47b..809a011 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { useNotifications } from '@/hooks/use-notifications' import { useAppData } from '@/hooks/use-app-data' import { useAppActions } from '@/hooks/use-app-actions' import { useViewPreload } from '@/hooks/use-view-preload' +import { useLocaleInit } from '@/hooks/use-locale-init' import { Sidebar } from '@/components/navigation' import { NotificationCenter } from '@/components/NotificationCenter' import { ViewRouter } from '@/components/ViewRouter' @@ -26,6 +27,7 @@ function App() { useSampleData() useViewPreload() + useLocaleInit() const { notifications, addNotification, markAsRead, markAllAsRead, deleteNotification, unreadCount } = useNotifications() diff --git a/src/hooks/index.ts b/src/hooks/index.ts index b5ed005..63764a8 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -70,6 +70,7 @@ export { useSortableData } from './use-sortable-data' export { useFilterableData } from './use-filterable-data' export { useFormatter } from './use-formatter' export { useTemplateManager } from './use-template-manager' +export { useLocaleInit } from './use-locale-init' export { useFetch } from './use-fetch' export { useLocalStorageState } from './use-local-storage-state' @@ -145,4 +146,3 @@ export type { UseAsyncActionResult } from './use-async-action' export type { UseMutationOptions, UseMutationResult } from './use-mutation' export type { UseFavoritesOptions, UseFavoritesResult } from './use-favorites' export type { UseClipboardResult } from './use-clipboard-copy' - diff --git a/src/hooks/use-locale-init.ts b/src/hooks/use-locale-init.ts new file mode 100644 index 0000000..c736966 --- /dev/null +++ b/src/hooks/use-locale-init.ts @@ -0,0 +1,18 @@ +import { useEffect } from 'react' +import { useKV } from '@github/spark/hooks' +import { useAppDispatch, useAppSelector } from '@/store/hooks' +import { setLocale } from '@/store/slices/uiSlice' + +type Locale = 'en' | 'es' | 'fr' + +export function useLocaleInit() { + const dispatch = useAppDispatch() + const reduxLocale = useAppSelector(state => state.ui.locale) + const [kvLocale] = useKV('app-locale', 'en') + + useEffect(() => { + if (kvLocale && kvLocale !== reduxLocale) { + dispatch(setLocale(kvLocale)) + } + }, [kvLocale, reduxLocale, dispatch]) +} diff --git a/src/hooks/use-translation.ts b/src/hooks/use-translation.ts index e86d836..8666f94 100644 --- a/src/hooks/use-translation.ts +++ b/src/hooks/use-translation.ts @@ -1,5 +1,7 @@ import { useKV } from '@github/spark/hooks' import { useState, useEffect, useCallback } from 'react' +import { useAppSelector, useAppDispatch } from '@/store/hooks' +import { setLocale as setReduxLocale } from '@/store/slices/uiSlice' type Translations = Record type Locale = 'en' | 'es' | 'fr' @@ -8,11 +10,21 @@ const AVAILABLE_LOCALES: Locale[] = ['en', 'es', 'fr'] const DEFAULT_LOCALE: Locale = 'en' export function useTranslation() { - const [locale, setLocale] = useKV('app-locale', DEFAULT_LOCALE) + const dispatch = useAppDispatch() + const reduxLocale = useAppSelector(state => state.ui.locale) + const [kvLocale, setKVLocale] = useKV('app-locale', DEFAULT_LOCALE) const [translations, setTranslations] = useState({}) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) + useEffect(() => { + if (kvLocale && kvLocale !== reduxLocale) { + dispatch(setReduxLocale(kvLocale)) + } + }, [kvLocale, reduxLocale, dispatch]) + + const locale = reduxLocale + useEffect(() => { const loadTranslations = async () => { try { @@ -68,9 +80,10 @@ export function useTranslation() { const changeLocale = useCallback((newLocale: Locale) => { if (AVAILABLE_LOCALES.includes(newLocale)) { - setLocale(newLocale) + dispatch(setReduxLocale(newLocale)) + setKVLocale(newLocale) } - }, [setLocale]) + }, [dispatch, setKVLocale]) return { t, @@ -83,16 +96,18 @@ export function useTranslation() { } export function useLocale() { - const [locale] = useKV('app-locale', DEFAULT_LOCALE) + const locale = useAppSelector(state => state.ui.locale) return locale } export function useChangeLocale() { - const [, setLocale] = useKV('app-locale', DEFAULT_LOCALE) + const dispatch = useAppDispatch() + const [, setKVLocale] = useKV('app-locale', DEFAULT_LOCALE) return useCallback((newLocale: Locale) => { if (AVAILABLE_LOCALES.includes(newLocale)) { - setLocale(newLocale) + dispatch(setReduxLocale(newLocale)) + setKVLocale(newLocale) } - }, [setLocale]) + }, [dispatch, setKVLocale]) } diff --git a/src/store/slices/uiSlice.ts b/src/store/slices/uiSlice.ts index f83876b..6fbb622 100644 --- a/src/store/slices/uiSlice.ts +++ b/src/store/slices/uiSlice.ts @@ -1,17 +1,21 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -type View = 'dashboard' | 'timesheets' | 'billing' | 'payroll' | 'compliance' | 'expenses' | 'roadmap' | 'reports' | 'currency' | 'email-templates' | 'invoice-templates' | 'qr-scanner' | 'missing-timesheets' | 'purchase-orders' | 'onboarding' | 'audit-trail' | 'notification-rules' | 'batch-import' | 'rate-templates' | 'custom-reports' | 'holiday-pay' | 'contract-validation' | 'shift-patterns' | 'query-guide' | 'component-showcase' | 'business-logic-demo' | 'data-admin' | 'translation-demo' | 'profile' +type View = 'dashboard' | 'timesheets' | 'billing' | 'payroll' | 'compliance' | 'expenses' | 'roadmap' | 'reports' | 'currency' | 'email-templates' | 'invoice-templates' | 'qr-scanner' | 'missing-timesheets' | 'purchase-orders' | 'onboarding' | 'audit-trail' | 'notification-rules' | 'batch-import' | 'rate-templates' | 'custom-reports' | 'holiday-pay' | 'contract-validation' | 'shift-patterns' | 'query-guide' | 'component-showcase' | 'business-logic-demo' | 'data-admin' | 'translation-demo' | 'profile' | 'roles-permissions' + +type Locale = 'en' | 'es' | 'fr' interface UIState { currentView: View searchQuery: string sidebarCollapsed: boolean + locale: Locale } const initialState: UIState = { currentView: 'dashboard', searchQuery: '', sidebarCollapsed: false, + locale: 'en', } const uiSlice = createSlice({ @@ -27,8 +31,11 @@ const uiSlice = createSlice({ toggleSidebar: (state) => { state.sidebarCollapsed = !state.sidebarCollapsed }, + setLocale: (state, action: PayloadAction) => { + state.locale = action.payload + }, }, }) -export const { setCurrentView, setSearchQuery, toggleSidebar } = uiSlice.actions +export const { setCurrentView, setSearchQuery, toggleSidebar, setLocale } = uiSlice.actions export default uiSlice.reducer