> For the complete documentation index, see [llms.txt](https://tech.x2bee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tech.x2bee.com/dev-guide/dev-start/interactive-blocks/bo-api.md).

# BO/API 메시지 처리

X2BEE BO(BackOffice)에서 메시지와 API의 메시지 처리 방법에 대해 설명합니다. 이를 통해 다국어 번역 처리 및 메시지 출력, 그리고 API에서 메시지를 동적으로 처리하는 방식에 대한 이해를 돕고, 실제 개발 환경에서의 활용 예시를 제공합니다.

***

## BO 메시지 처리 방식 가이드

BO의 메시지는 ‘다국어 번역’ 및 ‘사용자 경험 개선’을 위해 설계되었습니다. 이 구조는 `react-i18next`와 상태 관리 라이브러리를 활용하여 동적이고 일관된 번역과 메시지 출력을 제공합니다.

### allLangs 설정

다국어 번역 구조를 초기 설정하는 내용으로, 각 언어에 대한 기본 설정을 정의합니다.

<pre class="language-javascript"><code class="lang-javascript"><strong>export const allLangs = [
</strong>  {
    value: 'en',
    label: 'English',
    countryCode: 'GB',
    adapterLocale: 'en',
    numberFormat: { code: 'en-US', currency: 'USD' },
    systemValue: { components: { /* 영어 설정 */ } },
  },
  {
    value: 'ko',
    label: 'Korea',
    countryCode: 'KR',
    adapterLocale: 'ko',
    numberFormat: { code: 'ko', currency: 'WON' },
    systemValue: { components: { /* 한국어 설정 */ } },
  },
]
</code></pre>

### useTranslate Hook

다국어 번역을 관리하는 커스텀 훅으로, 언어 변경 처리 및 메시지 동기화 작업을 진행합니다.

<pre class="language-javascript"><code class="lang-javascript"><strong>export function useTranslate(
</strong>  ns?: string | string[], // 사용할 네임스페이스(다국어 번역 키 그룹)
  options: { keyPrefix?: KeyPrefix&#x3C;string> } = {} // 키 프리픽스 설정
) {
  const router = useRouter();
  // react-i18next의 번역 함수 및 언어 정보
  const { t, i18n } = useTranslation(ns, options);
  // 공통 코드 동기화 함수
  const { updateCodeList } = useUpdateCommonCodeStore();

  // 기본 언어 설정
  const fallback = allLangs.find((lang) => lang.value === fallbackLng);
  const currentLang = allLangs.find(
    (lang) => lang.value === i18n.resolvedLanguage // 현재 설정된 언어 확인
  );

  const onChangeLang = useCallback(
    async (newLang: LanguageValue) => {
      try {
        // 언어 쿠키 저장
        setCookie('data_lang_cd', newLang);
        setCookie('lang_cd', newLang);
        // 언어 변경 시 공통 코드 동기화
        updateCodeList();
        // 언어 변경 처리
        const langChangePromise = i18n.changeLanguage(newLang);
        // 해당 언어 메시지 가져오기
        const currentMessages = messages[newLang] || messages.en;
        // 언어 변경 상태에 따른 사용자 피드백
        toast.promise(langChangePromise, {
          loading: currentMessages.loading,
          success: () => currentMessages.success,
          error: currentMessages.error,
        });
        // 날짜 로케일 동기화
        if (currentLang) dayjs.locale(currentLang.adapterLocale);
        // 페이지 새로고침으로 UI 갱신
        router.refresh();
      } catch (error) {
        console.error(error);
      }
    },
    [currentLang, i18n, router, updateCodeList]
  );

  return {
    t, // 번역 함수
    i18n, // 번역 엔진 상태
    onChangeLang, // 언어 변경 핸들러
    currentLang: currentLang ?? fallback, // 현재 언어
  };
}
</code></pre>

### 메시지 JSON 작성

메시지 값은 `src/Locals/langs` 폴더 내 각 언어별 JSON 파일에 작성됩니다.

```json
{
  "adminCommon": {
    "message": {
      "successfully": {
        "saved": "저장되었습니다.",
        "deleted": "삭제되었습니다."
      }
    }
  }
}
```

### 페이지 및 컴포넌트에서의 사용 예시

페이지와 컴포넌트에서 다국어 메시지를 사용하는 예시입니다.

```javascript
const CM_NS = 'common';
const CM = { ns: CM_NS, keyPrefix: 'adminCommon' };

const sampleComponents = () => {
  const { t } = useTranslate([CM_NS]);
  const { dialogAlert } = useDialogContext();

  const onSave = () => {
    dialogAlert({
      text: t('message.successfully.saved', CM) // 저장되었습니다.
    });
  };

  // ...
};

export default sampleComponents;
```

***

## API 메시지 처리 방식 가이드

X2BEE는 API에서의 메시지 처리 방법으로 **MessageResolver 클래스**를 제공합니다. 이를 통해 서버에서 필요한 메시지를 동적으로 처리하고, 호출한 서버의 상황에 맞게 메시지를 반환합니다.

### MessageResolver.class

`getLocaleMessage` 함수는 메시지 키를 기반으로 메시지를 반환합니다. 서버가 BO-API인 경우, 해당 API에서 메시지를 반환하고, 그렇지 않으면 기본 메시지를 반환합니다.

```java
private static String getLocaleMessage(AppError appError, Object[] args, Locale locale) {
    String message = "";
    if (RequestContextUtil.isCallServerBo()) {
        // BO-API 호출 시
        message = getMessageKeyToMessageValue(appError.getBoMessageKey(), args, locale, false);
        if (StringUtils.isBlank(message)) {
            message = getMessageKeyToMessageValue(appError.getMessageKey(), args, locale, true);
        }
    } else {
        message = getMessageKeyToMessageValue(appError.getMessageKey(), args, locale, true);
    }
    return message;
}
```

### ApiError Class 파일 작성

해당 enum class 파일에 code 값 및 message 키 값을 정의하고, 서버에서 반환할 메시지 키를 관리합니다.

```java
public enum ApiError implements AppError {
    /* success */
    SUCCESS("0000", "common.message.success", "common.message.success", false),

    /* app error */
    EMPTY_PARAMETER("1001", "common.error.emptyParameter", "common.error.emptyParameter", false),
    INVALID_PARAMETER("1002", "common.error.invalidParameter", "common.error.invalidParameter", false),

    // unknown error
    UNKNOWN("9000", "common.error.unknown", "common.error.unknown"),

    // ValidationException error
    VALIDATION_EXCEPTION("9100", "common.error.unknown", "common.error.unknown"),

    TEST("9999", "event.aply.simple.member.limit.message", "event.aply.simple.member.limit.message.bo");

    private final String code;
    private final String messageKey;
    private final String boMessageKey;
    // ...
}
```

### message properties 파일 작성

`event_ko.properties`, `event_en.properties` 등 각 언어별로 메시지 값을 정의합니다.

```
event.aply.simple.member.limit.message = 간편회원은 응모하실 수 없습니다.
event.aply.simple.member.limit.message2 = 간편회원은 응모하실 수 없습니다.
```

### 비즈니스 로직에서 사용 예시

컨트롤러 또는 서비스에서 MessageResolver를 사용하는 예시입니다.

```java
@GetMapping("/test")
public ResponseEntity<Response> test() throws Exception {
    // 메시지를 직접 가져오는 경우. 해당 메시지 키 값 그대로 반환함.
    String eventMsg = MessageResolver.getMessage("event.aply.simple.member.limit.message");

    // 위와 동일하지만 정의한 ApiError enum Class를 활용하는 경우
    // 호출한 서버명에 따라서 messageKey 값을 반환함.
    String msg = MessageResolver.getMessage(ApiError.TEST);

    // AppException을 발생하는 경우
    // 호출한 서버명에 따라서 messageKey 값을 반환함.
    AppException.exception(ApiError.TEST);

    Response body = Response.builder().payload("OK").message(eventMsg).build();
    return ResponseEntity.ok().body(body);
}
```
