caseIds) {
TestPlanTestCaseExample example = new TestPlanTestCaseExample();
example.createCriteria().andCaseIdIn(caseIds);
diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack
index e4521190f0..647e945828 160000
--- a/backend/src/main/java/io/metersphere/xpack
+++ b/backend/src/main/java/io/metersphere/xpack
@@ -1 +1 @@
-Subproject commit e4521190f0f1be113c8b84fbdcdf8ff273bf274e
+Subproject commit 647e945828e9f15de61cf19e726424bec06051f8
diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties
index 63a9f26f1e..3ac43e7a11 100644
--- a/backend/src/main/resources/i18n/messages_en_US.properties
+++ b/backend/src/main/resources/i18n/messages_en_US.properties
@@ -43,6 +43,8 @@ test_not_running=Test is not running
load_test_already_exists=Duplicate load test name
no_nodes_message=No node message
duplicate_node_ip=Duplicate IPs
+duplicate_node_port=Duplicate Ports
+duplicate_node_ip_port=Duplicate IPs & Ports
max_thread_insufficient=The number of concurrent users exceeds
related_case_del_fail_prefix=Connected to
related_case_del_fail_suffix=TestCase, please disassociate first
diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties
index d42e0b0162..87c7dd1353 100644
--- a/backend/src/main/resources/i18n/messages_zh_CN.properties
+++ b/backend/src/main/resources/i18n/messages_zh_CN.properties
@@ -43,6 +43,8 @@ test_not_running=测试未运行
load_test_already_exists=测试名称不能重复
no_nodes_message=没有节点信息
duplicate_node_ip=节点 IP 重复
+duplicate_node_port=节点 Port 重复
+duplicate_node_ip_port=节点 IP、Port 重复
max_thread_insufficient=并发用户数超额
related_case_del_fail_prefix=已关联到
related_case_del_fail_suffix=测试用例,请先解除关联
diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties
index 5c2666354b..4e9f9ffff4 100644
--- a/backend/src/main/resources/i18n/messages_zh_TW.properties
+++ b/backend/src/main/resources/i18n/messages_zh_TW.properties
@@ -43,6 +43,8 @@ test_not_running=測試未運行
load_test_already_exists=測試名稱不能重復
no_nodes_message=沒有節點信息
duplicate_node_ip=節點 IP 重復
+duplicate_node_port=節點 Port 重復
+duplicate_node_ip_port=節點 IP、Port 重復
max_thread_insufficient=並發用戶數超額
related_case_del_fail_prefix=已關聯到
related_case_del_fail_suffix=測試用例,請先解除關聯
diff --git a/frontend/src/business/components/api/automation/ApiAutomation.vue b/frontend/src/business/components/api/automation/ApiAutomation.vue
index f4e322c62a..097ceb5346 100644
--- a/frontend/src/business/components/api/automation/ApiAutomation.vue
+++ b/frontend/src/business/components/api/automation/ApiAutomation.vue
@@ -35,7 +35,7 @@
closable>
+ :moduleOptions="moduleOptions" ref="autoScenarioConfig"/>
@@ -111,6 +111,16 @@
// 在 DOM 中添加 my-component 组件
this.renderComponent = true;
});
+ },
+ '$route'(to, from) { // 路由改变时,把接口定义界面中的 ctrl s 保存快捷键监听移除
+ if (to.path.indexOf('/api/automation') == -1) {
+ if (this.$refs && this.$refs.autoScenarioConfig) {
+ console.log(this.$refs.autoScenarioConfig);
+ this.$refs.autoScenarioConfig.forEach(item => {
+ item.removeListener();
+ });
+ }
+ }
}
},
methods: {
@@ -158,6 +168,20 @@
label = tab.currentScenario.name;
this.tabs.push({label: label, name: name, currentScenario: tab.currentScenario});
}
+ if (this.$refs && this.$refs.autoScenarioConfig) {
+ this.$refs.autoScenarioConfig.forEach(item => {
+ item.removeListener();
+ }); // 删除所有tab的 ctrl + s 监听
+ this.addListener();
+ }
+ },
+ addListener() {
+ let index = this.tabs.findIndex(item => item.name === this.activeName); // 找到当前选中tab的index
+ if(index != -1) { // 为当前选中的tab添加监听
+ this.$nextTick(()=>{
+ this.$refs.autoScenarioConfig[index].addListener();
+ });
+ }
},
handleTabClose() {
this.tabs = [];
@@ -181,6 +205,7 @@
this.tabs = this.tabs.filter(tab => tab.name !== targetName);
if (this.tabs.length > 0) {
this.activeName = this.tabs[this.tabs.length - 1].name;
+ this.addListener(); // 自动切换当前标签时,也添加监听
} else {
this.activeName = "default"
}
diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue
index e80461184f..257b9335ef 100644
--- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue
+++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue
@@ -5,7 +5,7 @@
- {{$t('commons.save')}}
+ {{$t('commons.save')}}
{{$t('test_track.plan_view.base_info')}}
@@ -222,6 +222,7 @@
import ScenarioApiRelevance from "./api/ApiRelevance";
import ScenarioRelevance from "./api/ScenarioRelevance";
import MsComponentConfig from "./component/ComponentConfig";
+ import {handleCtrlSEvent} from "../../../../../common/js/utils";
export default {
name: "EditApiScenario",
@@ -295,6 +296,7 @@
this.operatingElements = ELEMENTS.get("ALL");
this.getMaintainerOptions();
this.getApiScenario();
+ this.addListener(); // 添加 ctrl s 监听
},
directives: {OutsideClick},
computed: {
@@ -413,6 +415,17 @@
}
},
methods: {
+ addListener() {
+ document.addEventListener("keydown", this.createCtrlSHandle);
+ // document.addEventListener("keydown", (even => handleCtrlSEvent(even, this.$refs.httpApi.saveApi)));
+ },
+ removeListener() {
+ document.removeEventListener("keydown", this.createCtrlSHandle);
+ },
+ createCtrlSHandle(event) {
+ console.log("create ctrl + s");
+ handleCtrlSEvent(event, this.editScenario);
+ },
getIdx(index) {
return index - 0.33
},
@@ -822,15 +835,13 @@
}
return bodyUploadFiles;
},
- editScenario(showMessage) {
+ editScenario() {
this.$refs['currentScenario'].validate((valid) => {
if (valid) {
this.setParameter();
let bodyFiles = this.getBodyUploadFiles(this.currentScenario);
this.$fileUpload(this.path, null, bodyFiles, this.currentScenario, response => {
- if (showMessage) {
- this.$success(this.$t('commons.save_success'));
- }
+ this.$success(this.$t('commons.save_success'));
this.path = "/api/automation/update";
if (response.data) {
this.currentScenario.id = response.data.id;
diff --git a/frontend/src/business/components/api/definition/ApiDefinition.vue b/frontend/src/business/components/api/definition/ApiDefinition.vue
index 0421b518eb..9b47c97278 100644
--- a/frontend/src/business/components/api/definition/ApiDefinition.vue
+++ b/frontend/src/business/components/api/definition/ApiDefinition.vue
@@ -198,6 +198,15 @@
// 在 DOM 中添加 my-component 组件
this.renderComponent = true;
});
+ },
+ '$route'(to, from) { // 路由改变时,把接口定义界面中的 ctrl s 保存快捷键监听移除
+ if (to.path.indexOf('/api/definition') == -1) {
+ if (this.$refs && this.$refs.apiConfig) {
+ this.$refs.apiConfig.forEach(item => {
+ item.removeListener();
+ });
+ }
+ }
}
},
methods: {
@@ -211,6 +220,17 @@
if (tab.name === 'add') {
this.handleTabsEdit(this.$t('api_test.definition.request.fast_debug'), "debug");
}
+ if(this.$refs.apiConfig) {
+ this.$refs.apiConfig.forEach(item => {
+ console.log(item);
+ item.removeListener();
+ }); // 删除所有tab的 ctrl + s 监听
+ let tabs = this.apiTabs;
+ let index = tabs.findIndex(item => item.name === tab.name); // 找到当前选中tab的index
+ if(index != -1) {
+ this.$refs.apiConfig[index - 1].addListener(); // 为选中tab添加 ctrl + s 监听(index-1的原因是要除去第一个固有tab)
+ }
+ }
},
handleCommand(e) {
switch (e) {
diff --git a/frontend/src/business/components/api/definition/components/ApiConfig.vue b/frontend/src/business/components/api/definition/components/ApiConfig.vue
index 1cf299f8a2..bbd76219c8 100644
--- a/frontend/src/business/components/api/definition/components/ApiConfig.vue
+++ b/frontend/src/business/components/api/definition/components/ApiConfig.vue
@@ -3,16 +3,16 @@
+ :basisData="currentApi" :moduleOptions="moduleOptions" :syncTabs="syncTabs" v-if="currentProtocol === 'HTTP'" ref="httpApi"/>
+ :moduleOptions="moduleOptions" :syncTabs="syncTabs" v-if="currentProtocol === 'TCP'" ref="tcpApi"/>
+ :moduleOptions="moduleOptions" :syncTabs="syncTabs" v-if="currentProtocol === 'DUBBO'" ref="dubboApi"/>
+ :moduleOptions="moduleOptions" :syncTabs="syncTabs" v-if="currentProtocol === 'SQL'" ref="sqlApi"/>
@@ -27,6 +27,7 @@
import {createComponent, Request} from "./jmeter/components";
import Sampler from "./jmeter/components/sampler/sampler";
import {WORKSPACE_ID} from '@/common/js/constants';
+ import {handleCtrlSEvent} from "../../../../../common/js/utils";
export default {
name: "ApiConfig",
@@ -65,8 +66,30 @@
break;
}
this.formatApi();
+ this.addListener();
},
methods: {
+ addListener() {
+ document.addEventListener("keydown", this.createCtrlSHandle);
+ // document.addEventListener("keydown", (even => handleCtrlSEvent(even, this.$refs.httpApi.saveApi)));
+ },
+ removeListener() {
+ document.removeEventListener("keydown", this.createCtrlSHandle);
+ },
+ createCtrlSHandle(event) {
+ if(this.$refs.httpApi) {
+ handleCtrlSEvent(event, this.$refs.httpApi.saveApi);
+ }
+ else if(this.$refs.tcpApi) {
+ handleCtrlSEvent(event, this.$refs.tcpApi.saveApi);
+ }
+ else if(this.$refs.dubboApi) {
+ handleCtrlSEvent(event, this.$refs.dubboApi.saveApi);
+ }
+ else if(this.$refs.sqlApi) {
+ handleCtrlSEvent(event, this.$refs.sqlApi.saveApi);
+ }
+ },
runTest(data) {
this.setParameters(data);
let bodyFiles = this.getBodyUploadFiles(data);
diff --git a/frontend/src/business/components/api/definition/components/complete/EditCompleteDubboApi.vue b/frontend/src/business/components/api/definition/components/complete/EditCompleteDubboApi.vue
index ffecc7cd01..90769131cb 100644
--- a/frontend/src/business/components/api/definition/components/complete/EditCompleteDubboApi.vue
+++ b/frontend/src/business/components/api/definition/components/complete/EditCompleteDubboApi.vue
@@ -6,7 +6,7 @@
- {{ $t('commons.save') }}
+ {{ $t('commons.save') }}
{{ $t('commons.test') }}
diff --git a/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue b/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue
index 6f4415ee65..7cb015595a 100644
--- a/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue
+++ b/frontend/src/business/components/api/definition/components/complete/EditCompleteHTTPApi.vue
@@ -6,7 +6,7 @@
- {{ $t('commons.save') }}
+ {{ $t('commons.save') }}
{{ $t('commons.test') }}
diff --git a/frontend/src/business/components/api/definition/components/complete/EditCompleteSQLApi.vue b/frontend/src/business/components/api/definition/components/complete/EditCompleteSQLApi.vue
index 0b0bb5699e..14df6b2c0e 100644
--- a/frontend/src/business/components/api/definition/components/complete/EditCompleteSQLApi.vue
+++ b/frontend/src/business/components/api/definition/components/complete/EditCompleteSQLApi.vue
@@ -5,7 +5,7 @@
- {{ $t('commons.save') }}
+ {{ $t('commons.save') }}
{{ $t('commons.test') }}
diff --git a/frontend/src/business/components/api/definition/components/complete/EditCompleteTCPApi.vue b/frontend/src/business/components/api/definition/components/complete/EditCompleteTCPApi.vue
index 3253522333..682ea8817c 100644
--- a/frontend/src/business/components/api/definition/components/complete/EditCompleteTCPApi.vue
+++ b/frontend/src/business/components/api/definition/components/complete/EditCompleteTCPApi.vue
@@ -5,7 +5,7 @@
- {{ $t('commons.save') }}
+ {{ $t('commons.save') }}
{{ $t('commons.test') }}
diff --git a/frontend/src/business/components/common/head/HeaderUser.vue b/frontend/src/business/components/common/head/HeaderUser.vue
index ce11e27682..c096706f44 100644
--- a/frontend/src/business/components/common/head/HeaderUser.vue
+++ b/frontend/src/business/components/common/head/HeaderUser.vue
@@ -8,6 +8,7 @@
{{ $t('commons.personal_information') }}
{{ $t('commons.about_us') }}
{{ $t('commons.help_documentation') }}
+ {{ $t('commons.api_help_documentation') }}
{{ $t('commons.cut_back_old_version') }}
@@ -64,6 +65,9 @@ export default {
case "help":
window.location.href = "https://metersphere.io/docs/index.html";
break;
+ case "ApiHelp":
+ window.open('/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config', "_blank");
+ break;
default:
break;
}
diff --git a/frontend/src/business/components/track/case/TestCase.vue b/frontend/src/business/components/track/case/TestCase.vue
index 09cb2a382a..b618625b53 100644
--- a/frontend/src/business/components/track/case/TestCase.vue
+++ b/frontend/src/business/components/track/case/TestCase.vue
@@ -12,15 +12,15 @@
@@ -28,6 +28,7 @@
-
-
-
@@ -49,12 +47,10 @@ import NodeTree from '../common/NodeTree';
import TestCaseEdit from './components/TestCaseEdit';
import TestCaseList from "./components/TestCaseList";
import SelectMenu from "../common/SelectMenu";
-import TestCaseMove from "./components/TestCaseMove";
import MsContainer from "../../common/components/MsContainer";
import MsAsideContainer from "../../common/components/MsAsideContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import {checkoutTestManagerOrTestUser, getCurrentProjectID, hasRoles} from "../../../../common/js/utils";
-import BatchMove from "./components/BatchMove";
import TestCaseNodeTree from "../common/TestCaseNodeTree";
import {TrackEvent,LIST_CHANGE} from "@/business/components/common/head/ListEvent";
@@ -63,7 +59,7 @@ export default {
components: {
TestCaseNodeTree,
MsMainContainer,
- MsAsideContainer, MsContainer, TestCaseMove, TestCaseList, NodeTree, TestCaseEdit, SelectMenu, BatchMove
+ MsAsideContainer, MsContainer, TestCaseList, NodeTree, TestCaseEdit, SelectMenu
},
comments: {},
data() {
@@ -75,7 +71,8 @@ export default {
selectParentNodes: [],
testCaseReadOnly: true,
selectNode: {},
- condition: {}
+ condition: {},
+ moduleOptions: []
}
},
mounted() {
@@ -153,32 +150,14 @@ export default {
this.$refs.testCaseEditDialog.open();
}
},
-
- moveToNode(selectIds) {
- if (selectIds.size < 1) {
- this.$warning(this.$t('test_track.plan_view.select_manipulate'));
- return;
- }
- this.$refs.testCaseEditDialog.getModuleOptions();
- this.$refs.testCaseMove.open(this.$refs.testCaseEditDialog.moduleOptions, selectIds);
- },
- batchMove(selectIds) {
- this.$refs.testBatchMove.open(this.treeNodes, selectIds, this.$refs.testCaseEditDialog.moduleOptions);
- },
setTreeNodes(data) {
this.treeNodes = data;
},
setCondition(data) {
this.condition = data;
},
- moveSave(param) {
- this.result = this.$post('/test/case/batch/edit', param, () => {
- this.$success(this.$t('commons.save_success'));
- this.$refs.testBatchMove.close();
- // 发送广播,刷新 head 上的最新列表
- TrackEvent.$emit(LIST_CHANGE);
- this.refresh();
- });
+ setModuleOptions(data) {
+ this.moduleOptions = data;
}
}
}
diff --git a/frontend/src/business/components/track/case/components/TestCaseEdit.vue b/frontend/src/business/components/track/case/components/TestCaseEdit.vue
index b517936329..aaa1679c6e 100644
--- a/frontend/src/business/components/track/case/components/TestCaseEdit.vue
+++ b/frontend/src/business/components/track/case/components/TestCaseEdit.vue
@@ -379,6 +379,9 @@ export default {
treeNodes() {
this.getModuleOptions();
},
+ moduleOptions() {
+ this.$emit('setModuleOptions', this.moduleOptions);
+ }
},
methods: {
reload() {
diff --git a/frontend/src/business/components/track/case/components/TestCaseList.vue b/frontend/src/business/components/track/case/components/TestCaseList.vue
index 7d62024dec..d076a1f38b 100644
--- a/frontend/src/business/components/track/case/components/TestCaseList.vue
+++ b/frontend/src/business/components/track/case/components/TestCaseList.vue
@@ -15,11 +15,6 @@
:content="$t('test_track.case.import.import')" @click="importTestCase"/>
-
-
-
-
-
@@ -33,12 +28,22 @@
@sort-change="sort"
@filter-change="filter"
@select-all="handleSelectAll"
- @select="handleSelectionChange"
+ @select="handleSelect"
@cell-mouse-enter="showPopover"
row-key="id"
- class="test-content adjust-table">
+ class="test-content adjust-table ms-select-all"
+ ref="table">
+
+
+
+
@@ -156,6 +161,8 @@
+
+
@@ -184,10 +191,15 @@ import TestCaseDetail from "./TestCaseDetail";
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
import {getCurrentProjectID} from "../../../../../common/js/utils";
import MsTag from "@/business/components/common/components/MsTag";
+import MsTableSelectAll from "../../../common/components/table/MsTableSelectAll";
+import {_handleSelect, _handleSelectAll} from "../../../../../common/js/tableUtils";
+import BatchMove from "./BatchMove";
export default {
name: "TestCaseList",
components: {
+ BatchMove,
+ MsTableSelectAll,
MsTableButton,
MsTableOperatorButton,
MsTableOperator,
@@ -274,7 +286,7 @@ export default {
maintainer: [],
},
currentCaseId: null,
- projectId: ""
+ projectId: "",
}
},
props: {
@@ -283,6 +295,12 @@ export default {
},
selectParentNodes: {
type: Array
+ },
+ treeNodes: {
+ type: Array
+ },
+ moduleOptions: {
+ type: Array
}
},
created: function () {
@@ -303,6 +321,8 @@ export default {
this.projectId = getCurrentProjectID();
this.condition.planId = "";
this.condition.nodeIds = [];
+ this.condition.selectAll = false;
+ this.condition.unSelectIds = [];
if (this.planId) {
// param.planId = this.planId;
this.condition.planId = this.planId;
@@ -366,8 +386,10 @@ export default {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
- let ids = Array.from(this.selectRows).map(row => row.id);
- this.$post('/test/case/batch/delete', {ids: ids}, () => {
+ let param = {};
+ param.ids = Array.from(this.selectRows).map(row => row.id);
+ param.condition = this.condition;
+ this.$post('/test/case/batch/delete', param, () => {
this.selectRows.clear();
this.$emit("refresh");
this.$success(this.$t('commons.delete_success'));
@@ -401,26 +423,12 @@ export default {
this.$emit('testCaseDetail', row);
},
handleSelectAll(selection) {
- if (selection.length > 0) {
- this.tableData.forEach(item => {
- this.$set(item, "showMore", true);
- this.selectRows.add(item);
- });
- } else {
- this.selectRows.clear();
- this.tableData.forEach(row => {
- this.$set(row, "showMore", false);
- })
- }
+ _handleSelectAll(this, selection, this.tableData, this.selectRows);
+ this.setUnSelectIds();
},
- handleSelectionChange(selection, row) {
- if (this.selectRows.has(row)) {
- this.$set(row, "showMore", false);
- this.selectRows.delete(row);
- } else {
- this.$set(row, "showMore", true);
- this.selectRows.add(row);
- }
+ handleSelect(selection, row) {
+ _handleSelect(this, selection, row, this.selectRows);
+ this.setUnSelectIds();
},
importTestCase() {
if (!getCurrentProjectID()) {
@@ -457,7 +465,6 @@ export default {
});
},
handleBatch(type) {
-
if (this.selectRows.size < 1) {
if (type === 'export') {
this.$alert(this.$t('test_track.case.export_all_cases'), '', {
@@ -489,6 +496,7 @@ export default {
let param = {};
param[form.type] = form.value;
param.ids = ids;
+ param.condition = this.condition;
this.$post('/test/case/batch/edit', param, () => {
this.$success(this.$t('commons.save_success'));
this.refresh();
@@ -513,7 +521,7 @@ export default {
this.$refs.batchEdit.open();
},
handleBatchMove() {
- this.$emit("batchMove", Array.from(this.selectRows).map(row => row.id));
+ this.$refs.testBatchMove.open(this.treeNodes, Array.from(this.selectRows).map(row => row.id), this.moduleOptions);
},
getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
@@ -525,6 +533,31 @@ export default {
if (column.property === 'name') {
this.currentCaseId = row.id;
}
+ },
+ isSelectDataAll(data) {
+ this.condition.selectAll = data;
+ this.setUnSelectIds();
+ //如果已经全选,不需要再操作了
+ if (this.selectRows.size != this.tableData.length) {
+ this.$refs.table.toggleAllSelection(true);
+ }
+ },
+ setUnSelectIds() {
+ let ids = Array.from(this.selectRows).map(o => o.id);
+ let allIDs = this.tableData.map(o => o.id);
+ this.condition.unSelectIds = allIDs.filter(function (val) {
+ return ids.indexOf(val) === -1
+ });
+ },
+ moveSave(param) {
+ param.condition = this.condition;
+ this.result = this.$post('/test/case/batch/edit', param, () => {
+ this.$success(this.$t('commons.save_success'));
+ this.$refs.testBatchMove.close();
+ // 发送广播,刷新 head 上的最新列表
+ TrackEvent.$emit(LIST_CHANGE);
+ this.refresh();
+ });
}
}
}
diff --git a/frontend/src/common/js/utils.js b/frontend/src/common/js/utils.js
index 38de23f333..30602936cc 100644
--- a/frontend/src/common/js/utils.js
+++ b/frontend/src/common/js/utils.js
@@ -356,3 +356,12 @@ export function _getBodyUploadFiles(request, bodyUploadFiles, obj) {
}
}
}
+export function handleCtrlSEvent(event, func) {
+ if (event.keyCode === 83 && event.ctrlKey) {
+ // console.log('拦截到 ctrl + s');//ctrl+s
+ func();
+ event.preventDefault();
+ event.returnValue = false;
+ return false;
+ }
+}
diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js
index 85d73e703d..cf7154389a 100644
--- a/frontend/src/i18n/zh-CN.js
+++ b/frontend/src/i18n/zh-CN.js
@@ -8,6 +8,7 @@ export default {
comment: '评论',
examples: '示例',
help_documentation: '帮助文档',
+ api_help_documentation: 'API文档',
delete_cancelled: '已取消删除',
workspace: '工作空间',
organization: '组织',
diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js
index 47ede5346e..9c07b711b2 100644
--- a/frontend/src/i18n/zh-TW.js
+++ b/frontend/src/i18n/zh-TW.js
@@ -8,6 +8,7 @@ export default {
comment: '評論',
examples: '示例',
help_documentation: '幫助文檔',
+ api_help_documentation: 'API文檔',
delete_cancelled: '已取消刪除',
workspace: '工作空間',
organization: '組織',