@team-monolith/cds 1.117.20 → 1.118.0

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 (33) hide show
  1. package/dist/CdsProvider.d.ts +2 -2
  2. package/dist/CdsProvider.js +1 -11
  3. package/dist/i18n/i18n.d.ts +13 -3
  4. package/dist/i18n/i18n.js +31 -33
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +1 -0
  7. package/dist/patterns/LexicalEditor/components/FileSelectInput.js +7 -8
  8. package/dist/patterns/LexicalEditor/components/UploadFileDialog/UploadFileDialog.js +3 -1
  9. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/InputComponent.js +2 -2
  10. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/FormPlaceholder.js +3 -1
  11. package/dist/patterns/LexicalEditor/nodes/ProblemInputNode/SettingForm/SettingForm.js +1 -1
  12. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectBox/SelectBoxView.js +15 -14
  13. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SelectComponent.js +2 -1
  14. package/dist/patterns/LexicalEditor/nodes/ProblemSelectNode/SettingForm/SettingForm.js +3 -3
  15. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormIconAndLabel/FormIconAndLabel.js +2 -2
  16. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormIconAndLabel/FormLabel.js +5 -3
  17. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/FormQuestion.js +1 -1
  18. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/Evaluation/SettingForm/SettingForm.js +1 -1
  19. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/EvaluationComponent/EvaluationComponent.js +1 -1
  20. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/iconData.d.ts +2 -1
  21. package/dist/patterns/LexicalEditor/nodes/SelfEvaluationNode/iconData.js +4 -5
  22. package/dist/patterns/LexicalEditor/nodes/SheetInputNode/InputComponent.js +1 -1
  23. package/dist/patterns/LexicalEditor/nodes/SheetInputNode/SettingForm.js +2 -2
  24. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SelectComponent.js +2 -1
  25. package/dist/patterns/LexicalEditor/nodes/SheetSelectNode/SelectComponent/SettingForm/SettingForm.js +3 -3
  26. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +1 -0
  27. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useContextMenuOptions.js +58 -56
  28. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +2 -0
  29. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +29 -33
  30. package/dist/patterns/LexicalEditor/plugins/DragDropPastePlugin/index.js +4 -2
  31. package/dist/texts.d.ts +3 -1
  32. package/dist/texts.js +11 -11
  33. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { Theme } from "@emotion/react";
2
- import { i18nextOption } from "./i18n/i18n";
2
+ import { i18n } from "i18next";
3
3
  export declare const light: Theme;
4
4
  export declare const dark: Theme;
5
5
  interface CdsContext {
@@ -8,11 +8,11 @@ interface CdsContext {
8
8
  uploadByFile: (file: File) => Promise<string>;
9
9
  showFileError: (type: "upload" | "download", message: string) => void;
10
10
  };
11
- i18nextOption?: i18nextOption;
12
11
  }
13
12
  export declare const CdsContext: import("react").Context<CdsContext>;
14
13
  export declare function CdsProvider(props: {
15
14
  children: React.ReactNode;
16
15
  fontFamily?: string;
16
+ i18n: i18n;
17
17
  } & CdsContext): React.ReactElement;
18
18
  export {};
@@ -5,7 +5,6 @@ import { COLOR } from "./foundation/color";
5
5
  import { FONT } from "./foundation/font";
6
6
  import { createTheme } from "@mui/material";
7
7
  import { createContext } from "react";
8
- import { initI18nInstance, i18n } from "./i18n/i18n";
9
8
  import { I18nextProvider } from "react-i18next";
10
9
  export const light = {
11
10
  fontFamily: {
@@ -311,15 +310,6 @@ export const dark = {
311
310
  };
312
311
  export const CdsContext = createContext({});
313
312
  export function CdsProvider(props) {
314
- // AIDEV-NOTE:
315
- // CdsProvider가 렌더링 될 때 i18n 인스턴스가 초기화되지 않았다면, 초기화합니다.
316
- // 여기에 useEffect를 사용한다면, 마운트 시에 i18n 초기화가 되어있지 않을 때
317
- // react-i18next가 suspense를 사용하게 되어 컴포넌트가 언마운트 되므로,
318
- // useEffect가 실행이 되지 않는 문제가 있습니다.
319
- // 따라서 useEffect를 사용하지 않고, 동기적으로 i18n 초기화를 수행합니다.
320
- if (!i18n.isInitialized && !i18n.isInitializing) {
321
- initI18nInstance(props.i18nextOption);
322
- }
323
313
  const muiTheme = createTheme({
324
314
  typography: {
325
315
  fontFamily: props.fontFamily,
@@ -332,7 +322,7 @@ export function CdsProvider(props) {
332
322
  },
333
323
  },
334
324
  });
335
- const { theme = light } = props;
325
+ const { theme = light, i18n } = props;
336
326
  return (
337
327
  /**
338
328
  * AIDEV-NOTE:
@@ -1,8 +1,18 @@
1
- export type i18nextOption = {
1
+ import { i18n, TFunction } from "i18next";
2
+ type i18nextOption = {
2
3
  environment: string;
3
4
  mainLanguage: string;
4
5
  projectId: string;
5
6
  apiKey?: string;
6
7
  };
7
- export declare const i18n: import("i18next").i18n;
8
- export declare function initI18nInstance(option: i18nextOption | undefined): Promise<import("i18next").TFunction<"translation", undefined>>;
8
+ /**
9
+ * i18n 인스턴스를 생성하고 초기화합니다.
10
+ * @returns
11
+ * - i18n: i18n 인스턴스
12
+ * - ready: i18n 초기화가 완료된 후에 resolve 되는 Promise
13
+ */
14
+ export declare function createAndInitI18n(option?: i18nextOption): {
15
+ i18n: i18n;
16
+ ready: Promise<TFunction>;
17
+ };
18
+ export {};
package/dist/i18n/i18n.js CHANGED
@@ -1,12 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import i18next from "i18next";
11
2
  import LanguageDetector from "i18next-browser-languagedetector";
12
3
  import Backend from "i18next-locize-backend";
@@ -24,28 +15,35 @@ const CONTAINS_KOREAN = typeof DETECTED_LANGUAGE === "string"
24
15
  : Array.isArray(DETECTED_LANGUAGE)
25
16
  ? DETECTED_LANGUAGE.some((lang) => lang.startsWith("ko"))
26
17
  : false;
27
- // AIDEV-NOTE:
28
- // i18next 싱글톤 인스턴스를 사용하는 대신, CDS가 호스트 애플리케이션과 독립적으로
29
- // i18n 인스턴스를 관리할 수 있도록 createInstance()를 사용합니다.
30
- export const i18n = i18next
31
- .createInstance()
32
- .use(languageDetector)
33
- .use(initReactI18next);
34
- export function initI18nInstance(option) {
35
- return __awaiter(this, void 0, void 0, function* () {
36
- if (!option) {
37
- // option이 없는 경우, backend 플러그인 없이 기본 i18next 설정을 사용하여,
38
- // fallbackValue(=key) 항상 사용하게 됩니다.
39
- return i18n.init(Object.assign(Object.assign({}, I18N_COMMON_CONFIGS), { fallbackLng: false }));
40
- }
41
- /**
42
- * AIDEV-NOTE:
43
- * Locize의 사용량을 최적화하기 위해, Locize를 다음과 같은 상황에서만 활성화합니다:
44
- * 1. 프로덕션 환경이 아닐 때 (개발 or 로컬 환경) 또는,
45
- * 2. 언어 설정이 한국어가 아닐 때
46
- */
47
- const LOCIZE_ENABLED = option.environment !== "production" || !CONTAINS_KOREAN;
48
- return (LOCIZE_ENABLED ? i18n.use(Backend).use(locizePlugin) : i18n).init(Object.assign(Object.assign({}, I18N_COMMON_CONFIGS), { debug: option.environment !== "production",
18
+ /**
19
+ * i18n 인스턴스를 생성하고 초기화합니다.
20
+ * @returns
21
+ * - i18n: i18n 인스턴스
22
+ * - ready: i18n 초기화가 완료된 후에 resolve 되는 Promise
23
+ */
24
+ export function createAndInitI18n(option) {
25
+ const i18n = i18next
26
+ .createInstance()
27
+ .use(languageDetector)
28
+ .use(initReactI18next);
29
+ if (!option) {
30
+ // option이 없는 경우, backend 플러그인 없이 기본 i18next 설정을 사용하여,
31
+ // fallbackValue(=key)를 항상 사용하게 됩니다.
32
+ return {
33
+ i18n,
34
+ ready: i18n.init(Object.assign(Object.assign({}, I18N_COMMON_CONFIGS), { fallbackLng: false })),
35
+ };
36
+ }
37
+ /**
38
+ * AIDEV-NOTE:
39
+ * Locize의 사용량을 최적화하기 위해, Locize를 다음과 같은 상황에서만 활성화합니다:
40
+ * 1. 프로덕션 환경이 아닐 때 (개발 or 로컬 환경) 또는,
41
+ * 2. 언어 설정이 한국어가 아닐 때
42
+ */
43
+ const LOCIZE_ENABLED = option.environment !== "production" || !CONTAINS_KOREAN;
44
+ return {
45
+ i18n,
46
+ ready: (LOCIZE_ENABLED ? i18n.use(Backend).use(locizePlugin) : i18n).init(Object.assign(Object.assign({}, I18N_COMMON_CONFIGS), { debug: option.environment !== "production",
49
47
  // AIDEV-NOTE:
50
48
  // - 유저의 언어가 Korean인 경우:
51
49
  // fallbackLng를 false로 설정하여, 번역이 없는 경우 key 자체를 반환하도록 합니다.
@@ -58,6 +56,6 @@ export function initI18nInstance(option) {
58
56
  projectId: option.projectId,
59
57
  apiKey: option.apiKey,
60
58
  referenceLng: "ko",
61
- } }));
62
- });
59
+ } })),
60
+ };
63
61
  }
package/dist/index.d.ts CHANGED
@@ -37,3 +37,4 @@ export * from "./patterns/LexicalEditor";
37
37
  export * from "./patterns/Accordion";
38
38
  export * from "./patterns/ToggleButtonGroup";
39
39
  export * from "./patterns/Tabs";
40
+ export { createAndInitI18n } from "./i18n/i18n";
package/dist/index.js CHANGED
@@ -37,3 +37,4 @@ export * from "./patterns/LexicalEditor";
37
37
  export * from "./patterns/Accordion";
38
38
  export * from "./patterns/ToggleButtonGroup";
39
39
  export * from "./patterns/Tabs";
40
+ export { createAndInitI18n } from "./i18n/i18n";
@@ -16,20 +16,19 @@ import { css, useTheme } from "@emotion/react";
16
16
  import styled from "@emotion/styled";
17
17
  import moment from "moment";
18
18
  import { useTranslation } from "react-i18next";
19
- import { i18n } from "../../../i18n/i18n";
20
19
  import { getTexts } from "../../../texts";
21
- const getFileType = (fileType) => ({
20
+ const getFileType = (fileType, t) => ({
22
21
  image: {
23
22
  accept: "image/*",
24
- errorStr: i18n.t("업로드할 수 없는 파일입니다. 이미지 파일을 업로드해주세요."),
23
+ errorStr: t("업로드할 수 없는 파일입니다. 이미지 파일을 업로드해주세요."),
25
24
  },
26
25
  pdf: {
27
26
  accept: ".pdf",
28
- errorStr: i18n.t("업로드할 수 없는 파일입니다. PDF 파일을 업로드해주세요."),
27
+ errorStr: t("업로드할 수 없는 파일입니다. PDF 파일을 업로드해주세요."),
29
28
  },
30
29
  file: {
31
30
  accept: "*",
32
- errorStr: i18n.t("업로드할 수 없는 파일입니다. 모든 파일을 업로드해주세요."),
31
+ errorStr: t("업로드할 수 없는 파일입니다. 모든 파일을 업로드해주세요."),
33
32
  },
34
33
  })[fileType];
35
34
  const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@@ -42,17 +41,17 @@ export function FileSelectInput(props) {
42
41
  const { onChange, fileType, onFileSubmit, bold = false } = props;
43
42
  const [errorMessage, setErrorMessage] = useState(null);
44
43
  const inputRef = useRef(null);
44
+ const { t } = useTranslation();
45
+ const { accept, errorStr } = getFileType(fileType, t);
45
46
  const cdsContext = useContext(CdsContext);
46
- const { accept, errorStr } = getFileType(fileType);
47
47
  const theme = useTheme();
48
- const { t } = useTranslation();
49
48
  const handleFileChange = (event) => __awaiter(this, void 0, void 0, function* () {
50
49
  var _a, _b, _c;
51
50
  const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
52
51
  if (!file)
53
52
  return;
54
53
  if (file.size >= MAX_FILE_SIZE) {
55
- setErrorMessage(getTexts("errorFileTooLarge"));
54
+ setErrorMessage(getTexts(t, "errorFileTooLarge"));
56
55
  event.target.value = "";
57
56
  return;
58
57
  }
@@ -14,12 +14,14 @@ import moment from "moment";
14
14
  import { CdsContext } from "../../../../CdsProvider";
15
15
  import styled from "@emotion/styled";
16
16
  import { getTexts } from "../../../../texts";
17
+ import { useTranslation } from "react-i18next";
17
18
  /**
18
19
  * OS 파일 다이얼로그를 열어 파일을 선택하고 업로드하는 컴포넌트입니다.
19
20
  */
20
21
  export function UploadFileDialog({ open, onClose, onChange, }) {
21
22
  const inputRef = useRef(null);
22
23
  const cdsContext = useContext(CdsContext);
24
+ const { t } = useTranslation();
23
25
  const handleFileChange = (event) => __awaiter(this, void 0, void 0, function* () {
24
26
  var _a, _b, _c;
25
27
  const showFileError = (_a = cdsContext.lexical) === null || _a === void 0 ? void 0 : _a.showFileError;
@@ -30,7 +32,7 @@ export function UploadFileDialog({ open, onClose, onChange, }) {
30
32
  }
31
33
  const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
32
34
  if (file.size >= MAX_FILE_SIZE) {
33
- showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts("errorFileTooLarge"));
35
+ showFileError === null || showFileError === void 0 ? void 0 : showFileError("upload", getTexts(t, "errorFileTooLarge"));
34
36
  event.target.value = "";
35
37
  onClose();
36
38
  return;
@@ -54,9 +54,9 @@ export function InputComponent(props) {
54
54
  // 학생 view
55
55
  if (!isEditable) {
56
56
  if (showCharacterNumber) {
57
- return (_jsx(SegmentedInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || getTexts("placeholderEnterHere"), value: answerInput, onChange: handleChange }));
57
+ return (_jsx(SegmentedInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, answerFormat: answerFormat, placeholder: placeholder || getTexts(t, "placeholderEnterHere"), value: answerInput, onChange: handleChange }));
58
58
  }
59
- return (_jsx(TextInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || getTexts("placeholderEnterHere"), value: answerInput, onChange: (e) => {
59
+ return (_jsx(TextInput, { readOnly: freezeProblemNode, isCorrect: isCorrect, size: "small", color: "default", placeholder: placeholder || getTexts(t, "placeholderEnterHere"), value: answerInput, onChange: (e) => {
60
60
  handleChange(e.target.value);
61
61
  }, fullWidth: true }));
62
62
  }
@@ -2,7 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Controller } from "react-hook-form";
3
3
  import { Input } from "../../../../../components/Input";
4
4
  import { getTexts } from "../../../../../texts";
5
+ import { useTranslation } from "react-i18next";
5
6
  export function FormPlaceholder(props) {
6
7
  const { control } = props;
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") })) }));
8
+ const { t } = useTranslation();
9
+ return (_jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: getTexts(t, "exampleEnterHere") })) }));
8
10
  }
@@ -83,7 +83,7 @@ export function SettingForm(props) {
83
83
  ` }) })] }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "showCharacterNumber", items: [
84
84
  { value: false, label: t("한 칸으로") },
85
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 `
86
+ ] })] }), _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 주관식 설정창" }), _jsx(Tooltip, { text: getTexts(t, "descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
87
87
  width: 12px;
88
88
  height: 12px;
89
89
  ` }) })] }), _jsx(FormPlaceholder, { control: control })] }), _jsxs(FormArea, { children: [_jsx(Label, { children: t("띄어쓰기") }), _jsx(FormSegmentedControl, { control: control, trigger: trigger, name: "ignoreWhitespace", items: [
@@ -6,7 +6,6 @@ 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
9
  import { useTranslation } from "react-i18next";
11
10
  const TYPE_TO_INDEX_ICON = (type) => ({
12
11
  primary: (_jsx(CheckFillIcon, { css: css `
@@ -26,40 +25,42 @@ const TYPE_TO_INDEX_ICON = (type) => ({
26
25
  * isSelected, isAnswer, onClick에 따른 type, description 내용
27
26
  * /images/2025-05-05-01-25-06.png
28
27
  */
29
- function getTypeAndDescription(isSelected, isAnswer, onClick) {
28
+ function getTypeAndDescription(isSelected, isAnswer, onClick, t) {
30
29
  if (isAnswer === undefined) {
31
30
  return {
32
31
  type: isSelected ? "primary" : undefined,
33
- description: isSelected
34
- ? i18n.t("선택됨")
35
- : onClick
36
- ? i18n.t("선택하기")
37
- : "",
32
+ description: isSelected ? t("선택됨") : onClick ? t("선택하기") : "",
38
33
  };
39
34
  }
40
35
  if (isAnswer) {
41
36
  return {
42
37
  type: isSelected ? "success" : undefined,
43
38
  description: isSelected
44
- ? i18n.t("선택됨, 정답", { context: "렉시컬 선택지 하나의 상태" })
39
+ ? t("선택됨, 정답", {
40
+ context: "렉시컬 선택지 하나의 상태",
41
+ })
45
42
  : onClick
46
- ? i18n.t("선택하기, 정답", { context: "렉시컬 선택지 하나의 상태" })
47
- : i18n.t("정답", { context: "렉시컬 선택지 하나의 상태" }),
43
+ ? t("선택하기, 정답", {
44
+ context: "렉시컬 선택지 하나의 상태",
45
+ })
46
+ : t("정답", { context: "렉시컬 선택지 하나의 상태" }),
48
47
  };
49
48
  }
50
49
  return {
51
50
  type: isSelected ? "danger" : undefined,
52
51
  description: isSelected
53
- ? i18n.t("선택됨, 오답", { context: "렉시컬 선택지 하나의 상태" })
52
+ ? t("선택됨, 오답", { context: "렉시컬 선택지 하나의 상태" })
54
53
  : onClick
55
- ? i18n.t("선택하기, 오답", { context: "렉시컬 선택지 하나의 상태" })
56
- : i18n.t("오답", { context: "렉시컬 선택지 하나의 상태" }),
54
+ ? t("선택하기, 오답", {
55
+ context: "렉시컬 선택지 하나의 상태",
56
+ })
57
+ : t("오답", { context: "렉시컬 선택지 하나의 상태" }),
57
58
  };
58
59
  }
59
60
  export function SelectBoxView(props) {
60
61
  const { multipleSelectionsEnabled, index, isSelected, isAnswer, image, text, onClick, } = props;
61
62
  const { t } = useTranslation();
62
- const { type, description } = getTypeAndDescription(isSelected, isAnswer, onClick);
63
+ const { type, description } = getTypeAndDescription(isSelected, isAnswer, onClick, t);
63
64
  const boxRef = useRef(null);
64
65
  const handleKeyDown = (e) => {
65
66
  // onClick이 없으면 아무것도 하지 않음.
@@ -13,6 +13,7 @@ import { SettingForm } from "./SettingForm";
13
13
  import styled from "@emotion/styled";
14
14
  import { LexicalCustomConfigContext } from "../../LexicalCustomConfigContext";
15
15
  import { useTranslation } from "react-i18next";
16
+ import { getTexts } from "../../../../texts";
16
17
  export function SelectComponent(props) {
17
18
  const { selected, hasMultipleSolutions, selections, nodeKey } = props;
18
19
  const [editor] = useLexicalComposerContext();
@@ -35,7 +36,7 @@ export function SelectComponent(props) {
35
36
  `, children: [multipleSelectionsEnabled && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
36
37
  width: 14px;
37
38
  height: 14px;
38
- ` }), t("질문에 해당하는 답을 모두 고르는 문제입니다.")] })), selections.map((selection, index) => (_jsx(SelectBoxView, { multipleSelectionsEnabled: multipleSelectionsEnabled, index: index + 1, isAnswer: showQuizSolution && "isAnswer" in selection
39
+ ` }), getTexts(t, "multipleChoicesProblem")] })), selections.map((selection, index) => (_jsx(SelectBoxView, { multipleSelectionsEnabled: multipleSelectionsEnabled, index: index + 1, isAnswer: showQuizSolution && "isAnswer" in selection
39
40
  ? selection.isAnswer
40
41
  : undefined, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
41
42
  ? undefined
@@ -44,7 +44,7 @@ export function SettingForm(props) {
44
44
  function validateRequired(value) {
45
45
  if (value.show.image || value.show.text)
46
46
  return true;
47
- return getTexts("errorRequiredField");
47
+ return getTexts(t, "errorRequiredField");
48
48
  }
49
49
  function validateDuplicated(value, formValues) {
50
50
  const shows = formValues.selections.map((selection) => selection.show);
@@ -57,7 +57,7 @@ export function SettingForm(props) {
57
57
  });
58
58
  if (duplicatedIndexes.length < 2)
59
59
  return true;
60
- return getTexts("errorDuplicateChoice")(duplicatedIndexes.map((i) => i + 1).join(","));
60
+ return getTexts(t, "errorDuplicateChoice")(duplicatedIndexes.map((i) => i + 1).join(","));
61
61
  }
62
62
  const answersCount = watch("selections").filter((selection) => selection.isAnswer).length;
63
63
  const hasMultipleAnswers = answersCount > 1;
@@ -76,7 +76,7 @@ export function SettingForm(props) {
76
76
  }
77
77
  : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: fields.length < 9
78
78
  ? t("선택지 추가", { context: "버튼, 렉시컬 도구 설정창" })
79
- : getTexts("errorMaxChoicesExceeded"), onClick: () => {
79
+ : getTexts(t, "errorMaxChoicesExceeded"), onClick: () => {
80
80
  if (fields.length >= 9)
81
81
  return;
82
82
  append({
@@ -13,7 +13,7 @@ export function FormIconAndLabel(props) {
13
13
  const theme = useTheme();
14
14
  const { t } = useTranslation();
15
15
  return (_jsx(Controller, { control: control, name: "iconType", render: ({ field: { value, onChange } }) => {
16
- const { startIcon, title, icons } = getIconData(value, theme);
16
+ const { startIcon, title, icons } = getIconData(value, theme, t);
17
17
  return (_jsxs(_Fragment, { children: [_jsx(Dropdown, { label: t("아이콘: {{title}}", { title }), size: "small", color: "grey", closeOnItemClick: true, bold: true, startIcon: startIcon, endIcon: _jsx(ArrowDownSFillIcon, {}), menuProps: {
18
18
  menuCss: css `
19
19
  width: 216px;
@@ -27,7 +27,7 @@ export function FormIconAndLabel(props) {
27
27
  horizontal: "center",
28
28
  },
29
29
  }, children: ICON_TYPES.map((type, index) => {
30
- const { title, startIcon } = getIconData(type, theme);
30
+ const { title, startIcon } = getIconData(type, theme, t);
31
31
  return (_jsx(DropdownItem, { index: index, label: title, preserveIconColor: true, startIcon: startIcon, onClick: () => {
32
32
  onChange(type);
33
33
  } }, index));
@@ -4,14 +4,16 @@ import styled from "@emotion/styled";
4
4
  import { css } from "@emotion/react";
5
5
  import { FormInput } from "../../../../../Form";
6
6
  import { getTexts } from "../../../../../../../../texts";
7
- const getLabelRules = () => ({
8
- required: getTexts("errorRequiredField"),
7
+ import { useTranslation } from "react-i18next";
8
+ const getLabelRules = (t) => ({
9
+ required: getTexts(t, "errorRequiredField"),
9
10
  maxLength: 12,
10
11
  });
11
12
  /** 레이블을 설정하는 Form입니다. */
12
13
  export function FormLabel(props) {
13
14
  const { control, index } = props;
14
- const labelRules = getLabelRules();
15
+ const { t } = useTranslation();
16
+ const labelRules = getLabelRules(t);
15
17
  // endIcon에 들어갈 value, error를 사용하기 위해 useController를 사용합니다.
16
18
  const { field: { value }, fieldState: { error }, } = useController({
17
19
  control,
@@ -10,7 +10,7 @@ export function FormQuestion(props) {
10
10
  const { control, index, onDelete } = props;
11
11
  const { t } = useTranslation();
12
12
  return (_jsxs(Item, { children: [_jsx(Index, { children: index + 1 }), _jsx(FormInput, { control: control, name: `evaluations.${index}.question.text`, rules: {
13
- required: getTexts("errorRequiredField"),
13
+ required: getTexts(t, "errorRequiredField"),
14
14
  }, size: "small", placeholder: t("{{index}}번 평가 항목", { index: index + 1 }), multiline: true, fullWidth: true }), onDelete && (_jsx(SquareButton, { color: "white", size: "xsmall", icon: _jsx(DeleteBinLineIcon, {}), onClick: onDelete, "aria-label": t("삭제", {
15
15
  context: "스퀘어버튼, 렉시컬 3단계 평가 입력칸 도구 설정창",
16
16
  }) }))] }));
@@ -50,7 +50,7 @@ export function SettingForm(props) {
50
50
  }
51
51
  : undefined }, field.uid))), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: t("평가 항목 추가"), onClick: () => {
52
52
  append({
53
- question: { text: getTexts("placeholderEvaluationItem") },
53
+ question: { text: getTexts(t, "placeholderEvaluationItem") },
54
54
  selectedLabelIndex: null,
55
55
  });
56
56
  } })] }), _jsxs(Right, { children: [_jsx(Label, { children: t("아이콘, 레이블") }), _jsx(FormIconAndLabel, { control: control, labelsLength: labels.length })] })] }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: t("닫기", { context: "렉시컬 도구 설정창" }), onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: t("이대로 넣기", { context: "렉시컬 도구 설정창" }), bold: true, type: "submit" })] })] }));
@@ -23,7 +23,7 @@ export function EvaluationComponent(props) {
23
23
  const { freezeProblemNode } = useContext(LexicalCustomConfigContext);
24
24
  const { t } = useTranslation();
25
25
  const [settingOpen, setSettingOpen] = useState(false);
26
- const { icons } = getIconData(iconType, theme);
26
+ const { icons } = getIconData(iconType, theme, t);
27
27
  // label과 icons를 매핑하여 ToggelButtonData로 변환합니다.
28
28
  // 만약 둘의 길이가 같지 않다면 짧은 길이에 맞춰집니다.
29
29
  const data = _.zipWith(labels, icons, (label, { onIcon, offIcon }) => ({
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { IconType } from "./SelfEvaluationNode";
3
3
  import { Theme } from "@emotion/react";
4
+ import { TFunction } from "i18next";
4
5
  interface IconData {
5
6
  startIcon: React.ReactNode;
6
7
  title: string;
@@ -10,5 +11,5 @@ interface IconData {
10
11
  }[];
11
12
  }
12
13
  export declare const ICON_TYPES: IconType[];
13
- export declare const getIconData: (iconType: IconType, theme: Theme) => IconData;
14
+ export declare const getIconData: (iconType: IconType, theme: Theme, t: TFunction) => IconData;
14
15
  export {};
@@ -2,16 +2,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { CustomEmoGoodIcon, CustomEmoGreatIcon, CustomEmoNeutralIcon, } from "../../../../icons/custom/default";
3
3
  import { CustomEmoGoodColorIcon, CustomEmoGreatColorIcon, CustomEmoNeutralColorIcon, } from "../../../../icons/custom/colored";
4
4
  import { Number1Icon, Number2Icon, Number3Icon } from "../../../../icons";
5
- import { i18n } from "../../../../i18n/i18n";
6
5
  export const ICON_TYPES = [
7
6
  "emoji",
8
7
  "ascendingNumber",
9
8
  "descendingNumber",
10
9
  ];
11
- export const getIconData = (iconType, theme) => ({
10
+ export const getIconData = (iconType, theme, t) => ({
12
11
  emoji: {
13
12
  startIcon: _jsx(CustomEmoGreatColorIcon, {}),
14
- title: i18n.t("이모지"),
13
+ title: t("이모지"),
15
14
  icons: [
16
15
  {
17
16
  onIcon: _jsx(CustomEmoGreatColorIcon, {}),
@@ -26,7 +25,7 @@ export const getIconData = (iconType, theme) => ({
26
25
  },
27
26
  ascendingNumber: {
28
27
  startIcon: _jsx(Number1Icon, {}),
29
- title: i18n.t("숫자, 1-2-3"),
28
+ title: t("숫자, 1-2-3"),
30
29
  icons: [
31
30
  {
32
31
  onIcon: _jsx(Number1Icon, { color: theme.color.background.primary }),
@@ -44,7 +43,7 @@ export const getIconData = (iconType, theme) => ({
44
43
  },
45
44
  descendingNumber: {
46
45
  startIcon: _jsx(Number3Icon, {}),
47
- title: i18n.t("숫자, 3-2-1"),
46
+ title: t("숫자, 3-2-1"),
48
47
  icons: [
49
48
  {
50
49
  onIcon: _jsx(Number3Icon, { color: theme.color.background.primary }),
@@ -49,7 +49,7 @@ export function InputComponent(props) {
49
49
  : {
50
50
  onFocus: () => setFocus(true),
51
51
  onBlur: () => setFocus(false),
52
- }, size: "small", color: focus ? "activePrimary" : "default", placeholder: placeholder || getTexts("placeholderEnterHere"), value: valueInput, onChange: (e) => {
52
+ }, size: "small", color: focus ? "activePrimary" : "default", placeholder: placeholder || getTexts(t, "placeholderEnterHere"), value: valueInput, onChange: (e) => {
53
53
  handleChange(e.target.value);
54
54
  }, fullWidth: true }));
55
55
  }
@@ -36,10 +36,10 @@ export function SettingForm(props) {
36
36
  height: 12px;
37
37
  ` }), multiline
38
38
  ? t("서술형 입력 칸", { context: "렉시컬 단답형/서술형 도구" })
39
- : t("단답형 입력 칸", { context: "렉시컬 단답형/서술형 도구" })] }), _jsx(Content, { children: _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 단답형/서술형 도구" }), _jsx(Tooltip, { text: getTexts("descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
39
+ : t("단답형 입력 칸", { context: "렉시컬 단답형/서술형 도구" })] }), _jsx(Content, { children: _jsxs(FormArea, { children: [_jsxs(Label, { children: [t("자리 표시자", { context: "렉시컬 단답형/서술형 도구" }), _jsx(Tooltip, { text: getTexts(t, "descriptionDefaultInputText"), placement: "top", children: _jsx(QuestionFillIcon, { css: css `
40
40
  width: 12px;
41
41
  height: 12px;
42
- ` }) })] }), _jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: getTexts("exampleEnterHere") })) })] }) }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: t("닫기", { context: "렉시컬 도구 설정창" }), onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: t("이대로 넣기", { context: "렉시컬 도구 설정창" }), bold: true, type: "submit" })] })] }));
42
+ ` }) })] }), _jsx(Controller, { name: "placeholder", control: control, render: ({ field: { value, onChange } }) => (_jsx(Input, { size: "small", color: "default", value: value, onChange: onChange, placeholder: getTexts(t, "exampleEnterHere") })) })] }) }), _jsxs(Buttons, { children: [_jsx(Button, { color: "grey", size: "xsmall", label: t("닫기", { context: "렉시컬 도구 설정창" }), onClick: onClose }), _jsx(Button, { color: "primary", size: "xsmall", label: t("이대로 넣기", { context: "렉시컬 도구 설정창" }), bold: true, type: "submit" })] })] }));
43
43
  }
44
44
  const Form = styled.form(({ theme }) => css `
45
45
  display: flex;
@@ -13,6 +13,7 @@ import { SquareButton } from "../../../../../components/SquareButton";
13
13
  import { SettingForm } from "./SettingForm";
14
14
  import { SelectBoxEdit, SelectBoxView } from "./SelectBox";
15
15
  import { useTranslation } from "react-i18next";
16
+ import { getTexts } from "../../../../../texts";
16
17
  export function SelectComponent(props) {
17
18
  const { selections, selected, allowMultipleAnswers, nodeKey } = props;
18
19
  const [editor] = useLexicalComposerContext();
@@ -25,7 +26,7 @@ export function SelectComponent(props) {
25
26
  return (_jsxs(_Fragment, { children: [allowMultipleAnswers && (_jsxs(Alert, { children: [_jsx(AlarmWarningFillIcon, { css: css `
26
27
  width: 14px;
27
28
  height: 14px;
28
- ` }), t("질문에 해당하는 답을 모두 고르는 문제입니다.")] })), selections.map((selection, index) => (_jsx(SelectBoxView, { index: index + 1, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
29
+ ` }), getTexts(t, "multipleChoicesProblem")] })), selections.map((selection, index) => (_jsx(SelectBoxView, { index: index + 1, isSelected: selected.includes(selection.value), image: selection.show.image, text: selection.show.text, onClick: freezeProblemNode
29
30
  ? undefined
30
31
  : () => {
31
32
  const isSelected = selected.includes(selection.value);
@@ -44,7 +44,7 @@ export function SettingForm(props) {
44
44
  function validateRequired(value) {
45
45
  if (value.image || value.text)
46
46
  return true;
47
- return getTexts("errorRequiredField");
47
+ return getTexts(t, "errorRequiredField");
48
48
  }
49
49
  function validateDuplicated(value, formValues) {
50
50
  const shows = formValues.selections.map((selection) => selection.show);
@@ -56,7 +56,7 @@ export function SettingForm(props) {
56
56
  });
57
57
  if (duplicatedIndexes.length < 2)
58
58
  return true;
59
- return getTexts("errorDuplicateChoice")(duplicatedIndexes.map((i) => i + 1).join(","));
59
+ return getTexts(t, "errorDuplicateChoice")(duplicatedIndexes.map((i) => i + 1).join(","));
60
60
  }
61
61
  return (_jsxs(Form, { onSubmit: handleSubmit(onSettingSubmit), children: [_jsxs(Title, { children: [_jsx(ListRadioIcon, { css: css `
62
62
  width: 12px;
@@ -72,7 +72,7 @@ export function SettingForm(props) {
72
72
  }
73
73
  : undefined }, field.uid)))] }), _jsx(Button, { color: "grey", size: "small", startIcon: _jsx(AddFillIcon, {}), label: fields.length < 9
74
74
  ? t("선택지 추가", { context: "버튼, 렉시컬 도구 설정창" })
75
- : getTexts("errorMaxChoicesExceeded"), onClick: () => {
75
+ : getTexts(t, "errorMaxChoicesExceeded"), onClick: () => {
76
76
  if (fields.length >= 9)
77
77
  return;
78
78
  append({
@@ -219,6 +219,7 @@ export function ComponentAdderPlugin(props) {
219
219
  const baseOptions = getBaseOptions({
220
220
  editor,
221
221
  theme,
222
+ t,
222
223
  setImageOpen,
223
224
  setVideoOpen,
224
225
  isSheetEnabled,