@team-monolith/cds 1.117.12 → 1.117.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/components/AlertDialog/AlertDialogTitle.js +3 -1
  2. package/dist/components/FileTypeAlertDialog.js +3 -1
  3. package/dist/components/Pagination.js +7 -3
  4. package/dist/patterns/LexicalEditor/components/FileSelectInput.js +22 -9
  5. package/dist/patterns/LexicalEditor/components/InsertImageDialog/ImagePreview.js +3 -1
  6. package/dist/patterns/LexicalEditor/components/InsertImageDialog/InsertImageDialog.js +3 -1
  7. package/dist/patterns/LexicalEditor/components/UploadFileDialog/UploadFileDialog.js +2 -1
  8. package/dist/patterns/LexicalEditor/nodes/FileNode/FileDownloadButton.js +5 -3
  9. package/dist/patterns/LexicalEditor/nodes/ImageNode/ImageComponent.js +4 -2
  10. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +7 -4
  11. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormPlaceholder.js +2 -1
  12. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormSolution.js +4 -2
  13. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/SettingForm.js +19 -15
  14. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/TextTypeDropdown.js +6 -2
  15. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxView.js +18 -9
  16. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +8 -3
  17. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/FormSelection.js +23 -4
  18. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +9 -8
  19. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormIconAndLabel/FormIconAndLabel.js +6 -5
  20. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormIconAndLabel/FormLabel.js +7 -5
  21. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormQuestion.js +7 -2
  22. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/SettingForm.js +7 -4
  23. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/EvaluationComponent.js +5 -3
  24. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/iconData.d.ts +1 -1
  25. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/iconData.js +6 -5
  26. package/dist/patterns/LexicalEditor/nodes/SheetInputNode/InputComponent.js +9 -2
  27. package/dist/patterns/LexicalEditor/nodes/SheetInputNode/SettingForm.js +7 -2
  28. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectBox/SelectBoxView.js +10 -2
  29. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectComponent.js +8 -3
  30. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormAllowMultipleAnswers.js +3 -1
  31. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/FormSelection.js +20 -3
  32. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/SettingForm.js +10 -9
  33. package/dist/patterns/LexicalEditor/plugins/ActionsPlugin/index.js +3 -1
  34. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +8 -2
  35. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +33 -31
  36. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +44 -25
  37. package/dist/patterns/LexicalEditor/plugins/DragDropPastePlugin/index.js +2 -1
  38. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/FloatingLinkEditor.js +3 -1
  39. package/dist/patterns/ToggleButtonGroup/ToggleButton.js +4 -2
  40. package/dist/texts.d.ts +12 -0
  41. package/dist/texts.js +14 -0
  42. package/package.json +1 -1
@@ -17,6 +17,7 @@ import { SquareButton } from "../SquareButton";
17
17
  import { CloseFillIcon } from "../../icons";
18
18
  import { AlertDialogContext } from "./AlertDialogContext";
19
19
  import { MOBILE } from "../../foundation/breakpoints";
20
+ import { useTranslation } from "react-i18next";
20
21
  export const alertDialogTitleClasses = {
21
22
  root: "AlertDialogTitle",
22
23
  content: "AlertDialogTitle-content",
@@ -26,6 +27,7 @@ export const alertDialogTitleClasses = {
26
27
  export const AlertDialogTitle = React.forwardRef(function AlertDialogTitle(props, ref) {
27
28
  const { className, component: Component = "div", onClose, children } = props, other = __rest(props, ["className", "component", "onClose", "children"]);
28
29
  const { icon } = React.useContext(AlertDialogContext);
30
+ const { t } = useTranslation();
29
31
  const theme = useTheme();
30
32
  return (_jsx(_Fragment, { children: _jsxs(Component, Object.assign({}, other, { ref: ref, className: [alertDialogTitleClasses.root, className]
31
33
  .filter(Boolean)
@@ -63,5 +65,5 @@ export const AlertDialogTitle = React.forwardRef(function AlertDialogTitle(props
63
65
  height: 32px;
64
66
  }
65
67
  `)}
66
- `, children: icon })), children] }), onClose && (_jsx(SquareButton, { className: alertDialogTitleClasses.closeButton, color: "icon", size: "small", icon: _jsx(CloseFillIcon, {}), onClick: onClose, "aria-label": "\uB2EB\uAE30" }))] })) }));
68
+ `, children: icon })), children] }), onClose && (_jsx(SquareButton, { className: alertDialogTitleClasses.closeButton, color: "icon", size: "small", icon: _jsx(CloseFillIcon, {}), onClick: onClose, "aria-label": t("닫기", { context: "다이얼로그" }) }))] })) }));
67
69
  });
@@ -4,6 +4,7 @@ import { css, useTheme } from "@emotion/react";
4
4
  import { AlertDialog, AlertDialogActions, AlertDialogContent, AlertDialogTitle, } from "./AlertDialog";
5
5
  import { AlertFillIcon } from "../icons";
6
6
  import { Button } from "./Button";
7
+ import { useTranslation } from "react-i18next";
7
8
  export const fileTypeAlertDialogClasses = {
8
9
  root: "FileTypeAlertDialog",
9
10
  title: "FileTypeAlertDialog-title",
@@ -13,11 +14,12 @@ export const fileTypeAlertDialogClasses = {
13
14
  export function FileTypeAlertDialog(props) {
14
15
  const { className, open, onClose, content, openFileBrowser } = props;
15
16
  const theme = useTheme();
17
+ const { t } = useTranslation();
16
18
  return (_jsxs(AlertDialog, { className: [fileTypeAlertDialogClasses.root, className].filter(Boolean).join(" "), open: open, onClose: onClose, icon: _jsx(AlertFillIcon, { css: css `
17
19
  color: ${theme.color.background.danger};
18
20
  ` }), children: [_jsx(AlertDialogTitle, { className: fileTypeAlertDialogClasses.title, css: css `
19
21
  font-weight: 700;
20
- `, onClose: onClose, children: "\uD30C\uC77C \uD615\uC2DD\uC744 \uD655\uC778\uD574\uC8FC\uC138\uC694." }), _jsx(AlertDialogContent, { className: fileTypeAlertDialogClasses.content, children: content }), _jsxs(AlertDialogActions, { className: fileTypeAlertDialogClasses.actions, children: [_jsx(Button, { color: "grey", label: "\uB2EB\uAE30", size: "medium", onClick: onClose, bold: true }), _jsx(Button, { color: "primary", label: "\uB2E4\uC2DC \uC5C5\uB85C\uB4DC", size: "medium", onClick: () => {
22
+ `, onClose: onClose, children: t("파일 형식을 확인해주세요.") }), _jsx(AlertDialogContent, { className: fileTypeAlertDialogClasses.content, children: content }), _jsxs(AlertDialogActions, { className: fileTypeAlertDialogClasses.actions, children: [_jsx(Button, { color: "grey", label: t("닫기", { context: "다이얼로그" }), size: "medium", onClick: onClose, bold: true }), _jsx(Button, { color: "primary", label: t("다시 업로드"), size: "medium", onClick: () => {
21
23
  onClose();
22
24
  openFileBrowser();
23
25
  }, bold: true })] })] }));
@@ -16,6 +16,7 @@ import * as React from "react";
16
16
  import styled from "@emotion/styled";
17
17
  import { Pagination as MuiPagination } from "@mui/material";
18
18
  import { HOVER } from "../utils/hover";
19
+ import { useTranslation } from "react-i18next";
19
20
  export const paginationClasses = {
20
21
  root: "Pagination",
21
22
  };
@@ -24,12 +25,15 @@ export const paginationClasses = {
24
25
  */
25
26
  export const Pagination = React.forwardRef(function Pagination(props, ref) {
26
27
  const { className } = props, other = __rest(props, ["className"]);
28
+ const { t } = useTranslation();
27
29
  return (_jsx(StyledPagination, Object.assign({ siblingCount: 2, shape: "rounded" }, other, { className: [paginationClasses.root, className].filter(Boolean).join(" "), ref: ref, getItemAriaLabel: (type, page, selected) => {
28
30
  if (type === "previous")
29
- return "이전 페이지로 이동";
31
+ return t("이전 페이지로 이동");
30
32
  if (type === "next")
31
- return "다음 페이지로 이동";
32
- return `${page}페이지${selected ? " 선택됨" : ""}`;
33
+ return t("다음 페이지로 이동");
34
+ return selected
35
+ ? t("{{page}}페이지, 선택됨", { page })
36
+ : t("{{page}}페이지", { page });
33
37
  } })));
34
38
  });
35
39
  const StyledPagination = styled(MuiPagination)(({ theme }) => css `
@@ -15,11 +15,23 @@ import { FileWarningLineIcon, UploadLineIcon } from "../../../icons";
15
15
  import { css, useTheme } from "@emotion/react";
16
16
  import styled from "@emotion/styled";
17
17
  import moment from "moment";
18
- const fileTypeMap = {
19
- image: { accept: "image/*", typeStr: "이미지 파일" },
20
- pdf: { accept: ".pdf", typeStr: "PDF 파일" },
21
- file: { accept: "*", typeStr: "모든 파일" },
22
- };
18
+ import { useTranslation } from "react-i18next";
19
+ import { i18n } from "../../../i18n/i18n";
20
+ import { getTexts } from "../../../texts";
21
+ const getFileType = (fileType) => ({
22
+ image: {
23
+ accept: "image/*",
24
+ errorStr: i18n.t("업로드할 수 없는 파일입니다. 이미지 파일을 업로드해주세요."),
25
+ },
26
+ pdf: {
27
+ accept: ".pdf",
28
+ errorStr: i18n.t("업로드할 수 없는 파일입니다. PDF 파일을 업로드해주세요."),
29
+ },
30
+ file: {
31
+ accept: "*",
32
+ errorStr: i18n.t("업로드할 수 없는 파일입니다. 모든 파일을 업로드해주세요."),
33
+ },
34
+ })[fileType];
23
35
  const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
24
36
  /**
25
37
  * 파일 탐색기를 열어 특정 파일을 업로드 한 후, 그 경로를 onChange로 전달합니다.
@@ -31,15 +43,16 @@ export function FileSelectInput(props) {
31
43
  const [errorMessage, setErrorMessage] = useState(null);
32
44
  const inputRef = useRef(null);
33
45
  const cdsContext = useContext(CdsContext);
34
- const { accept, typeStr } = fileTypeMap[fileType];
46
+ const { accept, errorStr } = getFileType(fileType);
35
47
  const theme = useTheme();
48
+ const { t } = useTranslation();
36
49
  const handleFileChange = (event) => __awaiter(this, void 0, void 0, function* () {
37
50
  var _a, _b, _c;
38
51
  const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
39
52
  if (!file)
40
53
  return;
41
54
  if (file.size >= MAX_FILE_SIZE) {
42
- setErrorMessage("용량이 너무 큽니다. 1GB 이하의 파일을 선택해 주세요.");
55
+ setErrorMessage(getTexts("errorFileTooLarge"));
43
56
  event.target.value = "";
44
57
  return;
45
58
  }
@@ -49,7 +62,7 @@ export function FileSelectInput(props) {
49
62
  ? accept.slice(1) === ((_b = file.name.split(".").pop()) === null || _b === void 0 ? void 0 : _b.toLowerCase())
50
63
  : new RegExp(accept.replace("*", ".*")).test(file.type));
51
64
  if (!isTypeValid) {
52
- setErrorMessage(`업로드할 수 없는 파일입니다. ${typeStr}을 업로드해주세요.`);
65
+ setErrorMessage(errorStr);
53
66
  event.target.value = "";
54
67
  return;
55
68
  }
@@ -67,7 +80,7 @@ export function FileSelectInput(props) {
67
80
  }
68
81
  setErrorMessage(null);
69
82
  });
70
- return (_jsxs(_Fragment, { children: [_jsxs(Container, { children: [_jsx(Button, { fullWidth: true, color: "grey", size: "medium", label: "\uD30C\uC77C \uC120\uD0DD\uD558\uAE30", onClick: () => {
83
+ return (_jsxs(_Fragment, { children: [_jsxs(Container, { children: [_jsx(Button, { fullWidth: true, color: "grey", size: "medium", label: t("파일 선택하기"), onClick: () => {
71
84
  var _a;
72
85
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
73
86
  }, startIcon: _jsx(UploadLineIcon, {}), bold: bold }), _jsx(IconAndErrorMessage, { children: errorMessage && (_jsxs(_Fragment, { children: [_jsx(FileWarningLineIcon, { css: css `
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import styled from "@emotion/styled";
3
3
  import { useState } from "react";
4
+ import { useTranslation } from "react-i18next";
4
5
  export function ImagePreview(props) {
5
6
  const { src, alt, fallback } = props;
6
7
  const [erroredSrc, setErroredSrc] = useState(null);
7
- return (_jsxs(Container, { children: ["\uC774\uBBF8\uC9C0 \uBBF8\uB9AC\uBCF4\uAE30", erroredSrc === src ? (fallback) : (_jsx("img", { src: src, alt: alt, onError: () => setErroredSrc(src) }))] }));
8
+ const { t } = useTranslation();
9
+ return (_jsxs(Container, { children: [t("이미지 미리보기"), erroredSrc === src ? (fallback) : (_jsx("img", { src: src, alt: alt, onError: () => setErroredSrc(src) }))] }));
8
10
  }
9
11
  const Container = styled.div `
10
12
  display: flex;
@@ -12,9 +12,11 @@ import { ImagePreview } from "./ImagePreview";
12
12
  import { ImageNotAvailable } from "../../nodes/ImageNode/ImageNotAvailable";
13
13
  import styled from "@emotion/styled";
14
14
  import { debounce } from "lodash";
15
+ import { useTranslation } from "react-i18next";
15
16
  export function InsertImageDialog(props) {
16
17
  const { title, open, onClose, imageProps, onChange, onDelete, shouldReset } = props;
17
18
  const theme = useTheme();
19
+ const { t } = useTranslation();
18
20
  const { control, setValue, watch, reset, handleSubmit, subscribe } = useForm({
19
21
  defaultValues: imageProps !== null && imageProps !== void 0 ? imageProps : { src: "", altText: "" },
20
22
  });
@@ -49,7 +51,7 @@ export function InsertImageDialog(props) {
49
51
  handleSubmit(onSubmit)();
50
52
  }, disableIconPadding: true, children: [_jsx(StyledAlertDialogTitle, { onClose: handleOnClose, children: title }), _jsx(AlertDialogContent, { children: _jsxs(Inputs, { children: [_jsx(FileSelectInput, { onChange: (value) => {
51
53
  setValue("src", value);
52
- }, fileType: "image" }), _jsx(FormInput, { name: "src", control: control, placeholder: "https://www.pexels.com/photo/n-2848492", size: "medium", label: "URL", fullWidth: true, startIcon: _jsx(LinkIcon, {}) }), previewSrc && (_jsx(ImagePreview, { src: previewSrc, alt: watch("altText"), fallback: _jsx(ImageNotAvailable, {}) })), _jsx(FormInput, { name: "altText", control: control, placeholder: "\uC0BD\uC785\uD558\uB294 \uC774\uBBF8\uC9C0\uC5D0 \uAD00\uD55C \uC124\uBA85", size: "medium", label: "\uB300\uCCB4 \uD14D\uC2A4\uD2B8", fullWidth: true })] }) }), _jsxs(AlertDialogActions, { children: [_jsx(Button, { type: "submit", fullWidth: true, label: "\uC0BD\uC785\uD558\uAE30", size: "medium", color: "primary", disabled: isDisabled }), onDelete && (_jsx(Button, { color: "danger", size: "medium", fullWidth: true, label: "\uC0AD\uC81C\uD558\uAE30", onClick: onDelete }))] })] }));
54
+ }, fileType: "image" }), _jsx(FormInput, { name: "src", control: control, placeholder: "https://www.pexels.com/photo/n-2848492", size: "medium", label: "URL", fullWidth: true, startIcon: _jsx(LinkIcon, {}) }), previewSrc && (_jsx(ImagePreview, { src: previewSrc, alt: watch("altText"), fallback: _jsx(ImageNotAvailable, {}) })), _jsx(FormInput, { name: "altText", control: control, placeholder: t("삽입하는 이미지에 관한 설명"), size: "medium", label: t("대체 텍스트"), fullWidth: true })] }) }), _jsxs(AlertDialogActions, { children: [_jsx(Button, { type: "submit", fullWidth: true, label: t("삽입하기"), size: "medium", color: "primary", disabled: isDisabled }), onDelete && (_jsx(Button, { color: "danger", size: "medium", fullWidth: true, label: t("삭제하기"), onClick: onDelete }))] })] }));
53
55
  }
54
56
  const StyledAlertDialog = styled(AlertDialog) `
55
57
  gap: 16px;
@@ -13,6 +13,7 @@ import { useContext, useEffect, useRef } from "react";
13
13
  import moment from "moment";
14
14
  import { CdsContext } from "../../../../CdsProvider";
15
15
  import styled from "@emotion/styled";
16
+ import { getTexts } from "../../../../texts";
16
17
  /**
17
18
  * OS 파일 다이얼로그를 열어 파일을 선택하고 업로드하는 컴포넌트입니다.
18
19
  */
@@ -29,7 +30,7 @@ export function UploadFileDialog({ open, onClose, onChange, }) {
29
30
  }
30
31
  const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
31
32
  if (file.size >= MAX_FILE_SIZE) {
32
- showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", "용량이 너무 큽니다. 1GB 이하의 파일을 선택해 주세요.");
33
+ showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts("errorFileTooLarge"));
33
34
  event.target.value = "";
34
35
  onClose();
35
36
  return;
@@ -15,6 +15,7 @@ import { OverflowTooltip } from "../../../../components/OverflowTooltip";
15
15
  import { useContext } from "react";
16
16
  import { CdsContext } from "../../../../CdsProvider";
17
17
  import { HOVER } from "../../../../utils/hover";
18
+ import { useTranslation } from "react-i18next";
18
19
  /**
19
20
  * 파일 업로드 블록 내에서 삽입하기를 통해 렉시컬에 삽입된 파일블록을 표시하는 컴포넌트입니다. (ReadOnly)
20
21
  * images/2025-02-05-10-53-19.png
@@ -22,18 +23,19 @@ import { HOVER } from "../../../../utils/hover";
22
23
  export function FileDownloadButton({ fileName, fileSize, fileUrl, }) {
23
24
  const cdsContext = useContext(CdsContext);
24
25
  const theme = useTheme();
26
+ const { t } = useTranslation();
25
27
  const handleDownload = () => __awaiter(this, void 0, void 0, function* () {
26
28
  var _a;
27
29
  const showFileError = (_a = cdsContext.lexical) === null || _a === void 0 ? void 0 : _a.showFileError;
28
30
  try {
29
31
  if (!fileUrl) {
30
- showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", "다운로드 중 오류가 발생했어요");
32
+ showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", t("다운로드 중 오류가 발생했어요"));
31
33
  return;
32
34
  }
33
35
  // fileUrl에 대해 GET 요청을 보냅니다.
34
36
  const response = yield fetch(fileUrl, { method: "GET" });
35
37
  if (!response.ok) {
36
- showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", "파일이 삭제되어 다운로드할 수 없어요.");
38
+ showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", t("파일이 삭제되어 다운로드할 수 없어요."));
37
39
  return;
38
40
  }
39
41
  // 요청이 성공하면 응답 데이터를 Blob(파일 데이터)로 변환
@@ -52,7 +54,7 @@ export function FileDownloadButton({ fileName, fileSize, fileUrl, }) {
52
54
  URL.revokeObjectURL(blobUrl);
53
55
  }
54
56
  catch (_b) {
55
- showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", "다운로드 중 오류가 발생했어요");
57
+ showFileError === null || showFileError === void 0 ? void 0 : showFileError("download", t("다운로드 중 오류가 발생했어요"));
56
58
  }
57
59
  });
58
60
  return (_jsxs(ButtonContainer, { onClick: handleDownload, theme: theme, children: [_jsx(IconContainer, { children: getFileIcon(fileName) }), _jsx(FileNameLabel, { children: _jsx(OverflowTooltip, { text: fileName }) }), _jsxs(FileSize, { children: [(fileSize / (1024 * 1024)).toFixed(1), " MB"] })] }));
@@ -15,6 +15,7 @@ import { ImageResizer } from "./ImageResizer";
15
15
  import { css } from "@emotion/react";
16
16
  import { RESET_BUTTON } from "../../../../utils/reset";
17
17
  import { VISUALLY_HIDDEN } from "../../../../utils/visuallyHidden";
18
+ import { useTranslation } from "react-i18next";
18
19
  const imageCache = new Set();
19
20
  export const RIGHT_CLICK_IMAGE_COMMAND = createCommand("RIGHT_CLICK_IMAGE_COMMAND");
20
21
  function useSuspenseImage(src) {
@@ -61,6 +62,7 @@ export function ImageComponent({ src, altText, nodeKey, width, height, maxWidth,
61
62
  const [selection, setSelection] = useState(null);
62
63
  const activeEditorRef = useRef(null);
63
64
  const [isLoadError, setIsLoadError] = useState(false);
65
+ const { t } = useTranslation();
64
66
  const $onDelete = useCallback((payload) => {
65
67
  const deleteSelection = $getSelection();
66
68
  if (isSelected && $isNodeSelection(deleteSelection)) {
@@ -196,7 +198,7 @@ export function ImageComponent({ src, altText, nodeKey, width, height, maxWidth,
196
198
  cursor: pointer;
197
199
  `, src: src, altText: altText, imageRef: imageRef, width: width, height: height, maxWidth: maxWidth, onError: () => setIsLoadError(true), buttonProps: {
198
200
  onClick: () => window.open(src, "_blank"),
199
- }, title: "\uD074\uB9AD\uD574\uC11C \uC6D0\uBCF8 \uC774\uBBF8\uC9C0 \uBCF4\uAE30.", addtionalInfoLabel: _jsx("span", { css: VISUALLY_HIDDEN, children: "(\uC0C8 \uCC3D)" }) })) }) }));
201
+ }, title: t("클릭해서 원본 이미지 보기."), addtionalInfoLabel: _jsx("span", { css: VISUALLY_HIDDEN, children: t("( )", { context: "렉시컬 이미지 도구" }) }) })) }) }));
200
202
  }
201
203
  return (_jsxs(_Fragment, { children: [_jsxs(EditContainer, { children: [_jsx(Suspense, { fallback: null, children: _jsx("div", { draggable: draggable, css: css `
202
204
  // ImageResizer를 위한 relative 설정입니다.
@@ -208,7 +210,7 @@ export function ImageComponent({ src, altText, nodeKey, width, height, maxWidth,
208
210
  // maxWidth={maxWidth}
209
211
  onResizeStart: onResizeStart, onResizeEnd: onResizeEnd, captionsEnabled: false }))] })) }) }), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
210
212
  setOpen(true);
211
- }, "aria-label": "\uC774\uBBF8\uC9C0 \uC218\uC815\uD558\uAE30" })] }), _jsx(InsertImageDialog, { open: open, title: "\uC774\uBBF8\uC9C0 \uC218\uC815\uD558\uAE30", onClose: () => setOpen(false), imageProps: {
213
+ }, "aria-label": t("이미지 수정하기") })] }), _jsx(InsertImageDialog, { open: open, title: t("이미지 수정하기"), onClose: () => setOpen(false), imageProps: {
212
214
  src,
213
215
  altText,
214
216
  }, onChange: (props) => {
@@ -24,10 +24,13 @@ import { $getNodeByKey } from "lexical";
24
24
  import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
25
25
  import { TextInput } from "./TextInput";
26
26
  import { SegmentedInput } from "./SegmentedInput";
27
+ import { useTranslation } from "react-i18next";
28
+ import { getTexts } from "../../../../texts";
27
29
  export function InputComponent(props) {
28
30
  const { answer, isCorrect } = props, settingFormProps = __rest(props, ["answer", "isCorrect"]);
29
31
  const { solutions, showCharacterNumber, placeholder, nodeKey } = settingFormProps;
30
32
  const [editor] = useLexicalComposerContext();
33
+ const { t } = useTranslation();
31
34
  const [settingOpen, setSettingOpen] = useState(false);
32
35
  const isEditable = useLexicalEditable();
33
36
  // SoT를 EditorState로 설정하는 경우 한글 입력시 문제가 생김.
@@ -51,9 +54,9 @@ export function InputComponent(props) {
51
54
  // 학생 view
52
55
  if (!isEditable) {
53
56
  if (showCharacterNumber) {
54
- return (_jsx(SegmentedInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: handleChange }));
57
+ return (_jsx(SegmentedInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || getTexts("placeholderEnterHere"), value: answerInput, onChange: handleChange }));
55
58
  }
56
- return (_jsx(TextInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || "여기에 입력하세요.", value: answerInput, onChange: (e) => {
59
+ return (_jsx(TextInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || getTexts("placeholderEnterHere"), value: answerInput, onChange: (e) => {
57
60
  handleChange(e.target.value);
58
61
  }, fullWidth: true }));
59
62
  }
@@ -62,9 +65,9 @@ export function InputComponent(props) {
62
65
  display: flex;
63
66
  align-items: center;
64
67
  gap: 4px;
65
- `, children: [_jsx(VirtualInput, { onClick: () => setSettingOpen(true), children: "\uC8FC\uAD00\uC2DD \uC785\uB825 \uCE78" }), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
68
+ `, children: [_jsx(VirtualInput, { onClick: () => setSettingOpen(true), children: t("주관식 입력 ", { context: "렉시컬 주관식 입력 도구" }) }), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
66
69
  setSettingOpen(true);
67
- }, "aria-label": "\uC785\uB825 \uCE78 \uC124\uC815" })] }), settingOpen && (_jsx(SettingForm, Object.assign({}, settingFormProps, { onClose: () => setSettingOpen(false) })))] }));
70
+ }, "aria-label": t("입력 설정", { context: "렉시컬 주관식 입력 도구" }) })] }), settingOpen && (_jsx(SettingForm, Object.assign({}, settingFormProps, { onClose: () => setSettingOpen(false) })))] }));
68
71
  }
69
72
  const VirtualInput = styled.div(({ theme }) => css `
70
73
  box-sizing: border-box;
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Controller } from "react-hook-form";
3
3
  import { Input } from "../../../../../components/Input";
4
+ import { getTexts } from "../../../../../texts";
4
5
  export function FormPlaceholder(props) {
5
6
  const { control } = props;
6
- return (_jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: "\uC608) \uC5EC\uAE30\uC5D0 \uC785\uB825\uD558\uC138\uC694." })) }));
7
+ return (_jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: getTexts("exampleEnterHere") })) }));
7
8
  }
@@ -7,18 +7,20 @@ import { AlertFillIcon, DeleteBinLineIcon, ErrorWarningFillIcon, } from "../../.
7
7
  import { SquareButton } from "../../../../../components/SquareButton";
8
8
  import { Tooltip } from "../../../../../components/Tooltip";
9
9
  import { TextTypeDropdown } from "./TextTypeDropdown";
10
+ import { Trans, useTranslation } from "react-i18next";
10
11
  export function FormSolution(props) {
11
12
  const { index, control, rules, onDelete } = props;
12
13
  const theme = useTheme();
14
+ const { t } = useTranslation();
13
15
  return (_jsx(Controller, { name: `solutions.${index}.value`, control: control, rules: rules, render: ({ field: { value, onChange }, fieldState: { invalid, error }, }) => {
14
16
  const disabled = (error === null || error === void 0 ? void 0 : error.type) === "enabled";
15
- return (_jsx(Input, { size: "small", color: invalid ? "activeDanger" : "default", onChange: onChange, disabled: disabled, value: value, hintIcon: !disabled && invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: !disabled ? error === null || error === void 0 ? void 0 : error.message : undefined, placeholder: "\uC548\uB155\uD558\uC138\uC694", fullWidth: true, css: css `
17
+ return (_jsx(Input, { size: "small", color: invalid ? "activeDanger" : "default", onChange: onChange, disabled: disabled, value: value, hintIcon: !disabled && invalid ? _jsx(ErrorWarningFillIcon, {}) : undefined, hintText: !disabled ? error === null || error === void 0 ? void 0 : error.message : undefined, placeholder: t("안녕하세요"), fullWidth: true, css: css `
16
18
  > div {
17
19
  padding: 4px 12px;
18
20
  }
19
21
  `, startIcon: _jsx(TextTypeDropdown, { index: index, control: control, disabled: disabled }), endIcon: _jsxs("div", { css: css `
20
22
  display: flex;
21
23
  gap: 4px;
22
- `, children: [onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete, "aria-label": "\uC0AD\uC81C" })), disabled && (_jsx(Tooltip, { text: _jsxs("span", { children: ["\uC785\uB825 \uCE78 \uC124\uC815\uC774 '\uAE00\uC790 \uC218\uB300\uB85C'\uC778 \uACBD\uC6B0", _jsx("br", {}), "\uC815\uB2F5\uC744 \uD558\uB098\uB9CC \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."] }), children: _jsx(SquareButton, { color: "danger", size: "xsmall", icon: _jsx(AlertFillIcon, { color: theme.color.foreground.neutralAlt }), disabled: true, "aria-label": "\uC815\uB2F5 \uB4F1\uB85D \uBD88\uAC00" }) }))] }) }));
24
+ `, children: [onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete, "aria-label": t("삭제", { context: "스퀘어버튼, 렉시컬 주관식 설정창" }) })), disabled && (_jsx(Tooltip, { text: _jsx(Trans, { i18nKey: "\uC785\uB825 \uCE78 \uC124\uC815\uC774 '\uAE00\uC790 \uC218\uB300\uB85C'\uC778 \uACBD\uC6B0<br/>\uC815\uB2F5\uC744 \uD558\uB098\uB9CC \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4." }), children: _jsx(SquareButton, { color: "danger", size: "xsmall", icon: _jsx(AlertFillIcon, { color: theme.color.foreground.neutralAlt }), disabled: true, "aria-label": t("정답 등록 불가") }) }))] }) }));
23
25
  } }));
24
26
  }
@@ -13,9 +13,13 @@ import { FormSegmentedControl } from "./FormSegmentedControl";
13
13
  import { FormPlaceholder } from "./FormPlaceholder";
14
14
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
15
15
  import { $getNodeByKey } from "lexical";
16
+ import { Trans, useTranslation } from "react-i18next";
17
+ import { getTexts } from "../../../../../texts";
16
18
  export function SettingForm(props) {
17
19
  const { solutions, showCharacterNumber, placeholder, nodeKey, onClose, caseSensitive, ignoreWhitespace, } = props;
18
20
  const [editor] = useLexicalComposerContext();
21
+ const { t } = useTranslation();
22
+ const numCharOptionStr = t("글자 수대로");
19
23
  const { control, handleSubmit, watch, trigger } = useForm({
20
24
  mode: "all",
21
25
  defaultValues: {
@@ -50,19 +54,19 @@ export function SettingForm(props) {
50
54
  return (_jsxs(Form, { onSubmit: handleSubmit(onSettingSubmit), children: [_jsxs(Title, { children: [_jsx(InputMethodLineIcon, { css: css `
51
55
  width: 12px;
52
56
  height: 12px;
53
- ` }), "\uC8FC\uAD00\uC2DD \uC785\uB825 \uCE78"] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: "\uC815\uB2F5" }), fields.map((field, index) => (_jsx(FormSolution, { index: index, control: control, rules: {
57
+ ` }), t("주관식 입력 ", { context: "렉시컬 주관식 설정창" })] }), _jsxs(Content, { children: [_jsxs(Left, { children: [_jsxs(FormArea, { children: [_jsx(Label, { children: t("정답", { context: "소제목, 렉시컬 주관식 설정창" }) }), fields.map((field, index) => (_jsx(FormSolution, { index: index, control: control, rules: {
54
58
  validate: {
55
59
  // required 옵션보다 먼저 검증되어야 하는데 priority 옵션이 없어서 validate에서 통합해서 검증합니다.
56
60
  enabled: () => index === 0 ||
57
61
  !multipleSolutionsDisabled ||
58
- "복수 정답이 불가능합니다.",
59
- required: (value) => value !== "" || "정답을 입력해주세요.",
62
+ t("복수 정답이 불가능합니다."),
63
+ required: (value) => value !== "" || t("정답을 입력해주세요."),
60
64
  },
61
65
  }, onDelete: index !== 0
62
66
  ? () => {
63
67
  remove(index);
64
68
  }
65
- : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: "\uBCF5\uC218 \uC815\uB2F5 \uCD94\uAC00", disabled: multipleSolutionsDisabled, onClick: () => {
69
+ : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: t("복수 정답 추가"), disabled: multipleSolutionsDisabled, onClick: () => {
66
70
  append({
67
71
  textType: "normal",
68
72
  value: "",
@@ -73,22 +77,22 @@ export function SettingForm(props) {
73
77
  `, children: [_jsx(AlarmWarningFillIcon, { color: theme.color.foreground.neutralBaseDisabled, css: css `
74
78
  width: 14px;
75
79
  height: 14px;
76
- ` }), _jsx(Label, { children: "\uAC00\uB2A5\uD55C \uC815\uB2F5\uC744 \uBAA8\uB450 \uCD94\uAC00\uD574\uC57C \uC6D0\uD65C\uD558\uAC8C \uC790\uB3D9 \uCC44\uC810\uD560 \uC218 \uC788\uC5B4\uC694." })] })] }), _jsxs(Right, { children: [_jsxs(FormArea, { children: [_jsxs(Label, { children: ["\uC785\uB825 \uCE78", _jsx(Tooltip, { text: _jsxs("span", { children: [`예를 들어 정답이 '글자 수'이고`, _jsx("br", {}), _jsx("b", { children: "\uAE00\uC790 \uC218\uB300\uB85C" }), " \uC635\uC158\uC744 \uC120\uD0DD\uD588\uB2E4\uBA74", _jsx("br", {}), `입력 칸이 '☐☐ ' 처럼 표시됩니다.`] }), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
80
+ ` }), _jsx(Label, { children: t("가능한 정답을 모두 추가해야 원활하게 자동 채점할 있어요.") })] })] }), _jsxs(Right, { children: [_jsxs(FormArea, { children: [_jsxs(Label, { children: [t("입력 "), _jsx(Tooltip, { text: _jsx(Trans, { i18nKey: "\uC608\uB97C \uB4E4\uC5B4 \uC815\uB2F5\uC774 '\uAE00\uC790 \uC218'\uC774\uACE0<br /><strong>{{numCharOptionStr}}</strong> \uC635\uC158\uC744 \uC120\uD0DD\uD588\uB2E4\uBA74<br />\uC785\uB825 \uCE78\uC774 '\u2610\u2610 \u2610' \uCC98\uB7FC \uD45C\uC2DC\uB429\uB2C8\uB2E4.", value: { numCharOptionStr } }), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
77
81
  width: 12px;
78
82
  height: 12px;
79
83
  ` }) })] }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "showCharacterNumber", items: [
80
- { value: false, label: "한 칸으로" },
81
- { value: true, label: "글자 수대로" },
82
- ] })] }), _jsxs(FormArea, { children: [_jsxs(Label, { children: ["\uC790\uB9AC \uD45C\uC2DC\uC790", _jsx(Tooltip, { text: _jsx("span", { children: "\uC785\uB825 \uCE78\uC5D0 \uAE30\uBCF8\uC73C\uB85C \uB178\uCD9C\uB418\uB294 \uD14D\uC2A4\uD2B8\uC785\uB2C8\uB2E4." }), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
84
+ { value: false, label: t("한 칸으로") },
85
+ { value: true, label: numCharOptionStr },
86
+ ] })] }), _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 주관식 설정창" }), _jsx(Tooltip, { text: getTexts("descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
83
87
  width: 12px;
84
88
  height: 12px;
85
- ` }) })] }), _jsx(FormPlaceholder, { control: control })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: "\uB744\uC5B4\uC4F0\uAE30" }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "ignoreWhitespace", items: [
86
- { value: true, label: "무시하기" },
87
- { value: false, label: "포함하기" },
88
- ] })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: "\uB300\uC18C\uBB38\uC790" }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "caseSensitive", items: [
89
- { value: false, label: "무시하기" },
90
- { value: true, label: "포함하기" },
91
- ] })] })] })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: "\uB2EB\uAE30", onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: "\uC774\uB300\uB85C \uB123\uAE30", type: "submit" })] })] }));
89
+ ` }) })] }), _jsx(FormPlaceholder, { control: control })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: t("띄어쓰기") }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "ignoreWhitespace", items: [
90
+ { value: true, label: t("무시하기") },
91
+ { value: false, label: t("포함하기") },
92
+ ] })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: t("대소문자") }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "caseSensitive", items: [
93
+ { value: false, label: t("무시하기") },
94
+ { value: true, label: t("포함하기") },
95
+ ] })] })] })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: t("닫기", { context: "렉시컬 도구 설정창" }), onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: t("이대로 넣기", { context: "렉시컬 도구 설정창" }), type: "submit" })] })] }));
92
96
  }
93
97
  const Form = styled.form(({ theme }) => css `
94
98
  display: flex;
@@ -2,10 +2,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Controller } from "react-hook-form";
3
3
  import { css, useTheme } from "@emotion/react";
4
4
  import { Dropdown, DropdownItem } from "../../../../..";
5
+ import { useTranslation } from "react-i18next";
5
6
  export function TextTypeDropdown(props) {
6
7
  const theme = useTheme();
7
8
  const { index, control, disabled } = props;
8
- return (_jsx(Controller, { name: `solutions.${index}.textType`, control: control, render: ({ field: { value, onChange } }) => (_jsx(Dropdown, { label: value === "normal" ? "일반 텍스트" : "코드 텍스트", size: "xsmall", color: "textNeutral", closeOnItemClick: true, disabled: disabled, buttonCss: css `
9
+ const { t } = useTranslation();
10
+ const normalLabel = t("일반 텍스트");
11
+ const codeLabel = t("코드 텍스트");
12
+ return (_jsx(Controller, { name: `solutions.${index}.textType`, control: control, render: ({ field: { value, onChange } }) => (_jsx(Dropdown, { label: value === "normal" ? normalLabel : codeLabel, size: "xsmall", color: "textNeutral", closeOnItemClick: true, disabled: disabled, buttonCss: css `
9
13
  ${disabled && `color: ${theme.color.foreground.neutralAlt};`}
10
14
  > span {
11
15
  font-weight: 700;
@@ -19,7 +23,7 @@ export function TextTypeDropdown(props) {
19
23
  vertical: "top",
20
24
  horizontal: "center",
21
25
  },
22
- }, children: _jsx(DropdownItem, { index: 0, label: value === "normal" ? "코드 텍스트" : "일반 텍스트", onClick: () => {
26
+ }, children: _jsx(DropdownItem, { index: 0, label: value === "normal" ? codeLabel : normalLabel, onClick: () => {
23
27
  onChange(value === "normal" ? "code" : "normal");
24
28
  } }) })) }));
25
29
  }
@@ -6,6 +6,8 @@ import { SelectBoxComponent } from "./SelectBoxComponent";
6
6
  import { Tag } from "../../../../../components/Tag";
7
7
  import { useRef } from "react";
8
8
  import { useEventListener } from "usehooks-ts";
9
+ import { i18n } from "../../../../../i18n/i18n";
10
+ import { useTranslation } from "react-i18next";
9
11
  const TYPE_TO_INDEX_ICON = (type) => ({
10
12
  primary: (_jsx(CheckFillIcon, { css: css `
11
13
  width: 12px;
@@ -28,30 +30,35 @@ function getTypeAndDescription(isSelected, isAnswer, onClick) {
28
30
  if (isAnswer === undefined) {
29
31
  return {
30
32
  type: isSelected ? "primary" : undefined,
31
- description: isSelected ? "선택됨" : onClick ? "선택하기" : "",
33
+ description: isSelected
34
+ ? i18n.t("선택됨")
35
+ : onClick
36
+ ? i18n.t("선택하기")
37
+ : "",
32
38
  };
33
39
  }
34
40
  if (isAnswer) {
35
41
  return {
36
42
  type: isSelected ? "success" : undefined,
37
43
  description: isSelected
38
- ? "선택됨, 정답"
44
+ ? i18n.t("선택됨, 정답", { context: "렉시컬 선택지 하나의 상태" })
39
45
  : onClick
40
- ? "선택하기, 정답"
41
- : "정답",
46
+ ? i18n.t("선택하기, 정답", { context: "렉시컬 선택지 하나의 상태" })
47
+ : i18n.t("정답", { context: "렉시컬 선택지 하나의 상태" }),
42
48
  };
43
49
  }
44
50
  return {
45
51
  type: isSelected ? "danger" : undefined,
46
52
  description: isSelected
47
- ? "선택됨, 오답"
53
+ ? i18n.t("선택됨, 오답", { context: "렉시컬 선택지 하나의 상태" })
48
54
  : onClick
49
- ? "선택하기, 오답"
50
- : "오답",
55
+ ? i18n.t("선택하기, 오답", { context: "렉시컬 선택지 하나의 상태" })
56
+ : i18n.t("오답", { context: "렉시컬 선택지 하나의 상태" }),
51
57
  };
52
58
  }
53
59
  export function SelectBoxView(props) {
54
60
  const { multipleSelectionsEnabled, index, isSelected, isAnswer, image, text, onClick, } = props;
61
+ const { t } = useTranslation();
55
62
  const { type, description } = getTypeAndDescription(isSelected, isAnswer, onClick);
56
63
  const boxRef = useRef(null);
57
64
  const handleKeyDown = (e) => {
@@ -71,13 +78,15 @@ export function SelectBoxView(props) {
71
78
  };
72
79
  useEventListener("keydown", handleKeyDown);
73
80
  // text가 있으면 text, 없으면 image의 altText, 없으면 index를 aria-label로 사용
74
- const ariaText = text || (image && image.altText) || index.toString() + "번 선택지";
81
+ const ariaText = text ||
82
+ (image && image.altText) ||
83
+ t("{{index}}번 선택지", { index, context: "렉시컬 선택지 도구" });
75
84
  return (_jsx(SelectBoxComponent, { css: css `
76
85
  width: 100%;
77
86
  `, ref: boxRef, role: multipleSelectionsEnabled ? "checkbox" : "radio", "aria-checked": isSelected, "aria-label": [ariaText, description].filter(Boolean).join(" "), tabIndex: onClick ? 0 : -1, type: type, index: type ? TYPE_TO_INDEX_ICON(type) : index, image: image, text: text, onClick: onClick,
78
87
  // 선택되지 않았으나 정답일 때 정답 태그를 표시
79
88
  endIcon: isAnswer &&
80
- !isSelected && (_jsx(Tag, { label: "\uC815\uB2F5", icon: _jsx(CheckboxCircleFillIcon, {}), color: "green", css: css `
89
+ !isSelected && (_jsx(Tag, { label: t("정답", { context: "태그, 렉시컬 선택지" }), icon: _jsx(CheckboxCircleFillIcon, {}), color: "green", css: css `
81
90
  span {
82
91
  font-weight: 700;
83
92
  }
@@ -12,9 +12,11 @@ import { AlarmWarningFillIcon, Settings3FillIcon } from "../../../../icons";
12
12
  import { SettingForm } from "./SettingForm";
13
13
  import styled from "@emotion/styled";
14
14
  import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
15
+ import { useTranslation } from "react-i18next";
15
16
  export function SelectComponent(props) {
16
17
  const { selected, hasMultipleSolutions, selections, nodeKey } = props;
17
18
  const [editor] = useLexicalComposerContext();
19
+ const { t } = useTranslation();
18
20
  const [settingOpen, setSettingOpen] = useState(false);
19
21
  const isEditable = useLexicalEditable();
20
22
  const { freezeProblemNode, showQuizSolution } = useContext(LexicalCustomConfigContext);
@@ -33,7 +35,7 @@ export function SelectComponent(props) {
33
35
  `, children: [multipleSelectionsEnabled && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
34
36
  width: 14px;
35
37
  height: 14px;
36
- ` }), "\uC9C8\uBB38\uC5D0 \uD574\uB2F9\uD558\uB294 \uB2F5\uC744 \uBAA8\uB450 \uACE0\uB974\uB294 \uBB38\uC81C\uC785\uB2C8\uB2E4."] })), selections.map((selection, index) => (_jsx(SelectBoxView, { multipleSelectionsEnabled: multipleSelectionsEnabled, index: index + 1, isAnswer: showQuizSolution && "isAnswer" in selection
38
+ ` }), t("질문에 해당하는 답을 모두 고르는 문제입니다.")] })), selections.map((selection, index) => (_jsx(SelectBoxView, { multipleSelectionsEnabled: multipleSelectionsEnabled, index: index + 1, isAnswer: showQuizSolution && "isAnswer" in selection
37
39
  ? selection.isAnswer
38
40
  : undefined, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
39
41
  ? undefined
@@ -73,10 +75,13 @@ export function SelectComponent(props) {
73
75
  flex-direction: column;
74
76
  gap: 4px;
75
77
  `, children: selections.map((selection, index) => (_jsx(SelectBoxEdit, { index: index + 1, isAnswer: "isAnswer" in selection && selection.isAnswer, image: selection.show.image, text: selection.show.text === "" && selection.show.image === null
76
- ? `${index + 1}번 선택지`
78
+ ? t("{{index}}선택지", {
79
+ index: index + 1,
80
+ context: "렉시컬 선택지 도구",
81
+ })
77
82
  : selection.show.text, onClick: () => setSettingOpen(true) }, index))) }), _jsx(SquareButton, { size: "small", color: "icon", icon: _jsx(Settings3FillIcon, {}), onClick: () => {
78
83
  setSettingOpen(true);
79
- }, "aria-label": "\uC120\uD0DD\uC9C0 \uC124\uC815" })] }), settingOpen && (_jsx(SettingForm, { selections: selections, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
84
+ }, "aria-label": t("선택지 설정", { context: "렉시컬 선택지 도구" }) })] }), settingOpen && (_jsx(SettingForm, { selections: selections, nodeKey: nodeKey, onClose: () => setSettingOpen(false) }))] }));
80
85
  }
81
86
  const Alert = styled.div(({ theme }) => css `
82
87
  display: flex;