feat(localization): add and update translations for multiple languages
- Introduced new localization files for Albanian, German, Spanish, Portuguese, and Chinese, enhancing the application's multilingual support. - Added new keys and updated existing translations in project-view, task-list-table, and settings files to improve user experience across different languages. - Enhanced error handling and empty state messages in task management components to provide clearer feedback to users. - Updated tooltip texts and button labels for better clarity and consistency in the user interface.
This commit is contained in:
@@ -5,8 +5,15 @@ import { PushpinFilled, PushpinOutlined } from '@ant-design/icons';
|
||||
import { colors } from '../styles/colors';
|
||||
import { navRoutes, NavRoutesType } from '../features/navbar/navRoutes';
|
||||
|
||||
// Props type for the component
|
||||
type PinRouteToNavbarButtonProps = {
|
||||
name: string;
|
||||
path: string;
|
||||
adminOnly?: boolean;
|
||||
};
|
||||
|
||||
// this component pin the given path to navbar
|
||||
const PinRouteToNavbarButton = ({ name, path }: NavRoutesType) => {
|
||||
const PinRouteToNavbarButton = ({ name, path, adminOnly = false }: PinRouteToNavbarButtonProps) => {
|
||||
const navRoutesList: NavRoutesType[] = getJSONFromLocalStorage('navRoutes') || navRoutes;
|
||||
|
||||
const [isPinned, setIsPinned] = useState(
|
||||
@@ -18,7 +25,7 @@ const PinRouteToNavbarButton = ({ name, path }: NavRoutesType) => {
|
||||
const handlePinToNavbar = (name: string, path: string) => {
|
||||
let newNavRoutesList;
|
||||
|
||||
const route: NavRoutesType = { name, path };
|
||||
const route: NavRoutesType = { name, path, adminOnly };
|
||||
|
||||
if (isPinned) {
|
||||
newNavRoutesList = navRoutesList.filter(item => item.name !== route.name);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Divider, Form, Input, message, Modal, Typography } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import { editTeamName, fetchTeams } from '@/features/teams/teamSlice';
|
||||
import { ITeamGetResponse } from '@/types/teams/team.type';
|
||||
@@ -11,6 +12,7 @@ interface EditTeamNameModalProps {
|
||||
}
|
||||
|
||||
const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalProps) => {
|
||||
const { t } = useTranslation('settings/teams');
|
||||
const dispatch = useAppDispatch();
|
||||
const [form] = Form.useForm();
|
||||
const [updating, setUpdating] = useState(false);
|
||||
@@ -33,7 +35,7 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro
|
||||
}
|
||||
setUpdating(false);
|
||||
} catch (error) {
|
||||
message.error('Team name change failed!');
|
||||
message.error(t('updateFailed'));
|
||||
} finally {
|
||||
setUpdating(false);
|
||||
}
|
||||
@@ -49,13 +51,13 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
Edit Team Name
|
||||
{t('editTeamName')}
|
||||
<Divider />
|
||||
</Typography.Text>
|
||||
}
|
||||
open={isModalOpen}
|
||||
onOk={form.submit}
|
||||
okText="Update Name"
|
||||
okText={t('updateName')}
|
||||
onCancel={() => {
|
||||
onCancel();
|
||||
setUpdating(false);
|
||||
@@ -67,15 +69,15 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro
|
||||
<Form form={form} layout="vertical" onFinish={handleFormSubmit}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="Name"
|
||||
label={t('name')}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please enter a Name',
|
||||
message: t('nameRequired'),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="Name" />
|
||||
<Input placeholder={t('namePlaceholder')} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -65,7 +65,7 @@ const UpdateMemberDrawer = ({ selectedMemberId, onRoleUpdate }: UpdateMemberDraw
|
||||
setJobTitles(res.body.data || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching job titles:', error);
|
||||
logger.error('Error fetching job titles:', error);
|
||||
message.error(t('jobTitlesFetchError'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -154,7 +154,7 @@ const TaskDrawer = () => {
|
||||
onClick={handleAddTimeLog}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
Add new time log
|
||||
{t('taskTimeLogTab.addTimeLog')}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -519,7 +519,7 @@ const TaskListV2: React.FC = () => {
|
||||
|
||||
// Loading and error states
|
||||
if (loading || loadingColumns) return <Skeleton active />;
|
||||
if (error) return <div>Error: {error}</div>;
|
||||
if (error) return <div>{t('emptyStates.errorPrefix')} {error}</div>;
|
||||
|
||||
// Show message when no data
|
||||
if (groups.length === 0 && !loading) {
|
||||
@@ -531,10 +531,10 @@ const TaskListV2: React.FC = () => {
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
||||
No task groups found
|
||||
{t('emptyStates.noTaskGroups')}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Tasks will appear here when they are created or when filters are applied.
|
||||
{t('emptyStates.noTaskGroupsDescription')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -623,7 +623,7 @@ const TaskListV2: React.FC = () => {
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{allTasks.find(task => task.id === activeId)?.name ||
|
||||
allTasks.find(task => task.id === activeId)?.title ||
|
||||
'Task'}
|
||||
t('emptyStates.dragTaskFallback')}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{allTasks.find(task => task.id === activeId)?.task_key}
|
||||
|
||||
@@ -213,7 +213,7 @@ const useFilterData = (position: 'board' | 'list'): FilterSection[] => {
|
||||
return [
|
||||
{
|
||||
id: 'priority',
|
||||
label: 'Priority',
|
||||
label: t('priorityText'),
|
||||
options: filterData.priorities.map((p: any) => ({
|
||||
value: p.id,
|
||||
label: p.name,
|
||||
@@ -288,7 +288,7 @@ const useFilterData = (position: 'board' | 'list'): FilterSection[] => {
|
||||
return [
|
||||
{
|
||||
id: 'priority',
|
||||
label: 'Priority',
|
||||
label: t('priorityText'),
|
||||
options: filterData.priorities.map((p: any) => ({
|
||||
value: p.id,
|
||||
label: p.name,
|
||||
@@ -719,7 +719,34 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
|
||||
isDarkMode,
|
||||
}) => {
|
||||
const { t } = useTranslation('task-list-filters');
|
||||
const { t: tTable } = useTranslation('task-list-table');
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Helper function to get translated field label using existing task-list-table translations
|
||||
const getFieldLabel = useCallback((fieldKey: string) => {
|
||||
const keyMappings: Record<string, string> = {
|
||||
'KEY': 'keyColumn',
|
||||
'DESCRIPTION': 'descriptionColumn',
|
||||
'PROGRESS': 'progressColumn',
|
||||
'ASSIGNEES': 'assigneesColumn',
|
||||
'LABELS': 'labelsColumn',
|
||||
'PHASE': 'phaseColumn',
|
||||
'STATUS': 'statusColumn',
|
||||
'PRIORITY': 'priorityColumn',
|
||||
'TIME_TRACKING': 'timeTrackingColumn',
|
||||
'ESTIMATION': 'estimationColumn',
|
||||
'START_DATE': 'startDateColumn',
|
||||
'DUE_DATE': 'dueDateColumn',
|
||||
'DUE_TIME': 'dueTimeColumn',
|
||||
'COMPLETED_DATE': 'completedDateColumn',
|
||||
'CREATED_DATE': 'createdDateColumn',
|
||||
'LAST_UPDATED': 'lastUpdatedColumn',
|
||||
'REPORTER': 'reporterColumn',
|
||||
};
|
||||
|
||||
const translationKey = keyMappings[fieldKey];
|
||||
return translationKey ? tTable(translationKey) : fieldKey;
|
||||
}, [tTable]);
|
||||
const fieldsRaw = useSelector((state: RootState) => state.taskManagementFields);
|
||||
const columns = useSelector(selectColumns);
|
||||
const projectId = useAppSelector(state => state.projectReducer.projectId);
|
||||
@@ -857,7 +884,7 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
|
||||
|
||||
{/* Label and Count */}
|
||||
<div className="flex-1 flex items-center justify-between">
|
||||
<span className="truncate">{field.label}</span>
|
||||
<span className="truncate">{getFieldLabel(field.key)}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user