From 99ac6be9fdd0d850d87000a2adda3aed58245b1b Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Mon, 20 Nov 2023 07:52:43 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20585:=20=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=EF=BC=88=E3=83=9D=E3=83=83=E3=83=97=E3=82=A2?= =?UTF-8?q?=E3=83=83=E3=83=97=E8=A1=A8=E7=A4=BA=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3120: 画面実装(ポップアップ表示)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3120) - ディクテーション画面から開くバックアップポップアップを実装しました。 - ポップアップを開いて、対象タスクが表示されるところまでの実装です。 - バックアップボタンの挙動は対象外です。 ## レビューポイント - チェックボックスの挙動は適切でしょうか? - SharePointの挙動を参考にしています。 - デザインの適用で不自然な点はないでしょうか? ## UIの変更 - [Task3120](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3120?csf=1&web=1&e=KxCeU4) ## 動作確認状況 - ローカルで確認 --- .../src/assets/images/open_in_new.svg | 11 + .../src/features/dictation/constants.ts | 4 + .../src/features/dictation/dictationSlice.ts | 50 ++++ .../src/features/dictation/index.ts | 1 + .../src/features/dictation/operations.ts | 52 ++++ .../src/features/dictation/selectors.ts | 27 +++ .../src/features/dictation/state.ts | 9 + .../src/features/dictation/types.ts | 5 + .../src/pages/DictationPage/backupPopup.tsx | 224 ++++++++++++++++++ .../src/pages/DictationPage/index.tsx | 41 ++++ dictation_client/src/styles/app.module.scss | 51 +++- .../src/styles/app.module.scss.d.ts | 7 +- dictation_client/src/translation/de.json | 4 +- dictation_client/src/translation/en.json | 4 +- dictation_client/src/translation/es.json | 4 +- dictation_client/src/translation/fr.json | 4 +- 16 files changed, 485 insertions(+), 13 deletions(-) create mode 100644 dictation_client/src/assets/images/open_in_new.svg create mode 100644 dictation_client/src/features/dictation/types.ts create mode 100644 dictation_client/src/pages/DictationPage/backupPopup.tsx diff --git a/dictation_client/src/assets/images/open_in_new.svg b/dictation_client/src/assets/images/open_in_new.svg new file mode 100644 index 0000000..669e721 --- /dev/null +++ b/dictation_client/src/assets/images/open_in_new.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/dictation_client/src/features/dictation/constants.ts b/dictation_client/src/features/dictation/constants.ts index 0fc83e7..d699a54 100644 --- a/dictation_client/src/features/dictation/constants.ts +++ b/dictation_client/src/features/dictation/constants.ts @@ -95,3 +95,7 @@ export const INIT_DISPLAY_INFO: DisplayInfoType = { OptionItem9: false, OptionItem10: false, } as const; + +export const BACKUP_POPUP_LIST_SIZE = 10; + +export const BACKUP_POPUP_LIST_STATUS = [STATUS.FINISHED, STATUS.BACKUP]; diff --git a/dictation_client/src/features/dictation/dictationSlice.ts b/dictation_client/src/features/dictation/dictationSlice.ts index fe97dff..0775825 100644 --- a/dictation_client/src/features/dictation/dictationSlice.ts +++ b/dictation_client/src/features/dictation/dictationSlice.ts @@ -3,6 +3,7 @@ import { Assignee, Task } from "api/api"; import { DictationState } from "./state"; import { getSortColumnAsync, + listBackupPopupTasksAsync, listTasksAsync, listTypistGroupsAsync, listTypistsAsync, @@ -27,6 +28,11 @@ const initialState: DictationState = { tasks: [], typists: [], typistGroups: [], + backup: { + tasks: [], + offset: 0, + total: 0, + }, }, apps: { displayInfo: INIT_DISPLAY_INFO, @@ -38,6 +44,7 @@ const initialState: DictationState = { pool: [], }, isLoading: true, + isBackupListLoading: false, }, }; @@ -97,6 +104,30 @@ export const dictationSlice = createSlice({ state.apps.assignee.selected = selected; state.apps.assignee.pool = pool; }, + changeBackupTaskChecked: ( + state, + action: PayloadAction<{ audioFileId: number; checked: boolean }> + ) => { + const { audioFileId, checked } = action.payload; + const tasks = state.domain.backup.tasks.map((task) => { + if (task.audioFileId === audioFileId) { + task.checked = checked; + } + return task; + }); + state.domain.backup.tasks = tasks; + }, + changeBackupTaskAllCheched: ( + state, + action: PayloadAction<{ checked: boolean }> + ) => { + const { checked } = action.payload; + const tasks = state.domain.backup.tasks.map((task) => { + task.checked = checked; + return task; + }); + state.domain.backup.tasks = tasks; + }, }, extraReducers: (builder) => { builder.addCase(listTasksAsync.pending, (state) => { @@ -145,6 +176,23 @@ export const dictationSlice = createSlice({ builder.addCase(playbackAsync.rejected, (state) => { state.apps.isLoading = false; }); + builder.addCase(listBackupPopupTasksAsync.pending, (state) => { + state.apps.isBackupListLoading = true; + }); + builder.addCase(listBackupPopupTasksAsync.fulfilled, (state, action) => { + const { offset, total, tasks } = action.payload; + state.domain.backup.tasks = tasks.map((task) => ({ + ...task, + checked: true, + })); + state.domain.backup.offset = offset; + state.domain.backup.total = total; + + state.apps.isBackupListLoading = false; + }); + builder.addCase(listBackupPopupTasksAsync.rejected, (state) => { + state.apps.isBackupListLoading = false; + }); }, }); @@ -154,6 +202,8 @@ export const { changeParamName, changeSelectedTask, changeAssignee, + changeBackupTaskChecked, + changeBackupTaskAllCheched, } = dictationSlice.actions; export default dictationSlice.reducer; diff --git a/dictation_client/src/features/dictation/index.ts b/dictation_client/src/features/dictation/index.ts index eb04fc5..0682113 100644 --- a/dictation_client/src/features/dictation/index.ts +++ b/dictation_client/src/features/dictation/index.ts @@ -3,3 +3,4 @@ export * from "./constants"; export * from "./selectors"; export * from "./dictationSlice"; export * from "./operations"; +export * from "./types"; diff --git a/dictation_client/src/features/dictation/operations.ts b/dictation_client/src/features/dictation/operations.ts index 111c513..e7ff81d 100644 --- a/dictation_client/src/features/dictation/operations.ts +++ b/dictation_client/src/features/dictation/operations.ts @@ -15,6 +15,8 @@ import { import { Configuration } from "../../api/configuration"; import { ErrorObject, createErrorObject } from "../../common/errors"; import { + BACKUP_POPUP_LIST_SIZE, + BACKUP_POPUP_LIST_STATUS, DIRECTION, DirectionType, SORTABLE_COLUMN, @@ -352,3 +354,53 @@ export const playbackAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); + +export const listBackupPopupTasksAsync = createAsyncThunk< + TasksResponse, + { + // パラメータ + offset: number; + }, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("dictations/listBackupPopupTasksAsync", async (args, thunkApi) => { + const { offset } = args; + + // apiのConfigurationを取得する + const { getState } = thunkApi; + const state = getState() as RootState; + const { configuration } = state.auth; + const accessToken = getAccessToken(state.auth); + const config = new Configuration(configuration); + const tasksApi = new TasksApi(config); + + try { + const res = await tasksApi.getTasks( + BACKUP_POPUP_LIST_SIZE, + offset, + BACKUP_POPUP_LIST_STATUS.join(","), // ステータスはFinished,Backupのみ + DIRECTION.ASC, + SORTABLE_COLUMN.Status, + { + headers: { authorization: `Bearer ${accessToken}` }, + } + ); + + return res.data; + } catch (e) { + // e ⇒ errorObjectに変換" + const error = createErrorObject(e); + 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 6461175..bd61ca4 100644 --- a/dictation_client/src/features/dictation/selectors.ts +++ b/dictation_client/src/features/dictation/selectors.ts @@ -1,5 +1,6 @@ import { RootState } from "app/store"; import { ceil, floor } from "lodash"; +import { BACKUP_POPUP_LIST_SIZE } from "./constants"; export const selectTasks = (state: RootState) => state.dictation.domain.tasks; @@ -41,3 +42,29 @@ export const selectPoolTranscriptionists = (state: RootState) => export const selectIsLoading = (state: RootState) => state.dictation.apps.isLoading; + +export const selectBackupTasks = (state: RootState) => + state.dictation.domain.backup.tasks; + +export const selectTotalBackupPage = (state: RootState) => { + const { total } = state.dictation.domain.backup; + const page = ceil(total / BACKUP_POPUP_LIST_SIZE); + return page; +}; + +export const selectCurrentBackupPage = (state: RootState) => { + const { offset } = state.dictation.domain.backup; + const page = floor(offset / BACKUP_POPUP_LIST_SIZE) + 1; + return page; +}; + +export const selectBackupTotal = (state: RootState) => + state.dictation.domain.backup.total; + +export const selectBackupAllChecked = (state: RootState) => { + const { tasks } = state.dictation.domain.backup; + return tasks.every((task) => task.checked); +}; + +export const selectIsBackupListLoading = (state: RootState) => + state.dictation.apps.isBackupListLoading; diff --git a/dictation_client/src/features/dictation/state.ts b/dictation_client/src/features/dictation/state.ts index 0d383f7..90a20c9 100644 --- a/dictation_client/src/features/dictation/state.ts +++ b/dictation_client/src/features/dictation/state.ts @@ -4,6 +4,7 @@ import { DisplayInfoType, SortableColumnType, } from "./constants"; +import { BackupTask } from "./types"; export interface DictationState { domain: Domain; @@ -17,6 +18,7 @@ export interface Domain { tasks: Task[]; typists: Typist[]; typistGroups: TypistGroup[]; + backup: Backup; } export interface Apps { @@ -29,4 +31,11 @@ export interface Apps { pool: Assignee[]; }; isLoading: boolean; + isBackupListLoading: boolean; +} + +export interface Backup { + tasks: BackupTask[]; + offset: number; + total: number; } diff --git a/dictation_client/src/features/dictation/types.ts b/dictation_client/src/features/dictation/types.ts new file mode 100644 index 0000000..62d0de3 --- /dev/null +++ b/dictation_client/src/features/dictation/types.ts @@ -0,0 +1,5 @@ +import { Task } from "api"; + +export interface BackupTask extends Task { + checked: boolean; +} diff --git a/dictation_client/src/pages/DictationPage/backupPopup.tsx b/dictation_client/src/pages/DictationPage/backupPopup.tsx new file mode 100644 index 0000000..c622aae --- /dev/null +++ b/dictation_client/src/pages/DictationPage/backupPopup.tsx @@ -0,0 +1,224 @@ +import React, { useCallback, useEffect } from "react"; +import styles from "styles/app.module.scss"; +import { useDispatch, useSelector } from "react-redux"; +import { + BACKUP_POPUP_LIST_SIZE, + changeBackupTaskAllCheched, + changeBackupTaskChecked, + listBackupPopupTasksAsync, + selectBackupAllChecked, + selectBackupTasks, + selectBackupTotal, + selectCurrentBackupPage, + selectIsBackupListLoading, + selectTotalBackupPage, +} from "features/dictation"; +import { AppDispatch } from "app/store"; +import { getTranslationID } from "translation"; +import { useTranslation } from "react-i18next"; +import progress_activit from "../../assets/images/progress_activit.svg"; +import close from "../../assets/images/close.svg"; + +interface BackupPopupProps { + onClose: (isChanged: boolean) => void; + isOpen: boolean; +} + +export const BackupPopup: React.FC = (props) => { + const { onClose, isOpen } = props; + const dispatch: AppDispatch = useDispatch(); + const [t] = useTranslation(); + + const isBackupListLoading = useSelector(selectIsBackupListLoading); + + const backupTasks = useSelector(selectBackupTasks); + + const total = useSelector(selectBackupTotal); + const totalPage = useSelector(selectTotalBackupPage); + const currentPage = useSelector(selectCurrentBackupPage); + + const allChecked = useSelector(selectBackupAllChecked); + + // ポップアップを閉じる処理 + const closePopup = useCallback(() => { + onClose(false); + }, [onClose]); + + useEffect(() => { + if (isOpen) { + dispatch(listBackupPopupTasksAsync({ offset: 0 })); + } + }, [dispatch, isOpen]); + + // ページネーションの制御 + const getFirstPage = useCallback(() => { + dispatch(listBackupPopupTasksAsync({ offset: 0 })); + }, [dispatch]); + + const getLastPage = useCallback(() => { + const lastPageOffset = (totalPage - 1) * BACKUP_POPUP_LIST_SIZE; + dispatch(listBackupPopupTasksAsync({ offset: lastPageOffset })); + }, [dispatch, totalPage]); + + const getPrevPage = useCallback(() => { + const prevPageOffset = (currentPage - 2) * BACKUP_POPUP_LIST_SIZE; + dispatch(listBackupPopupTasksAsync({ offset: prevPageOffset })); + }, [dispatch, currentPage]); + + const getNextPage = useCallback(() => { + const nextPageOffset = currentPage * BACKUP_POPUP_LIST_SIZE; + dispatch(listBackupPopupTasksAsync({ offset: nextPageOffset })); + }, [dispatch, currentPage]); + + return ( +
+
+

+ {t(getTranslationID("dictationPage.label.fileBackup"))} + +

+
+
+
+
+ + + + + + + + + + {!isBackupListLoading && + backupTasks.map((task) => ( + + + + + + + + ))} + {isBackupListLoading && ( + Loading + )} +
+ + dispatch( + changeBackupTaskAllCheched({ + checked: e.target.checked, + }) + ) + } + /> + + {t(getTranslationID("dictationPage.label.jobNumber"))} + + {t(getTranslationID("dictationPage.label.status"))} + + {t(getTranslationID("dictationPage.label.fileName"))} + + {t( + getTranslationID( + "dictationPage.label.transcriptionFinishedDate" + ) + )} +
+ { + dispatch( + changeBackupTaskChecked({ + audioFileId: task.audioFileId, + checked: e.target.checked, + }) + ); + }} + /> + {task.jobNumber}{task.status}{task.fileName}{task.transcriptionFinishedDate}
+
+ + {/** */} +
+ +
+
+
+ +
+
+
+
+
+ ); +}; diff --git a/dictation_client/src/pages/DictationPage/index.tsx b/dictation_client/src/pages/DictationPage/index.tsx index f9c47f8..34df912 100644 --- a/dictation_client/src/pages/DictationPage/index.tsx +++ b/dictation_client/src/pages/DictationPage/index.tsx @@ -42,8 +42,11 @@ import finished from "../../assets/images/finished.svg"; import backup from "../../assets/images/backup.svg"; import lock from "../../assets/images/lock.svg"; import progress_activit from "../../assets/images/progress_activit.svg"; +import download from "../../assets/images/download.svg"; +import open_in_new from "../../assets/images/open_in_new.svg"; import { DisPlayInfo } from "./displayInfo"; import { ChangeTranscriptionistPopup } from "./changeTranscriptionistPopup"; +import { BackupPopup } from "./backupPopup"; const DictationPage: React.FC = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); @@ -59,6 +62,7 @@ const DictationPage: React.FC = (): JSX.Element => { isChangeTranscriptionistPopupOpen, setIsChangeTranscriptionistPopupOpen, ] = useState(false); + const [isBackupPopupOpen, setIsBackupPopupOpen] = useState(false); const onChangeTranscriptionistPopupOpen = useCallback( (task: Task) => { @@ -413,6 +417,14 @@ const DictationPage: React.FC = (): JSX.Element => { ] ); + const onCloseBackupPopup = useCallback(() => { + setIsBackupPopupOpen(false); + }, []); + + const onClickBackup = useCallback(() => { + setIsBackupPopupOpen(true); + }, []); + const sortIconClass = ( currentParam: SortableColumnType, currentDirection: DirectionType, @@ -467,6 +479,7 @@ const DictationPage: React.FC = (): JSX.Element => { return ( <> + { + diff --git a/dictation_client/src/styles/app.module.scss b/dictation_client/src/styles/app.module.scss index b6728b0..8d64ef1 100644 --- a/dictation_client/src/styles/app.module.scss +++ b/dictation_client/src/styles/app.module.scss @@ -890,6 +890,11 @@ h3 + .brCrumb .tlIcon { width: 42%; text-align: left; position: relative; + white-space: pre-line; +} +.listVertical dt.overLine { + padding: 0.4rem 4%; + line-height: 1.15; } .listVertical dd { width: 42%; @@ -1107,6 +1112,25 @@ h3 + .brCrumb .tlIcon { width: inherit; padding: 0.2rem 0.5rem; } +.modal .form .table.backup .formCheck { + margin-right: 0; +} +.modal .form .table.backup th:first-child { + padding: 0 0.2rem; +} +.modal .form .table.backup td { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.modal .form .table.backup td:first-child { + padding: 0.6rem 0.2rem; +} +.modal .form .pagenation { + margin-bottom: 1.5rem; + padding-right: 2.5%; +} .modal .encryptionPass { display: none; } @@ -1898,6 +1922,19 @@ tr.isSelected .menuInTable li a.isDisable { .dictation .menuAction { margin-top: -1rem; + height: 34px; + position: relative; +} +.dictation .menuAction .alignLeft { + position: absolute; + left: 0; +} +.dictation .menuAction .alignLeft .menuLink { + padding: 0.3rem 0.3rem 0.3rem 0.5rem; +} +.dictation .menuAction .alignLeft .menuIcon { + margin-right: 0; + margin-left: 0.4rem; } .dictation .displayOptions { display: none; @@ -2047,7 +2084,7 @@ tr.isSelected .menuInTable li a.isDisable { .dictation .table.dictation td .menuInTable li:nth-child(3) { border-right: none; } -.dictation .table.dictation td .menuInTable li a.mnBack { +.dictation .table.dictation td .menuInTable li a.mnCancel { margin-left: 3rem; } .dictation .table.dictation td:has(img[alt="encrypted"]) { @@ -2266,7 +2303,8 @@ tr.isSelected .menuInTable li a.isDisable { } .formChange ul.chooseMember li input + label:hover, .formChange ul.holdMember li input + label:hover { - background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center; + background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left + center; background-size: 1.3rem; } .formChange ul.chooseMember li input:checked + label, @@ -2277,8 +2315,8 @@ tr.isSelected .menuInTable li a.isDisable { } .formChange ul.chooseMember li input:checked + label:hover, .formChange ul.holdMember li input:checked + label:hover { - background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right - center; + background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat + right center; background-size: 1.3rem; } .formChange > p { @@ -2431,7 +2469,8 @@ tr.isSelected .menuInTable li a.isDisable { } .formChange ul.chooseMember li input + label:hover, .formChange ul.holdMember li input + label:hover { - background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left center; + background: #e6e6e6 url(../assets/images/arrow_circle_left.svg) no-repeat left + center; background-size: 1.3rem; } .formChange ul.chooseMember li input:checked + label, @@ -2442,7 +2481,7 @@ tr.isSelected .menuInTable li a.isDisable { } .formChange ul.chooseMember li input:checked + label:hover, .formChange ul.holdMember li input:checked + label:hover { - background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right + background: #e6e6e6 url(../images/arrow_circle_right.svg) no-repeat right center; background-size: 1.3rem; } diff --git a/dictation_client/src/styles/app.module.scss.d.ts b/dictation_client/src/styles/app.module.scss.d.ts index 6c4bd76..a9c42a8 100644 --- a/dictation_client/src/styles/app.module.scss.d.ts +++ b/dictation_client/src/styles/app.module.scss.d.ts @@ -72,11 +72,12 @@ declare const classNames: { readonly tableWrap: "tableWrap"; readonly table: "table"; readonly tableHeader: "tableHeader"; + readonly backup: "backup"; + readonly pagenation: "pagenation"; readonly encryptionPass: "encryptionPass"; readonly pageHeader: "pageHeader"; readonly pageTitle: "pageTitle"; readonly pageTx: "pageTx"; - readonly pagenation: "pagenation"; readonly pagenationNav: "pagenationNav"; readonly pagenationTotal: "pagenationTotal"; readonly widthMid: "widthMid"; @@ -124,10 +125,11 @@ declare const classNames: { readonly cardHistory: "cardHistory"; readonly partner: "partner"; readonly isOpen: "isOpen"; + readonly alignLeft: "alignLeft"; readonly displayOptions: "displayOptions"; readonly tableFilter: "tableFilter"; readonly tableFilter2: "tableFilter2"; - readonly mnBack: "mnBack"; + readonly mnCancel: "mnCancel"; readonly txWsline: "txWsline"; readonly hidePri: "hidePri"; readonly opPri: "opPri"; @@ -197,7 +199,6 @@ declare const classNames: { readonly template: "template"; readonly worktype: "worktype"; readonly selectMenu: "selectMenu"; - readonly alignLeft: "alignLeft"; readonly floatNone: "floatNone"; readonly floatLeft: "floatLeft"; readonly floatRight: "floatRight"; diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index 190d940..b08ae0a 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -245,7 +245,9 @@ "changeTranscriptionist": "Transkriptionist ändern", "deleteDictation": "Diktat löschen", "selectedTranscriptionist": "Ausgewählter transkriptionist", - "poolTranscriptionist": "Transkriptionsliste" + "poolTranscriptionist": "Transkriptionsliste", + "fileBackup": "(de)File Backup", + "downloadForBackup": "(de)Download for backup" } }, "cardLicenseIssuePopupPage": { diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index ee47601..e4426cf 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -245,7 +245,9 @@ "changeTranscriptionist": "Change Transcriptionist", "deleteDictation": "Delete Dictation", "selectedTranscriptionist": "Selected Transcriptionist", - "poolTranscriptionist": "Transcription List" + "poolTranscriptionist": "Transcription List", + "fileBackup": "File Backup", + "downloadForBackup": "Download for backup" } }, "cardLicenseIssuePopupPage": { diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index 545778b..ada310b 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -245,7 +245,9 @@ "changeTranscriptionist": "Cambiar transcriptor", "deleteDictation": "Borrar dictado", "selectedTranscriptionist": "Transcriptor seleccionado", - "poolTranscriptionist": "Lista de transcriptor" + "poolTranscriptionist": "Lista de transcriptor", + "fileBackup": "(es)File Backup", + "downloadForBackup": "(es)Download for backup" } }, "cardLicenseIssuePopupPage": { diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index ee74ef0..8285a52 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -245,7 +245,9 @@ "changeTranscriptionist": "Changer de transcriptionniste ", "deleteDictation": "Supprimer la dictée", "selectedTranscriptionist": "Transcriptionniste sélectionné", - "poolTranscriptionist": "Liste de transcriptionniste" + "poolTranscriptionist": "Liste de transcriptionniste", + "fileBackup": "(fr)File Backup", + "downloadForBackup": "(fr)Download for backup" } }, "cardLicenseIssuePopupPage": {