diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts
index 2b0b5d9..ed33472 100644
--- a/dictation_client/src/api/api.ts
+++ b/dictation_client/src/api/api.ts
@@ -361,72 +361,72 @@ export interface GetLicenseSummaryRequest {
* @interface GetLicenseSummaryResponse
*/
export interface GetLicenseSummaryResponse {
- /**
- *
- * @type {LicenseSummaryInfo}
- * @memberof GetLicenseSummaryResponse
- */
- totalLicense: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- allocatedLicense: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- reusableLicense: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- freeLicense: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- expiringWithin14daysLicense: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- issueRequesting: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- numberOfRequesting: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- shortage: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- storageSize: number;
- /**
- *
- * @type {number}
- * @memberof GetLicenseSummaryResponse
- */
- usedSize: number;
- /**
- *
- * @type {boolean}
- * @memberof GetLicenseSummaryResponse
- */
- isAccountLock: boolean;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'totalLicense': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'allocatedLicense': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'reusableLicense': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'freeLicense': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'expiringWithin14daysLicense': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'issueRequesting': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'numberOfRequesting': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'shortage': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'storageSize': number;
+ /**
+ *
+ * @type {number}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'usedSize': number;
+ /**
+ *
+ * @type {boolean}
+ * @memberof GetLicenseSummaryResponse
+ */
+ 'isAccountLock': boolean;
}
/**
*
diff --git a/dictation_client/src/common/errors/code.ts b/dictation_client/src/common/errors/code.ts
index c296113..da8a766 100644
--- a/dictation_client/src/common/errors/code.ts
+++ b/dictation_client/src/common/errors/code.ts
@@ -25,4 +25,5 @@ export const errorCodes = [
"E010301", // メールアドレス登録済みエラー
"E010302", // authorId重複エラー
"E010401", // PONumber重複エラー
+ "E010601", // タスク変更不可エラー
] as const;
diff --git a/dictation_client/src/features/auth/utils.ts b/dictation_client/src/features/auth/utils.ts
index b134894..9b28f49 100644
--- a/dictation_client/src/features/auth/utils.ts
+++ b/dictation_client/src/features/auth/utils.ts
@@ -1,6 +1,6 @@
import { ConfigurationParameters } from "api";
import { decodeToken } from "../../common/decodeToken";
-import { ADMIN_ROLES } from "../../components/auth/constants";
+import { ADMIN_ROLES, USER_ROLES } from "../../components/auth/constants";
/**
* Get access token
@@ -66,3 +66,16 @@ export const isAdminUser = (): boolean => {
}
return token.role.includes(ADMIN_ROLES.ADMIN);
};
+
+/**
+ * is author user ログインしているユーザがAuthorかどうかを返す
+ * @returns bool
+ */
+export const isAuthorUser = (): boolean => {
+ const jwt = loadAccessToken();
+ const token = jwt ? decodeToken(jwt) : null;
+ if (!token) {
+ return false;
+ }
+ return token.role.includes(USER_ROLES.AUTHOR);
+};
diff --git a/dictation_client/src/features/dictation/dictationSlice.ts b/dictation_client/src/features/dictation/dictationSlice.ts
index 31a4f55..009d47f 100644
--- a/dictation_client/src/features/dictation/dictationSlice.ts
+++ b/dictation_client/src/features/dictation/dictationSlice.ts
@@ -1,6 +1,12 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
+import { Assignee, Task } from "api/api";
import { DictationState } from "./state";
-import { getSortColumnAsync, listTasksAsync } from "./operations";
+import {
+ getSortColumnAsync,
+ listTasksAsync,
+ listTypistGroupsAsync,
+ listTypistsAsync,
+} from "./operations";
import {
SORTABLE_COLUMN,
DIRECTION,
@@ -17,11 +23,18 @@ const initialState: DictationState = {
offset: 0,
total: 0,
tasks: [],
+ typists: [],
+ typistGroups: [],
},
apps: {
displayInfo: INIT_DISPLAY_INFO,
direction: DIRECTION.ASC,
paramName: SORTABLE_COLUMN.JobNumber,
+ selectedTask: undefined,
+ assignee: {
+ selected: [],
+ pool: [],
+ },
},
};
@@ -50,6 +63,37 @@ export const dictationSlice = createSlice({
const { paramName } = action.payload;
state.apps.paramName = paramName;
},
+ changeSelectedTask: (state, action: PayloadAction<{ task: Task }>) => {
+ const { task } = action.payload;
+ state.apps.selectedTask = task;
+ },
+ changeAssignee: (
+ state,
+ action: PayloadAction<{ selected: Assignee[] }>
+ ) => {
+ const { selected } = action.payload;
+
+ const typists = state.domain.typists.map(
+ (x) => ({ typistUserId: x.id, typistName: x.name } as Assignee)
+ );
+ const typistGroups = state.domain.typistGroups.map(
+ (x) => ({ typistGroupId: x.id, typistName: x.name } as Assignee)
+ );
+
+ const transcriptionists = [...typists, ...typistGroups];
+
+ const pool = transcriptionists.filter(
+ (x) =>
+ selected.find(
+ (assign) =>
+ assign.typistGroupId === x.typistGroupId &&
+ assign.typistUserId === x.typistUserId
+ ) === undefined
+ );
+
+ state.apps.assignee.selected = selected;
+ state.apps.assignee.pool = pool;
+ },
},
extraReducers: (builder) => {
builder.addCase(listTasksAsync.fulfilled, (state, action) => {
@@ -62,10 +106,21 @@ export const dictationSlice = createSlice({
state.apps.direction = action.payload.direction;
state.apps.paramName = action.payload.paramName;
});
+ builder.addCase(listTypistsAsync.fulfilled, (state, action) => {
+ state.domain.typists = action.payload.typists;
+ });
+ builder.addCase(listTypistGroupsAsync.fulfilled, (state, action) => {
+ state.domain.typistGroups = action.payload.typistGroups;
+ });
},
});
-export const { changeDisplayInfo, changeDirection, changeParamName } =
- dictationSlice.actions;
+export const {
+ changeDisplayInfo,
+ changeDirection,
+ changeParamName,
+ changeSelectedTask,
+ changeAssignee,
+} = dictationSlice.actions;
export default dictationSlice.reducer;
diff --git a/dictation_client/src/features/dictation/operations.ts b/dictation_client/src/features/dictation/operations.ts
index c4bddbf..18963cc 100644
--- a/dictation_client/src/features/dictation/operations.ts
+++ b/dictation_client/src/features/dictation/operations.ts
@@ -2,7 +2,15 @@ import { createAsyncThunk } from "@reduxjs/toolkit";
import type { RootState } from "app/store";
import { getTranslationID } from "translation";
import { openSnackbar } from "features/ui/uiSlice";
-import { TasksResponse, TasksApi, UsersApi } from "../../api/api";
+import {
+ TasksResponse,
+ TasksApi,
+ UsersApi,
+ AccountsApi,
+ GetTypistsResponse,
+ GetTypistGroupsResponse,
+ Assignee,
+} from "../../api/api";
import { Configuration } from "../../api/configuration";
import { ErrorObject, createErrorObject } from "../../common/errors";
import {
@@ -162,3 +170,133 @@ export const updateSortColumnAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
+
+export const listTypistsAsync = createAsyncThunk<
+ GetTypistsResponse,
+ void,
+ {
+ // rejectした時の返却値の型
+ rejectValue: {
+ error: ErrorObject;
+ };
+ }
+>("dictations/listTypistsAsync", async (args, thunkApi) => {
+ // apiのConfigurationを取得する
+ const { getState } = thunkApi;
+ const state = getState() as RootState;
+ const { configuration, accessToken } = state.auth;
+ const config = new Configuration(configuration);
+ const accountsApi = new AccountsApi(config);
+
+ try {
+ const typists = await accountsApi.getTypists({
+ headers: { authorization: `Bearer ${accessToken}` },
+ });
+
+ return typists.data;
+ } catch (e) {
+ // e ⇒ errorObjectに変換"
+ const error = createErrorObject(e);
+ thunkApi.dispatch(
+ openSnackbar({
+ level: "error",
+ message: getTranslationID("common.message.internalServerError"),
+ })
+ );
+ return thunkApi.rejectWithValue({ error });
+ }
+});
+
+export const listTypistGroupsAsync = createAsyncThunk<
+ GetTypistGroupsResponse,
+ void,
+ {
+ // rejectした時の返却値の型
+ rejectValue: {
+ error: ErrorObject;
+ };
+ }
+>("dictations/listTypistGroupsAsync", async (args, thunkApi) => {
+ // apiのConfigurationを取得する
+ const { getState } = thunkApi;
+ const state = getState() as RootState;
+ const { configuration, accessToken } = state.auth;
+ const config = new Configuration(configuration);
+ const accountsApi = new AccountsApi(config);
+
+ try {
+ const typistGroup = await accountsApi.getTypistGroups({
+ headers: { authorization: `Bearer ${accessToken}` },
+ });
+
+ return typistGroup.data;
+ } catch (e) {
+ // e ⇒ errorObjectに変換"
+ const error = createErrorObject(e);
+ thunkApi.dispatch(
+ openSnackbar({
+ level: "error",
+ message: getTranslationID("common.message.internalServerError"),
+ })
+ );
+ return thunkApi.rejectWithValue({ error });
+ }
+});
+
+export const updateAssigneeAsync = createAsyncThunk<
+ {
+ /** empty */
+ },
+ {
+ audioFileId: number;
+ assignees: Assignee[];
+ },
+ {
+ // rejectした時の返却値の型
+ rejectValue: {
+ error: ErrorObject;
+ };
+ }
+>("dictations/updateAssigneeAsync", async (args, thunkApi) => {
+ const { audioFileId, assignees } = args;
+
+ // apiのConfigurationを取得する
+ const { getState } = thunkApi;
+ const state = getState() as RootState;
+ const { configuration, accessToken } = state.auth;
+ const config = new Configuration(configuration);
+ const tasksApi = new TasksApi(config);
+
+ try {
+ await tasksApi.changeCheckoutPermission(
+ audioFileId,
+ { assignees },
+ {
+ headers: { authorization: `Bearer ${accessToken}` },
+ }
+ );
+ return {};
+ } catch (e) {
+ // e ⇒ errorObjectに変換"
+ const error = createErrorObject(e);
+
+ // ステータスがUploaded以外、タスクが存在しない場合
+ if (error.code === "E010601") {
+ thunkApi.dispatch(
+ openSnackbar({
+ level: "error",
+ message: getTranslationID("dictationPage.message.taskNotEditable"),
+ })
+ );
+ return thunkApi.rejectWithValue({ error });
+ }
+
+ thunkApi.dispatch(
+ openSnackbar({
+ level: "error",
+ message: getTranslationID("common.message.internalServerError"),
+ })
+ );
+ return thunkApi.rejectWithValue({ error });
+ }
+});
diff --git a/dictation_client/src/features/dictation/selectors.ts b/dictation_client/src/features/dictation/selectors.ts
index 7524a2f..455edd0 100644
--- a/dictation_client/src/features/dictation/selectors.ts
+++ b/dictation_client/src/features/dictation/selectors.ts
@@ -29,3 +29,12 @@ export const selectDirection = (state: RootState) =>
export const selectParamName = (state: RootState) =>
state.dictation.apps.paramName;
+
+export const selectSelectedTask = (state: RootState) =>
+ state.dictation.apps.selectedTask;
+
+export const selectSelectedTranscriptionists = (state: RootState) =>
+ state.dictation.apps.assignee.selected;
+
+export const selectPoolTranscriptionists = (state: RootState) =>
+ state.dictation.apps.assignee.pool;
diff --git a/dictation_client/src/features/dictation/state.ts b/dictation_client/src/features/dictation/state.ts
index e12f8da..244b2ad 100644
--- a/dictation_client/src/features/dictation/state.ts
+++ b/dictation_client/src/features/dictation/state.ts
@@ -1,4 +1,4 @@
-import { Task } from "../../api/api";
+import { Task, Assignee, Typist, TypistGroup } from "../../api/api";
import {
DirectionType,
DisplayInfoType,
@@ -15,10 +15,17 @@ export interface Domain {
offset: number;
total: number;
tasks: Task[];
+ typists: Typist[];
+ typistGroups: TypistGroup[];
}
export interface Apps {
displayInfo: DisplayInfoType;
direction: DirectionType;
paramName: SortableColumnType;
+ selectedTask?: Task;
+ assignee: {
+ selected: Assignee[];
+ pool: Assignee[];
+ };
}
diff --git a/dictation_client/src/pages/DictationPage/changeTranscriptionistPopup.tsx b/dictation_client/src/pages/DictationPage/changeTranscriptionistPopup.tsx
new file mode 100644
index 0000000..3ebc642
--- /dev/null
+++ b/dictation_client/src/pages/DictationPage/changeTranscriptionistPopup.tsx
@@ -0,0 +1,194 @@
+import React, { useCallback } from "react";
+import styles from "styles/app.module.scss";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ changeAssignee,
+ selectPoolTranscriptionists,
+ selectSelectedTask,
+ selectSelectedTranscriptionists,
+ updateAssigneeAsync,
+} from "features/dictation";
+import { Assignee } from "api";
+import { AppDispatch } from "app/store";
+import { getTranslationID } from "translation";
+import { useTranslation } from "react-i18next";
+import close from "../../assets/images/close.svg";
+
+interface ChangeTranscriptionistPopupProps {
+ onClose: (isChanged: boolean) => void;
+ isOpen: boolean;
+}
+
+export const ChangeTranscriptionistPopup: React.FC<
+ ChangeTranscriptionistPopupProps
+> = (props) => {
+ const { onClose, isOpen } = props;
+ const dispatch: AppDispatch = useDispatch();
+ const [t] = useTranslation();
+
+ // ポップアップを閉じる処理
+ const closePopup = useCallback(() => {
+ onClose(false);
+ }, [onClose]);
+
+ const selectedTask = useSelector(selectSelectedTask);
+ const selectedTranscriptionists = useSelector(
+ selectSelectedTranscriptionists
+ );
+ const poolTranscriptionists = useSelector(selectPoolTranscriptionists);
+
+ const removeAssignee = useCallback(
+ (assignee: Assignee) => {
+ dispatch(
+ changeAssignee({
+ selected: selectedTranscriptionists.filter(
+ (x) =>
+ x.typistGroupId !== assignee.typistGroupId ||
+ x.typistUserId !== assignee.typistUserId
+ ),
+ })
+ );
+ },
+ [dispatch, selectedTranscriptionists]
+ );
+
+ const addAssignee = useCallback(
+ (assignee: Assignee) => {
+ dispatch(
+ changeAssignee({ selected: [...selectedTranscriptionists, assignee] })
+ );
+ },
+ [dispatch, selectedTranscriptionists]
+ );
+
+ const onChangeTranscriptionist = useCallback(async () => {
+ // ダイアログ確認
+ if (
+ !selectedTask ||
+ /* eslint-disable-next-line no-alert */
+ !window.confirm(t(getTranslationID("common.message.dialogConfirm")))
+ ) {
+ return;
+ }
+ const { meta } = await dispatch(
+ updateAssigneeAsync({
+ audioFileId: selectedTask.audioFileId,
+ assignees: selectedTranscriptionists,
+ })
+ );
+
+ if (meta.requestStatus === "fulfilled") {
+ onClose(true);
+ }
+ }, [dispatch, selectedTranscriptionists, selectedTask, onClose, t]);
+
+ return (
+
+
+
+ {t(getTranslationID("dictationPage.label.changeTranscriptionist"))}
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions */}
+
+
+
+
+
+ );
+};
diff --git a/dictation_client/src/pages/DictationPage/index.tsx b/dictation_client/src/pages/DictationPage/index.tsx
index 04b4f0d..095e082 100644
--- a/dictation_client/src/pages/DictationPage/index.tsx
+++ b/dictation_client/src/pages/DictationPage/index.tsx
@@ -22,10 +22,16 @@ import {
selectDirection,
changeParamName,
changeDirection,
+ changeSelectedTask,
updateSortColumnAsync,
SortableColumnType,
+ changeAssignee,
+ listTypistsAsync,
+ listTypistGroupsAsync,
} from "features/dictation";
import { getTranslationID } from "translation";
+import { Task } from "api/api";
+import { isAdminUser, isAuthorUser } from "features/auth/utils";
import { STATUS, LIMIT_TASK_NUM } from "../../features/dictation";
import uploaded from "../../assets/images/uploaded.svg";
import pending from "../../assets/images/pending.svg";
@@ -34,11 +40,29 @@ import finished from "../../assets/images/finished.svg";
import backup from "../../assets/images/backup.svg";
import lock from "../../assets/images/lock.svg";
import { DisPlayInfo } from "./displayInfo";
+import { ChangeTranscriptionistPopup } from "./changeTranscriptionistPopup";
const DictationPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
const [t] = useTranslation();
+ const isAdmin = isAdminUser();
+ const isAuthor = isAuthorUser();
+ // popup制御関係
+ const [
+ isChangeTranscriptionistPopupOpen,
+ setIsChangeTranscriptionistPopupOpen,
+ ] = useState(false);
+
+ const onChangeTranscriptionistPopupOpen = useCallback(
+ (task: Task) => {
+ dispatch(changeAssignee({ selected: task.assignees }));
+ dispatch(changeSelectedTask({ task }));
+ setIsChangeTranscriptionistPopupOpen(true);
+ },
+ [dispatch, setIsChangeTranscriptionistPopupOpen]
+ );
+
// 各カラムの表示/非表示
const displayColumn = useSelector(selectDisplayInfo);
@@ -76,6 +100,8 @@ const DictationPage: React.FC = (): JSX.Element => {
paramName: sortableParamName,
})
);
+ dispatch(listTypistsAsync());
+ dispatch(listTypistGroupsAsync());
}, [
dispatch,
filterUploaded,
@@ -105,6 +131,8 @@ const DictationPage: React.FC = (): JSX.Element => {
paramName: sortableParamName,
})
);
+ dispatch(listTypistsAsync());
+ dispatch(listTypistGroupsAsync());
}, [
dispatch,
totalPage,
@@ -135,6 +163,8 @@ const DictationPage: React.FC = (): JSX.Element => {
paramName: sortableParamName,
})
);
+ dispatch(listTypistsAsync());
+ dispatch(listTypistGroupsAsync());
}, [
dispatch,
currentPage,
@@ -165,6 +195,8 @@ const DictationPage: React.FC = (): JSX.Element => {
paramName: sortableParamName,
})
);
+ dispatch(listTypistsAsync());
+ dispatch(listTypistGroupsAsync());
}, [
dispatch,
currentPage,
@@ -203,6 +235,8 @@ const DictationPage: React.FC = (): JSX.Element => {
paramName,
})
);
+ dispatch(listTypistsAsync());
+ dispatch(listTypistGroupsAsync());
},
[
dispatch,
@@ -271,6 +305,8 @@ const DictationPage: React.FC = (): JSX.Element => {
paramName: sortableParamName,
})
);
+ dispatch(listTypistsAsync());
+ dispatch(listTypistGroupsAsync());
},
[dispatch, sortDirection, sortableParamName]
);
@@ -284,6 +320,40 @@ const DictationPage: React.FC = (): JSX.Element => {
);
}, [dispatch, sortDirection, sortableParamName]);
+ const onClosePopup = useCallback(
+ (isChanged: boolean) => {
+ if (isChanged) {
+ const filter = getFilter(
+ filterUploaded,
+ filterInProgress,
+ filterPending,
+ filterFinished,
+ filterBackup
+ );
+ dispatch(
+ listTasksAsync({
+ limit: LIMIT_TASK_NUM,
+ offset: 0,
+ filter,
+ direction: sortDirection,
+ paramName: sortableParamName,
+ })
+ );
+ }
+ setIsChangeTranscriptionistPopupOpen(false);
+ },
+ [
+ dispatch,
+ filterUploaded,
+ filterInProgress,
+ filterPending,
+ filterFinished,
+ filterBackup,
+ sortDirection,
+ sortableParamName,
+ ]
+ );
+
// 初回読み込み処理
useEffect(() => {
(async () => {
@@ -316,653 +386,707 @@ const DictationPage: React.FC = (): JSX.Element => {
paramName,
})
);
+ dispatch(listTypistsAsync());
+ dispatch(listTypistGroupsAsync());
}
})();
}, [dispatch]);
return (
-
-
-
-
-
-
- {t(getTranslationID("dictationPage.label.title"))}
-
-
-
-
-
-
-
- - {t(getTranslationID("dictationPage.label.filter"))}:
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
-
-
-
-
-
- | {/** th is empty */} |
- {displayColumn.JobNumber && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.JobNumber)
- }
- >
- {t(getTranslationID("dictationPage.label.jobNumber"))}
-
- |
- )}
- {displayColumn.Status && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.Status)
- }
- >
- {t(getTranslationID("dictationPage.label.status"))}
-
- |
- )}
- {displayColumn.Priority && (
- Priority |
- )}
- {displayColumn.Encryption && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.Encryption)
- }
- >
- {t(
- getTranslationID("dictationPage.label.encryption")
- )}
-
- |
- )}
- {displayColumn.AuthorId && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.AuthorId)
- }
- >
- {t(getTranslationID("dictationPage.label.authorId"))}
-
- |
- )}
- {displayColumn.WorkType && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.WorkType)
- }
- >
- {t(getTranslationID("dictationPage.label.workType"))}
-
- |
- )}
- {displayColumn.FileName && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.FileName)
- }
- >
- {t(getTranslationID("dictationPage.label.fileName"))}
-
- |
- )}
- {displayColumn.FileLength && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.FileLength)
- }
- >
- {t(
- getTranslationID("dictationPage.label.fileLength")
- )}
-
- |
- )}
- {displayColumn.FileSize && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.FileSize)
- }
- >
- {t(getTranslationID("dictationPage.label.fileSize"))}
-
- |
- )}
- {displayColumn.RecordingStartedDate && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(
- SORTABLE_COLUMN.RecordingStartedDate
- )
- }
- >
- {t(
- getTranslationID(
- "dictationPage.label.recordingStartedDate"
- )
- )}
-
- |
- )}
- {displayColumn.RecordingFinishedDate && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(
- SORTABLE_COLUMN.RecordingFinishedDate
- )
- }
- >
- {t(
- getTranslationID(
- "dictationPage.label.recordingFinishedDate"
- )
- )}
-
- |
- )}
- {displayColumn.UploadDate && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(SORTABLE_COLUMN.UploadDate)
- }
- >
- {t(
- getTranslationID("dictationPage.label.uploadDate")
- )}
-
- |
- )}
- {displayColumn.TranscriptionStartedDate && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(
- SORTABLE_COLUMN.TranscriptionStartedDate
- )
- }
- >
- {t(
- getTranslationID(
- "dictationPage.label.transcriptionStartedDate"
- )
- )}
-
- |
- )}
- {displayColumn.TranscriptionFinishedDate && (
-
- {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
-
- updateSortColumn(
- SORTABLE_COLUMN.TranscriptionFinishedDate
- )
- }
- >
- {t(
- getTranslationID(
- "dictationPage.label.transcriptionFinishedDate"
- )
- )}
-
- |
- )}
- {displayColumn.Transcriptionist && (
-
- {t(
- getTranslationID(
- "dictationPage.label.transcriptionist"
- )
- )}
- |
- )}
- {displayColumn.Comment && (
-
- {t(getTranslationID("dictationPage.label.comment"))}
- |
- )}
- {displayColumn.OptionItem1 && (
-
- {t(getTranslationID("dictationPage.label.optionItem1"))}
- |
- )}
- {displayColumn.OptionItem2 && (
-
- {t(getTranslationID("dictationPage.label.optionItem2"))}
- |
- )}
- {displayColumn.OptionItem3 && (
-
- {t(getTranslationID("dictationPage.label.optionItem3"))}
- |
- )}
- {displayColumn.OptionItem4 && (
-
- {t(getTranslationID("dictationPage.label.optionItem4"))}
- |
- )}
- {displayColumn.OptionItem5 && (
-
- {t(getTranslationID("dictationPage.label.optionItem5"))}
- |
- )}
- {displayColumn.OptionItem6 && (
-
- {t(getTranslationID("dictationPage.label.optionItem6"))}
- |
- )}
- {displayColumn.OptionItem7 && (
-
- {t(getTranslationID("dictationPage.label.optionItem7"))}
- |
- )}
- {displayColumn.OptionItem8 && (
-
- {t(getTranslationID("dictationPage.label.optionItem8"))}
- |
- )}
- {displayColumn.OptionItem9 && (
-
- {t(getTranslationID("dictationPage.label.optionItem9"))}
- |
- )}
- {displayColumn.OptionItem10 && (
-
- {t(
- getTranslationID("dictationPage.label.optionItem10")
- )}
- |
- )}
-
- {tasks.length !== 0 &&
- tasks.map((x) => (
-
- |
-
- |
- {displayColumn.JobNumber && (
- {x.jobNumber} |
- )}
- {displayColumn.Status && (
-
- {(() => {
- switch (x.status) {
- case STATUS.UPLOADED:
- return ;
- case STATUS.PENDING:
- return ;
- case STATUS.FINISHED:
- return ;
- case STATUS.INPROGRESS:
- return (
-
- );
- default:
- return ;
- }
- })()}
- {x.status}
- |
- )}
- {displayColumn.Priority && (
-
- {x.priority === "01" ? "High" : "Normal"}
- |
- )}
- {displayColumn.Encryption && (
-
- {x.isEncrypted ? (
-
- ) : (
- <>->
- )}
- |
- )}
- {displayColumn.AuthorId && (
- {x.authorId} |
- )}
- {displayColumn.WorkType && (
- {x.workType} |
- )}
- {displayColumn.FileName && (
- {x.fileName} |
- )}
- {displayColumn.FileLength && (
- {x.audioDuration} |
- )}
- {displayColumn.FileSize && (
- {x.fileSize} |
- )}
- {displayColumn.RecordingStartedDate && (
- {x.audioCreatedDate} |
- )}
- {displayColumn.RecordingFinishedDate && (
-
- {x.audioFinishedDate}
- |
- )}
- {displayColumn.UploadDate && (
-
- {x.audioUploadedDate}
- |
- )}
- {displayColumn.TranscriptionStartedDate && (
-
- {x.transcriptionStartedDate}
- |
- )}
- {displayColumn.TranscriptionFinishedDate && (
-
- {x.transcriptionFinishedDate}
- |
- )}
- {displayColumn.Transcriptionist && (
-
- {x.assignees.map((a, i) => (
- <>
- {a.typistName}
- {i !== x.assignees.length - 1 && }
- >
- ))}
- |
- )}
- {displayColumn.Comment && (
- {x.comment} |
- )}
- {displayColumn.OptionItem1 && (
-
- {x.optionItemList[0].optionItemValue}
- |
- )}
-
- {displayColumn.OptionItem2 && (
-
- {x.optionItemList[1].optionItemValue}
- |
- )}
- {displayColumn.OptionItem3 && (
-
- {x.optionItemList[2].optionItemValue}
- |
- )}
- {displayColumn.OptionItem4 && (
-
- {x.optionItemList[3].optionItemValue}
- |
- )}
- {displayColumn.OptionItem5 && (
-
- {x.optionItemList[4].optionItemValue}
- |
- )}
- {displayColumn.OptionItem6 && (
-
- {x.optionItemList[5].optionItemValue}
- |
- )}
- {displayColumn.OptionItem7 && (
-
- {x.optionItemList[6].optionItemValue}
- |
- )}
- {displayColumn.OptionItem8 && (
-
- {x.optionItemList[7].optionItemValue}
- |
- )}
- {displayColumn.OptionItem9 && (
-
- {x.optionItemList[8].optionItemValue}
- |
- )}
- {displayColumn.OptionItem10 && (
-
- {x.optionItemList[9].optionItemValue}
- |
- )}
-
- ))}
-
- {tasks.length === 0 && (
-
- {t(getTranslationID("common.message.listEmpty"))}
-
- )}
-
-
- {/** pagenation */}
-
-
-
+ <>
+
+
+
+
+
+
+
+ {t(getTranslationID("dictationPage.label.title"))}
+
-
-
-
-
-
+
+
+
+
+
+ - {t(getTranslationID("dictationPage.label.filter"))}:
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+ | {/** th is empty */} |
+ {displayColumn.JobNumber && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.JobNumber)
+ }
+ >
+ {t(
+ getTranslationID("dictationPage.label.jobNumber")
+ )}
+
+ |
+ )}
+ {displayColumn.Status && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.Status)
+ }
+ >
+ {t(getTranslationID("dictationPage.label.status"))}
+
+ |
+ )}
+ {displayColumn.Priority && (
+ Priority |
+ )}
+ {displayColumn.Encryption && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.Encryption)
+ }
+ >
+ {t(
+ getTranslationID("dictationPage.label.encryption")
+ )}
+
+ |
+ )}
+ {displayColumn.AuthorId && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.AuthorId)
+ }
+ >
+ {t(
+ getTranslationID("dictationPage.label.authorId")
+ )}
+
+ |
+ )}
+ {displayColumn.WorkType && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.WorkType)
+ }
+ >
+ {t(
+ getTranslationID("dictationPage.label.workType")
+ )}
+
+ |
+ )}
+ {displayColumn.FileName && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.FileName)
+ }
+ >
+ {t(
+ getTranslationID("dictationPage.label.fileName")
+ )}
+
+ |
+ )}
+ {displayColumn.FileLength && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.FileLength)
+ }
+ >
+ {t(
+ getTranslationID("dictationPage.label.fileLength")
+ )}
+
+ |
+ )}
+ {displayColumn.FileSize && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.FileSize)
+ }
+ >
+ {t(
+ getTranslationID("dictationPage.label.fileSize")
+ )}
+
+ |
+ )}
+ {displayColumn.RecordingStartedDate && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(
+ SORTABLE_COLUMN.RecordingStartedDate
+ )
+ }
+ >
+ {t(
+ getTranslationID(
+ "dictationPage.label.recordingStartedDate"
+ )
+ )}
+
+ |
+ )}
+ {displayColumn.RecordingFinishedDate && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(
+ SORTABLE_COLUMN.RecordingFinishedDate
+ )
+ }
+ >
+ {t(
+ getTranslationID(
+ "dictationPage.label.recordingFinishedDate"
+ )
+ )}
+
+ |
+ )}
+ {displayColumn.UploadDate && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(SORTABLE_COLUMN.UploadDate)
+ }
+ >
+ {t(
+ getTranslationID("dictationPage.label.uploadDate")
+ )}
+
+ |
+ )}
+ {displayColumn.TranscriptionStartedDate && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(
+ SORTABLE_COLUMN.TranscriptionStartedDate
+ )
+ }
+ >
+ {t(
+ getTranslationID(
+ "dictationPage.label.transcriptionStartedDate"
+ )
+ )}
+
+ |
+ )}
+ {displayColumn.TranscriptionFinishedDate && (
+
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
+
+ updateSortColumn(
+ SORTABLE_COLUMN.TranscriptionFinishedDate
+ )
+ }
+ >
+ {t(
+ getTranslationID(
+ "dictationPage.label.transcriptionFinishedDate"
+ )
+ )}
+
+ |
+ )}
+ {displayColumn.Transcriptionist && (
+
+ {t(
+ getTranslationID(
+ "dictationPage.label.transcriptionist"
+ )
+ )}
+ |
+ )}
+ {displayColumn.Comment && (
+
+ {t(getTranslationID("dictationPage.label.comment"))}
+ |
+ )}
+ {displayColumn.OptionItem1 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem1")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem2 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem2")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem3 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem3")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem4 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem4")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem5 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem5")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem6 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem6")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem7 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem7")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem8 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem8")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem9 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem9")
+ )}
+ |
+ )}
+ {displayColumn.OptionItem10 && (
+
+ {t(
+ getTranslationID("dictationPage.label.optionItem10")
+ )}
+ |
+ )}
+
+ {tasks.length !== 0 &&
+ tasks.map((x) => (
+
+ |
+
+ |
+ {displayColumn.JobNumber && (
+ {x.jobNumber} |
+ )}
+ {displayColumn.Status && (
+
+ {(() => {
+ switch (x.status) {
+ case STATUS.UPLOADED:
+ return (
+
+ );
+ case STATUS.PENDING:
+ return ;
+ case STATUS.FINISHED:
+ return (
+
+ );
+ case STATUS.INPROGRESS:
+ return (
+
+ );
+ default:
+ return ;
+ }
+ })()}
+ {x.status}
+ |
+ )}
+ {displayColumn.Priority && (
+
+ {x.priority === "01" ? "High" : "Normal"}
+ |
+ )}
+ {displayColumn.Encryption && (
+
+ {x.isEncrypted ? (
+
+ ) : (
+ <>->
+ )}
+ |
+ )}
+ {displayColumn.AuthorId && (
+ {x.authorId} |
+ )}
+ {displayColumn.WorkType && (
+ {x.workType} |
+ )}
+ {displayColumn.FileName && (
+ {x.fileName} |
+ )}
+ {displayColumn.FileLength && (
+ {x.audioDuration} |
+ )}
+ {displayColumn.FileSize && (
+ {x.fileSize} |
+ )}
+ {displayColumn.RecordingStartedDate && (
+
+ {x.audioCreatedDate}
+ |
+ )}
+ {displayColumn.RecordingFinishedDate && (
+
+ {x.audioFinishedDate}
+ |
+ )}
+ {displayColumn.UploadDate && (
+
+ {x.audioUploadedDate}
+ |
+ )}
+ {displayColumn.TranscriptionStartedDate && (
+
+ {x.transcriptionStartedDate}
+ |
+ )}
+ {displayColumn.TranscriptionFinishedDate && (
+
+ {x.transcriptionFinishedDate}
+ |
+ )}
+ {displayColumn.Transcriptionist && (
+
+ {x.assignees.map((a, i) => (
+ <>
+ {a.typistName}
+ {i !== x.assignees.length - 1 && }
+ >
+ ))}
+ |
+ )}
+ {displayColumn.Comment && (
+ {x.comment} |
+ )}
+ {displayColumn.OptionItem1 && (
+
+ {x.optionItemList[0].optionItemValue}
+ |
+ )}
+
+ {displayColumn.OptionItem2 && (
+
+ {x.optionItemList[1].optionItemValue}
+ |
+ )}
+ {displayColumn.OptionItem3 && (
+
+ {x.optionItemList[2].optionItemValue}
+ |
+ )}
+ {displayColumn.OptionItem4 && (
+
+ {x.optionItemList[3].optionItemValue}
+ |
+ )}
+ {displayColumn.OptionItem5 && (
+
+ {x.optionItemList[4].optionItemValue}
+ |
+ )}
+ {displayColumn.OptionItem6 && (
+
+ {x.optionItemList[5].optionItemValue}
+ |
+ )}
+ {displayColumn.OptionItem7 && (
+
+ {x.optionItemList[6].optionItemValue}
+ |
+ )}
+ {displayColumn.OptionItem8 && (
+
+ {x.optionItemList[7].optionItemValue}
+ |
+ )}
+ {displayColumn.OptionItem9 && (
+
+ {x.optionItemList[8].optionItemValue}
+ |
+ )}
+ {displayColumn.OptionItem10 && (
+
+ {x.optionItemList[9].optionItemValue}
+ |
+ )}
+
+ ))}
+
+ {tasks.length === 0 && (
+
+ {t(getTranslationID("common.message.listEmpty"))}
+
+ )}
+
+
+ {/** pagenation */}
+
+
+
+
+
+
+
+
+
+ >
);
};
diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json
index 5a4b844..7a0d5b7 100644
--- a/dictation_client/src/translation/de.json
+++ b/dictation_client/src/translation/de.json
@@ -5,7 +5,8 @@
"passwordIncorrectError": "(de)Error Message",
"emailIncorrectError": "(de)Error Message",
"internalServerError": "(de)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
- "listEmpty": "(de)検索結果が0件です。"
+ "listEmpty": "(de)検索結果が0件です。",
+ "dialogConfirm": "(de)操作を実行しますか?"
},
"label": {
"cancel": "(de)Cancel",
@@ -171,6 +172,9 @@
}
},
"dictationPage": {
+ "message": {
+ "taskNotEditable": "(de)すでに文字起こし作業着手中またはタスクが存在しないため、タイピストを変更できません。"
+ },
"label": {
"title": "(de)Dictations",
"displayInfomation": "(de)Display Information",
@@ -209,7 +213,10 @@
"playback": "(de)Playback",
"fileProperty": "(de)File Property",
"changeTranscriptionist": "(de)Change Transcriptionist",
- "deleteDictation": "(de)Delete Dictation"
+ "deleteDictation": "(de)Delete Dictation",
+ "selectedTranscriptionist": "(de)Selected",
+ "poolTranscriptionist": "(de)Pool",
+ "saveChanges": "(de)Save changes"
}
}
}
\ No newline at end of file
diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json
index c15aef5..c0a18fd 100644
--- a/dictation_client/src/translation/en.json
+++ b/dictation_client/src/translation/en.json
@@ -5,7 +5,8 @@
"passwordIncorrectError": "Error Message",
"emailIncorrectError": "Error Message",
"internalServerError": "処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
- "listEmpty": "検索結果が0件です。"
+ "listEmpty": "検索結果が0件です。",
+ "dialogConfirm": "操作を実行しますか?"
},
"label": {
"cancel": "Cancel",
@@ -171,6 +172,9 @@
}
},
"dictationPage": {
+ "message": {
+ "taskNotEditable": "すでに文字起こし作業着手中またはタスクが存在しないため、タイピストを変更できません。"
+ },
"label": {
"title": "Dictations",
"displayInfomation": "Display Information",
@@ -209,7 +213,10 @@
"playback": "Playback",
"fileProperty": "File Property",
"changeTranscriptionist": "Change Transcriptionist",
- "deleteDictation": "Delete Dictation"
+ "deleteDictation": "Delete Dictation",
+ "selectedTranscriptionist": "Selected",
+ "poolTranscriptionist": "Pool",
+ "saveChanges": "Save changes"
}
}
}
\ No newline at end of file
diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json
index 3958c14..58308fa 100644
--- a/dictation_client/src/translation/es.json
+++ b/dictation_client/src/translation/es.json
@@ -5,7 +5,8 @@
"passwordIncorrectError": "(es)Error Message",
"emailIncorrectError": "(es)Error Message",
"internalServerError": "(es)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
- "listEmpty": "(es)検索結果が0件です。"
+ "listEmpty": "(es)検索結果が0件です。",
+ "dialogConfirm": "(es)操作を実行しますか?"
},
"label": {
"cancel": "(es)Cancel",
@@ -171,6 +172,9 @@
}
},
"dictationPage": {
+ "message": {
+ "taskNotEditable": "(es)すでに文字起こし作業着手中またはタスクが存在しないため、タイピストを変更できません。"
+ },
"label": {
"title": "(es)Dictations",
"displayInfomation": "(es)Display Information",
@@ -209,7 +213,10 @@
"playback": "(es)Playback",
"fileProperty": "(es)File Property",
"changeTranscriptionist": "(es)Change Transcriptionist",
- "deleteDictation": "(es)Delete Dictation"
+ "deleteDictation": "(es)Delete Dictation",
+ "selectedTranscriptionist": "(es)Selected",
+ "poolTranscriptionist": "(es)Pool",
+ "saveChanges": "(es)Save changes"
}
}
}
\ No newline at end of file
diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json
index ed74436..ed7d524 100644
--- a/dictation_client/src/translation/fr.json
+++ b/dictation_client/src/translation/fr.json
@@ -5,7 +5,8 @@
"passwordIncorrectError": "(fr)Error Message",
"emailIncorrectError": "(fr)Error Message",
"internalServerError": "(fr)処理に失敗しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。",
- "listEmpty": "(fr)検索結果が0件です。"
+ "listEmpty": "(fr)検索結果が0件です。",
+ "dialogConfirm": "(fr)操作を実行しますか?"
},
"label": {
"cancel": "(fr)Cancel",
@@ -171,6 +172,9 @@
}
},
"dictationPage": {
+ "message": {
+ "taskNotEditable": "(fr)すでに文字起こし作業着手中またはタスクが存在しないため、タイピストを変更できません。"
+ },
"label": {
"title": "(fr)Dictations",
"displayInfomation": "(fr)Display Information",
@@ -209,7 +213,10 @@
"playback": "(fr)Playback",
"fileProperty": "(fr)File Property",
"changeTranscriptionist": "(fr)Change Transcriptionist",
- "deleteDictation": "(fr)Delete Dictation"
+ "deleteDictation": "(fr)Delete Dictation",
+ "selectedTranscriptionist": "(fr)Selected",
+ "poolTranscriptionist": "(fr)Pool",
+ "saveChanges": "(fr)Save changes"
}
}
}
\ No newline at end of file