feat(localization): improve translation handling and update UI labels
- Updated the 'board' label in project-view translations for consistency. - Enhanced the getTabLabel function to include fallback labels for better user experience when translations are not available. - Implemented translation loading checks in ProjectView to ensure labels are updated correctly based on the selected language. - Refactored tab label updates to handle translation loading errors gracefully.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"taskList": "Task List",
|
"taskList": "Task List",
|
||||||
"board": "Kanban Board",
|
"board": "Board",
|
||||||
"insights": "Insights",
|
"insights": "Insights",
|
||||||
"files": "Files",
|
"files": "Files",
|
||||||
"members": "Members",
|
"members": "Members",
|
||||||
|
|||||||
@@ -29,9 +29,36 @@ type TabItems = {
|
|||||||
element: ReactNode;
|
element: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to get translated labels
|
// Function to get translated labels with fallback
|
||||||
const getTabLabel = (key: string): string => {
|
const getTabLabel = (key: string): string => {
|
||||||
return i18n.t(`project-view:${key}`);
|
try {
|
||||||
|
const translated = i18n.t(`project-view:${key}`);
|
||||||
|
// If translation is not loaded, it returns the key back, so we provide fallbacks
|
||||||
|
if (translated === `project-view:${key}` || translated === key) {
|
||||||
|
// Provide fallback labels
|
||||||
|
const fallbacks: Record<string, string> = {
|
||||||
|
taskList: 'Task List',
|
||||||
|
board: 'Board',
|
||||||
|
insights: 'Insights',
|
||||||
|
files: 'Files',
|
||||||
|
members: 'Members',
|
||||||
|
updates: 'Updates',
|
||||||
|
};
|
||||||
|
return fallbacks[key] || key;
|
||||||
|
}
|
||||||
|
return translated;
|
||||||
|
} catch (error) {
|
||||||
|
// Fallback labels in case of any error
|
||||||
|
const fallbacks: Record<string, string> = {
|
||||||
|
taskList: 'Task List',
|
||||||
|
board: 'Board',
|
||||||
|
insights: 'Insights',
|
||||||
|
files: 'Files',
|
||||||
|
members: 'Members',
|
||||||
|
updates: 'Updates',
|
||||||
|
};
|
||||||
|
return fallbacks[key] || key;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// settings all element items use for tabs
|
// settings all element items use for tabs
|
||||||
@@ -94,26 +121,30 @@ export const tabItems: TabItems[] = [
|
|||||||
|
|
||||||
// Function to update tab labels when language changes
|
// Function to update tab labels when language changes
|
||||||
export const updateTabLabels = () => {
|
export const updateTabLabels = () => {
|
||||||
tabItems.forEach(item => {
|
try {
|
||||||
switch (item.key) {
|
tabItems.forEach(item => {
|
||||||
case 'tasks-list':
|
switch (item.key) {
|
||||||
item.label = getTabLabel('taskList');
|
case 'tasks-list':
|
||||||
break;
|
item.label = getTabLabel('taskList');
|
||||||
case 'board':
|
break;
|
||||||
item.label = getTabLabel('board');
|
case 'board':
|
||||||
break;
|
item.label = getTabLabel('board');
|
||||||
case 'project-insights-member-overview':
|
break;
|
||||||
item.label = getTabLabel('insights');
|
case 'project-insights-member-overview':
|
||||||
break;
|
item.label = getTabLabel('insights');
|
||||||
case 'all-attachments':
|
break;
|
||||||
item.label = getTabLabel('files');
|
case 'all-attachments':
|
||||||
break;
|
item.label = getTabLabel('files');
|
||||||
case 'members':
|
break;
|
||||||
item.label = getTabLabel('members');
|
case 'members':
|
||||||
break;
|
item.label = getTabLabel('members');
|
||||||
case 'updates':
|
break;
|
||||||
item.label = getTabLabel('updates');
|
case 'updates':
|
||||||
break;
|
item.label = getTabLabel('updates');
|
||||||
}
|
break;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating tab labels:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
ConfigProvider,
|
ConfigProvider,
|
||||||
Flex,
|
Flex,
|
||||||
Tooltip,
|
|
||||||
Badge,
|
|
||||||
Tabs,
|
Tabs,
|
||||||
PushpinFilled,
|
PushpinFilled,
|
||||||
PushpinOutlined,
|
PushpinOutlined,
|
||||||
@@ -20,7 +18,6 @@ import { useAppSelector } from '@/hooks/useAppSelector';
|
|||||||
import { getProject, setProjectId, setProjectView } from '@/features/project/project.slice';
|
import { getProject, setProjectId, setProjectView } from '@/features/project/project.slice';
|
||||||
import { fetchStatuses, resetStatuses } from '@/features/taskAttributes/taskStatusSlice';
|
import { fetchStatuses, resetStatuses } from '@/features/taskAttributes/taskStatusSlice';
|
||||||
import { projectsApiService } from '@/api/projects/projects.api.service';
|
import { projectsApiService } from '@/api/projects/projects.api.service';
|
||||||
import { colors } from '@/styles/colors';
|
|
||||||
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
||||||
import ProjectViewHeader from './project-view-header';
|
import ProjectViewHeader from './project-view-header';
|
||||||
import './project-view.css';
|
import './project-view.css';
|
||||||
@@ -42,6 +39,7 @@ import { resetState as resetEnhancedKanbanState } from '@/features/enhanced-kanb
|
|||||||
import { setProjectId as setInsightsProjectId } from '@/features/projects/insights/project-insights.slice';
|
import { setProjectId as setInsightsProjectId } from '@/features/projects/insights/project-insights.slice';
|
||||||
import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
|
import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ensureTranslationsLoaded } from '@/i18n';
|
||||||
|
|
||||||
// Import critical components synchronously to avoid suspense interruptions
|
// Import critical components synchronously to avoid suspense interruptions
|
||||||
import TaskDrawer from '@components/task-drawer/task-drawer';
|
import TaskDrawer from '@components/task-drawer/task-drawer';
|
||||||
@@ -64,12 +62,15 @@ const ProjectView = React.memo(() => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { t } = useTranslation('project-view');
|
const { t, i18n } = useTranslation('project-view');
|
||||||
|
|
||||||
// Memoized selectors to prevent unnecessary re-renders
|
// Memoized selectors to prevent unnecessary re-renders
|
||||||
const selectedProject = useAppSelector(state => state.projectReducer.project);
|
const selectedProject = useAppSelector(state => state.projectReducer.project);
|
||||||
const projectLoading = useAppSelector(state => state.projectReducer.projectLoading);
|
const projectLoading = useAppSelector(state => state.projectReducer.projectLoading);
|
||||||
|
|
||||||
|
// State to track translation loading
|
||||||
|
const [translationsReady, setTranslationsReady] = useState(false);
|
||||||
|
|
||||||
// Optimize document title updates
|
// Optimize document title updates
|
||||||
useDocumentTitle(selectedProject?.name || t('projectView'));
|
useDocumentTitle(selectedProject?.name || t('projectView'));
|
||||||
|
|
||||||
@@ -95,6 +96,30 @@ const ProjectView = React.memo(() => {
|
|||||||
setTaskId(urlParams.taskId);
|
setTaskId(urlParams.taskId);
|
||||||
}, [urlParams]);
|
}, [urlParams]);
|
||||||
|
|
||||||
|
// Ensure translations are loaded for project-view namespace
|
||||||
|
useEffect(() => {
|
||||||
|
const loadTranslations = async () => {
|
||||||
|
try {
|
||||||
|
await ensureTranslationsLoaded(['project-view'], [i18n.language]);
|
||||||
|
updateTabLabels();
|
||||||
|
setTranslationsReady(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load project-view translations:', error);
|
||||||
|
// Set ready to true anyway to prevent infinite loading
|
||||||
|
setTranslationsReady(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadTranslations();
|
||||||
|
}, [i18n.language]);
|
||||||
|
|
||||||
|
// Update tab labels when language changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (translationsReady) {
|
||||||
|
updateTabLabels();
|
||||||
|
}
|
||||||
|
}, [t, translationsReady]);
|
||||||
|
|
||||||
// Comprehensive cleanup function for when leaving project view entirely
|
// Comprehensive cleanup function for when leaving project view entirely
|
||||||
const resetAllProjectData = useCallback(() => {
|
const resetAllProjectData = useCallback(() => {
|
||||||
dispatch(setProjectId(null));
|
dispatch(setProjectId(null));
|
||||||
@@ -176,11 +201,6 @@ const ProjectView = React.memo(() => {
|
|||||||
setIsInitialized(false);
|
setIsInitialized(false);
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
// Update tab labels when language changes
|
|
||||||
useEffect(() => {
|
|
||||||
updateTabLabels();
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
// Effect for handling task drawer opening from URL params
|
// Effect for handling task drawer opening from URL params
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (taskid && isInitialized) {
|
if (taskid && isInitialized) {
|
||||||
@@ -250,6 +270,11 @@ const ProjectView = React.memo(() => {
|
|||||||
|
|
||||||
// Memoized tab menu items with enhanced styling
|
// Memoized tab menu items with enhanced styling
|
||||||
const tabMenuItems = useMemo(() => {
|
const tabMenuItems = useMemo(() => {
|
||||||
|
// Only render tabs when translations are ready
|
||||||
|
if (!translationsReady) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const menuItems = tabItems.map(item => ({
|
const menuItems = tabItems.map(item => ({
|
||||||
key: item.key,
|
key: item.key,
|
||||||
label: (
|
label: (
|
||||||
@@ -304,7 +329,7 @@ const ProjectView = React.memo(() => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return menuItems;
|
return menuItems;
|
||||||
}, [pinnedTab, pinToDefaultTab, t]);
|
}, [pinnedTab, pinToDefaultTab, t, translationsReady]);
|
||||||
|
|
||||||
// Optimized secondary components loading with better UX
|
// Optimized secondary components loading with better UX
|
||||||
const [shouldLoadSecondaryComponents, setShouldLoadSecondaryComponents] = useState(false);
|
const [shouldLoadSecondaryComponents, setShouldLoadSecondaryComponents] = useState(false);
|
||||||
@@ -341,8 +366,8 @@ const ProjectView = React.memo(() => {
|
|||||||
[shouldLoadSecondaryComponents]
|
[shouldLoadSecondaryComponents]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Show loading state while project is being fetched
|
// Show loading state while project is being fetched or translations are loading
|
||||||
if (projectLoading || !isInitialized) {
|
if (projectLoading || !isInitialized || !translationsReady) {
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBlockStart: 70, marginBlockEnd: 12, minHeight: '80vh' }}>
|
<div style={{ marginBlockStart: 70, marginBlockEnd: 12, minHeight: '80vh' }}>
|
||||||
<SuspenseFallback />
|
<SuspenseFallback />
|
||||||
|
|||||||
Reference in New Issue
Block a user