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:
chamikaJ
2025-07-08 15:26:55 +05:30
parent e750023fdc
commit f06851fa37
53 changed files with 700 additions and 117 deletions

View File

@@ -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);

View File

@@ -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>

View File

@@ -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);

View File

@@ -154,7 +154,7 @@ const TaskDrawer = () => {
onClick={handleAddTimeLog}
style={{ width: '100%' }}
>
Add new time log
{t('taskTimeLogTab.addTimeLog')}
</Button>
</Flex>
);

View File

@@ -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}

View File

@@ -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>
);