表头国际化

This commit is contained in:
chenjianxing 2020-05-19 15:56:05 +08:00
parent 81922206ca
commit cd52993e24
13 changed files with 347 additions and 131 deletions

View File

@ -14,52 +14,52 @@ public class TestCaseExcelData {
@NotBlank
@Length(max=50)
@ExcelProperty("用例名称")
@ExcelProperty("{test_case_name}")
private String name;
@NotBlank
@Length(max=1000)
@ExcelProperty("所属模块")
@ExcelProperty("{test_case_module}")
@ColumnWidth(30)
@Pattern(regexp = "^(?!.*//).*$", message = "{incorrect_format}")
private String nodePath;
@NotBlank
@ExcelProperty("用例类型")
@ExcelProperty("{test_case_type}")
@Pattern(regexp = "(^functional$)|(^performance$)|(^api$)", message = "{test_case_type_validate}")
private String type;
@NotBlank
@ExcelProperty("维护人")
@ExcelProperty("{test_case_maintainer}")
private String maintainer;
@NotBlank
@ExcelProperty("优先级")
@ExcelProperty("{test_case_priority}")
@Pattern(regexp = "(^P0$)|(^P1$)|(^P2$)|(^P3$)", message = "{test_case_priority_validate}")
private String priority;
@NotBlank
@ExcelProperty("测试方式")
@ExcelProperty("{test_case_method}")
@Pattern(regexp = "(^manual$)|(^auto$)", message = "{test_case_method_validate}")
private String method;
@ColumnWidth(50)
@ExcelProperty("前置条件")
@ExcelProperty("{test_case_prerequisite}")
@Length(min=0, max=1000)
private String prerequisite;
@ColumnWidth(50)
@ExcelProperty("备注")
@ExcelProperty("{test_case_remark}")
@Length(max=1000)
private String remark;
@ColumnWidth(50)
@ExcelProperty("步骤描述")
@ExcelProperty("{test_case_step_desc}")
@Length(max=1000)
private String stepDesc;
@ColumnWidth(50)
@ExcelProperty("预期结果")
@ExcelProperty("{test_case_step_result}")
@Length(max=1000)
private String stepResult;
}

View File

@ -6,24 +6,22 @@ import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.util.StringUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.utils.ExcelValidateHelper;
import io.metersphere.excel.domain.ExcelErrData;
import io.metersphere.excel.utils.EasyExcelI18nTranslator;
import io.metersphere.excel.utils.ExcelValidateHelper;
import io.metersphere.i18n.Translator;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.*;
import java.util.*;
@Component
public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
protected List<ExcelErrData<T>> errList = new ArrayList<>();
protected List<T> list = new ArrayList<>();
protected EasyExcelI18nTranslator easyExcelI18nTranslator;
/**
* 每隔2000条存储数据库然后清理list 方便内存回收
*/
@ -31,16 +29,15 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
protected Class<T> clazz;
@Resource
ExcelValidateHelper excelValidateHelper;
public EasyExcelListener(){
Type type = getClass().getGenericSuperclass();
this.clazz = (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
this.easyExcelI18nTranslator = new EasyExcelI18nTranslator(this.clazz);
this.easyExcelI18nTranslator.translateExcelProperty();
}
/**
* 这个条数据解析都会调用
* 每条数据解析都会调用
*
* @param t
* @param analysisContext
@ -51,7 +48,7 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
try {
//根据excel数据实体中的javax.validation + 正则表达式来校验excel数据
errMsg = excelValidateHelper.validateEntity(t);
errMsg = ExcelValidateHelper.validateEntity(t);
//自定义校验规则
errMsg = validate(t, errMsg);
} catch (NoSuchFieldException e) {
@ -104,7 +101,6 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
super.invokeHeadMap(headMap, context);
if (clazz != null){
try {
Set<String> fieldNameSet = getFieldNameSet(clazz);
@ -118,6 +114,7 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
e.printStackTrace();
}
}
super.invokeHeadMap(headMap, context);
}
/**
@ -142,11 +139,11 @@ public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
return result;
}
public List<ExcelErrData<T>> getAndClearErrList() {
List<ExcelErrData<T>> tmp = this.errList;
this.errList = new ArrayList<>();
return tmp;
public List<ExcelErrData<T>> getErrList() {
return errList;
}
public void close () {
this.easyExcelI18nTranslator.resetExcelProperty();
}
}

View File

@ -2,26 +2,23 @@ package io.metersphere.excel.listener;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.base.domain.TestCaseWithBLOBs;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.i18n.Translator;
import io.metersphere.track.service.TestCaseService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Component
public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
@Resource
private TestCaseService testCaseService;
private String projectId;
@ -30,13 +27,11 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
Set<String> userIds;
public TestCaseDataListener() {}
public TestCaseDataListener init(String projectId, Set<String> testCaseNames, Set<String> userIds) {
public TestCaseDataListener(TestCaseService testCaseService, String projectId, Set<String> testCaseNames, Set<String> userIds) {
this.testCaseService = testCaseService;
this.projectId = projectId;
this.testCaseNames = testCaseNames;
this.userIds = userIds;
return this;
}
@Override

View File

@ -0,0 +1,52 @@
package io.metersphere.excel.utils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.exception.ExcelException;
import io.metersphere.i18n.Translator;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class EasyExcelExporter {
EasyExcelI18nTranslator easyExcelI18nTranslator;
public EasyExcelExporter() {
easyExcelI18nTranslator = new EasyExcelI18nTranslator(TestCaseExcelData.class);
easyExcelI18nTranslator.translateExcelProperty();
}
public void export(HttpServletResponse response, Class clazz, List data, String fileName, String sheetName) {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
try {
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);
} catch (UnsupportedEncodingException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("Utf-8 encoding is not supported");
} catch (IOException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("IO exception");
}
}
public void close() {
easyExcelI18nTranslator.resetExcelProperty();
}
}

View File

@ -0,0 +1,98 @@
package io.metersphere.excel.utils;
import com.alibaba.excel.annotation.ExcelProperty;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.exception.ExcelException;
import io.metersphere.i18n.Translator;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
/**
* 表头国际化
* 先调用 saveOriginalExcelProperty 存储原表头注解值
* 再调用 translateExcelProperty 获取国际化的值
* 最后调用 resetExcelProperty 重置为原来值防止切换语言后无法国际化
*/
public class EasyExcelI18nTranslator {
private Map<String, List<String>> excelPropertyMap = new HashMap<>();
private Class clazz;
public EasyExcelI18nTranslator(Class clazz) {
this.clazz = clazz;
saveOriginalExcelProperty();
}
private void readExcelProperty(Class clazz, BiConsumer<String, Map<String, Object>> operate) {
Field field;
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length ; i++) {
try {
field = clazz.getDeclaredField(fields[i].getName());
field.setAccessible(true);
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if(excelProperty != null){
InvocationHandler invocationHandler = Proxy.getInvocationHandler(excelProperty);
Field fieldValue = invocationHandler.getClass().getDeclaredField("memberValues");
fieldValue.setAccessible(true);
Map<String, Object> memberValues = null;
try {
memberValues = (Map<String, Object>) fieldValue.get(invocationHandler);
} catch (IllegalAccessException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException(e.getMessage());
}
operate.accept(field.getName(), memberValues);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage(), e);
}
}
}
public void saveOriginalExcelProperty() {
readExcelProperty(clazz, (fieldName, memberValues) -> {
List<String> values = Arrays.asList((String [])memberValues.get("value"));
List<String> copyValues = new ArrayList<>();
values.forEach(value -> {
copyValues.add(value);
});
excelPropertyMap.put(fieldName, copyValues);
});
}
public void translateExcelProperty() {
readExcelProperty(TestCaseExcelData.class, (fieldName, memberValues) -> {
String [] values = (String[]) memberValues.get("value");
for (int j = 0; j < values.length; j++) {
if (Pattern.matches("^\\{.+\\}$", values[j])) {
values[j] = Translator.get(values[j].substring(1, values[j].length() - 1));
}
}
memberValues.put("value", values);
});
}
public void resetExcelProperty() {
readExcelProperty(clazz, (fieldName, memberValues) -> {
String [] values = (String[]) memberValues.get("value");
List<String> list = excelPropertyMap.get(fieldName);
for (int j = 0; j < values.length; j++) {
values[j] = list.get(j);
}
memberValues.put("value", values);
});
}
}

View File

@ -1,30 +0,0 @@
package io.metersphere.excel.utils;
import com.alibaba.excel.EasyExcel;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.exception.ExcelException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
public class EasyExcelUtil {
public static void export(HttpServletResponse response, Class clazz, List data, String fileName, String sheetName) {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
try {
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);
} catch (UnsupportedEncodingException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("不支持UTF-8编码");
} catch (IOException e) {
LogUtil.error(e.getMessage(), e);
throw new ExcelException("IO异常");
}
}
}

View File

@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelProperty;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.groups.Default;
@ -13,14 +14,14 @@ import java.util.Set;
@Component
public class ExcelValidateHelper {
private ExcelValidateHelper(){}
private static ExcelValidateHelper excelValidateHelper;
@Resource
LocalValidatorFactoryBean localValidatorFactoryBean;
public <T> String validateEntity(T obj) throws NoSuchFieldException {
public static <T> String validateEntity(T obj) throws NoSuchFieldException {
StringBuilder result = new StringBuilder();
Set<ConstraintViolation<T>> set = localValidatorFactoryBean.getValidator().validate(obj, Default.class);
Set<ConstraintViolation<T>> set = excelValidateHelper.localValidatorFactoryBean.getValidator().validate(obj, Default.class);
if (set != null && !set.isEmpty()) {
for (ConstraintViolation<T> cv : set) {
Field declaredField = obj.getClass().getDeclaredField(cv.getPropertyPath().toString());
@ -31,4 +32,10 @@ public class ExcelValidateHelper {
}
return result.toString();
}
@PostConstruct
public void initialize() {
excelValidateHelper = this;
excelValidateHelper.localValidatorFactoryBean = this.localValidatorFactoryBean;
}
}

View File

@ -77,7 +77,7 @@ public class TestCaseController {
}
@PostMapping("/import/{projectId}")
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId){
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId) throws NoSuchFieldException {
return testCaseService.testCaseImport(file, projectId);
}

View File

@ -9,14 +9,13 @@ import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.excel.domain.ExcelErrData;
import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.excel.domain.TestCaseExcelData;
import io.metersphere.excel.listener.EasyExcelListener;
import io.metersphere.excel.listener.TestCaseDataListener;
import io.metersphere.excel.utils.EasyExcelUtil;
import io.metersphere.excel.utils.EasyExcelExporter;
import io.metersphere.i18n.Translator;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
@ -31,7 +30,6 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@ -60,9 +58,6 @@ public class TestCaseService {
@Resource
TestCaseNodeService testCaseNodeService;
@Resource
TestCaseDataListener testCaseDataListener;
@Resource
UserMapper userMapper;
@ -173,8 +168,6 @@ public class TestCaseService {
public ExcelResponse testCaseImport(MultipartFile file, String projectId) {
try {
ExcelResponse excelResponse = new ExcelResponse();
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
@ -190,10 +183,17 @@ public class TestCaseService {
List<User> users = userMapper.selectByExample(userExample);
Set<String> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
EasyExcelFactory.read(file.getInputStream(), TestCaseExcelData.class,
testCaseDataListener.init(projectId, testCaseNames, userIds)).sheet().doRead();
List<ExcelErrData<TestCaseExcelData>> errList = testCaseDataListener.getAndClearErrList();
EasyExcelListener easyExcelListener = null;
List<ExcelErrData<TestCaseExcelData>> errList = null;
try {
easyExcelListener = new TestCaseDataListener(this, projectId, testCaseNames, userIds);
EasyExcelFactory.read(file.getInputStream(), TestCaseExcelData.class, easyExcelListener).sheet().doRead();
errList = easyExcelListener.getErrList();
} catch (Exception e) {
MSException.throwException(e.getMessage());
} finally {
easyExcelListener.close();
}
//如果包含错误信息就导出错误信息
if (!errList.isEmpty()) {
excelResponse.setSuccess(false);
@ -202,13 +202,6 @@ public class TestCaseService {
excelResponse.setSuccess(true);
}
return excelResponse;
} catch (IOException e) {
LogUtil.error(e.getMessage(), e);
e.printStackTrace();
}
return null;
}
public void saveImportData(List<TestCaseWithBLOBs> testCases, String projectId) {
@ -226,8 +219,16 @@ public class TestCaseService {
}
public void testCaseTemplateExport(HttpServletResponse response) {
EasyExcelUtil.export(response, TestCaseExcelData.class, generateExportTemplate(),
EasyExcelExporter easyExcelExporter = null;
try {
easyExcelExporter = new EasyExcelExporter();
easyExcelExporter.export(response, TestCaseExcelData.class, generateExportTemplate(),
Translator.get("test_case_import_template_name"), Translator.get("test_case_import_template_sheet"));
} catch (Exception e) {
MSException.throwException(e);
} finally {
easyExcelExporter.close();
}
}
private List<TestCaseExcelData> generateExportTemplate() {
@ -238,28 +239,29 @@ public class TestCaseService {
SessionUser user = SessionUtils.getUser();
for (int i = 1; i <= 5; i++) {
TestCaseExcelData data = new TestCaseExcelData();
data.setName("测试用例" + i);
path.append("/" + "模块" + i);
data.setName(Translator.get("test_case") + i);
path.append("/" + Translator.get("module") + i);
data.setNodePath(path.toString());
data.setPriority("P" + i%4);
data.setType(types.get(i%3));
data.setMethod(methods.get(i%2));
data.setPrerequisite("前置条件选填");
data.setStepDesc("1. 每个步骤以换行分隔\n2. 步骤前可标序号\n3. 测试步骤和结果选填");
data.setStepResult("1. 每条结果以换行分隔\n2. 结果前可标序号\n3. 测试步骤和结果选填");
data.setPrerequisite(Translator.get("preconditions_optional"));
data.setStepDesc("1. " + Translator.get("step_tip_separate") +
"\n2. " + Translator.get("step_tip_order") + "\n3. " + Translator.get("step_tip_optional"));
data.setStepResult("1. " + Translator.get("step_tip_order") + "\n2. " + Translator.get("result_tip_order") + "\n3. " + Translator.get("result_tip_optional"));
data.setMaintainer(user.getId());
data.setRemark("备注选填");
data.setRemark(Translator.get("remark_optional"));
list.add(data);
}
list.add(new TestCaseExcelData());
TestCaseExcelData explain = new TestCaseExcelData();
explain.setName("同一项目下测试用例名称不能重复!");
explain.setNodePath("模块名称请按照'/模块1/模块2'的格式书写; 错误格式示例:('/', '/tes//test'); 若无该模块,则自动创建模块");
explain.setType("用例类型必须为functional、performance、api");
explain.setMethod("测试方式必须为manual、auto");
explain.setPriority("优先级必须为P0、P1、P2、P3");
explain.setMaintainer("维护人必须为该工作空间相关人员");
explain.setName(Translator.get("do_not_modify_header_order"));
explain.setNodePath(Translator.get("module_created_automatically"));
explain.setType(Translator.get("options") + "functional、performance、api");
explain.setMethod(Translator.get("options") + "manual、auto");
explain.setPriority(Translator.get("options") + "P0、P1、P2、P3");
explain.setMaintainer(Translator.get("please_input_workspace_member"));
list.add(explain);
return list;

View File

@ -18,3 +18,27 @@ incorrect_format=
test_case_type_validate=
test_case_priority_validate=
test_case_method_validate=
test_case_name=
test_case_module=
test_case_type=
test_case_maintainer=
test_case_priority=
test_case_method=
test_case_prerequisite=
test_case_remark=
test_case_step_desc=
test_case_step_result=
test_case=
module=
preconditions_optional=
step_tip_separate=
step_tip_order=
step_tip_optional=
result_tip_separate=
result_tip_order=
result_tip_optional=
remark_optional=
do_not_modify_header_order=
module_created_automatically=
options=
please_input_workspace_member=

View File

@ -48,7 +48,30 @@ missing_header_information=Missing header information
number=Number
row=row
error=error
incorrect_format=Incorrect format
incorrect_format=Maintainer
test_case_type_validate=must be functional, performance, api
test_case_priority_validate=must be P0, P1, P2, P3
test_case_method_validate=\ must be manual, auto
test_case_name=Name
test_case_type=Type
test_case_priority=Priority
test_case_method=method
test_case_prerequisite=Prerequisite
test_case_remark=Remark
test_case_step_desc=Step description
test_case_step_result=Step result
test_case_module=module
test_case=Test case
module=Module
preconditions_optional=Preconditions optional
step_tip_separate=Each step is separated by a new line
step_tip_order=The serial number can be marked before the step
step_tip_optional=Test steps and results optional
result_tip_separate=Each result is separated by a new line
result_tip_order=The sequence number can be marked before the result
result_tip_optional=Test steps and results optional
remark_optional=Remark optional
do_not_modify_header_order=Do not modify the header order
module_created_automatically=If there is no such module, will be created automatically
options=options
please_input_workspace_member=Please input workspace merber

View File

@ -48,9 +48,33 @@ parse_data_error=解析数据出错
missing_header_information=缺少头部信息
number=
row=
incorrect_format=格式不正确
incorrect_format=所属模块
test_case_type_validate=必须为functional、performance、api
test_case_priority_validate=必须为P0、P1、P2、P3
test_case_method_validate=必须为manual、auto
error=出错
test_case_name=用例名称
test_case_type=用例类型
test_case_maintainer=维护人
test_case_priority=优先级
test_case_method=测试方式
test_case_prerequisite=前置条件
test_case_remark=备注
test_case_step_desc=步骤描述
test_case_step_result=预期结果
test_case_module=所属模块
test_case=测试用例
module=模块
preconditions_optional=前置条件选填
step_tip_separate=每个步骤以换行分隔
step_tip_order=步骤前可标序号
step_tip_optional=步骤前可标序号
result_tip_separate=每条结果以换行分隔
result_tip_order=结果前可标序号
result_tip_optional=测试步骤和结果选填
remark_optional=备注选填
do_not_modify_header_order=请勿修改表头顺序
module_created_automatically=若无该模块将自动创建
options=选项
please_input_workspace_member=请填写该工作空间相关人员
#test case end

View File

@ -48,9 +48,33 @@ parse_data_error=解析數據出錯
missing_header_information=缺少頭部信息
number=
row=
incorrect_format=格式不正確
incorrect_format=所屬模塊
test_case_type_validate=必須為functional、performance、api
test_case_priority_validate=必須為P0、P1、P2、P3
test_case_method_validate=必須為manual、auto
error=出错
error=出錯
test_case_name=用例名稱
test_case_type=用例類型
test_case_maintainer=維護人
test_case_priority=優先級
test_case_method=測試方式
test_case_prerequisite=前置條件
test_case_remark=備註
test_case_step_desc=步驟描述
test_case_step_result=預期結果
test_case_module=所屬模塊
test_case=測試用例
module=模塊
preconditions_optional=前置條件選填
step_tip_separate=每個步驟以換行分隔
step_tip_order=步驟前可標序號
step_tip_optional=步驟前可標序號
result_tip_separate=每條結果以換行分隔
result_tip_order=結果前可標序號
result_tip_optional=測試步驟和結果選填
remark_optional=備註選填
do_not_modify_header_order=請勿修改表頭順序
module_created_automatically=若無該模塊將自動創建
options=選項
please_input_workspace_member=請填寫該工作空間相關人員
#test case end