> 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/developer-guide-en/dev-start/interactive-blocks/bo-api-message-handling.md).

# BO/API Message Handling

This explains how messages and API messages are handled in X2BEE BO (BackOffice). It provides an understanding of multilingual translation handling and message output, as well as how messages are dynamically handled in the API, along with practical usage examples in the actual development environment.

***

## BO Message Handling Method Guide

BO messages are designed for ‘multilingual translation’ and ‘improved user experience’. This structure uses `react-i18next` and a state management library to provide dynamic and consistent translation and message output.

### allLangs Configuration

This is the initial setup content for the multilingual translation structure, defining the default configuration for each language.

<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: { /* English settings */ } },
  },
  {
    value: 'ko',
    label: 'Korea',
    countryCode: 'KR',
    adapterLocale: 'ko',
    numberFormat: { code: 'ko', currency: 'WON' },
    systemValue: { components: { /* Korean settings */ } },
  },
]
</code></pre>

### useTranslate Hook

This is a custom hook that manages multilingual translation, handling language change processing and message synchronization.

<pre class="language-javascript"><code class="lang-javascript"><strong>export function useTranslate(
</strong>  ns?: string | string[], // Namespace(s) to use (multilingual translation key group)
  options: { keyPrefix?: KeyPrefix&#x3C;string> } = {} // Key prefix configuration
) {
  const router = useRouter();
  // react-i18next's translation function and language info
  const { t, i18n } = useTranslation(ns, options);
  // Common code synchronization function
  const { updateCodeList } = useUpdateCommonCodeStore();

  // Default language configuration
  const fallback = allLangs.find((lang) => lang.value === fallbackLng);
  const currentLang = allLangs.find(
    (lang) => lang.value === i18n.resolvedLanguage // Check the currently configured language
  );

  const onChangeLang = useCallback(
    async (newLang: LanguageValue) => {
      try {
        // Save language cookie
        setCookie('data_lang_cd', newLang);
        setCookie('lang_cd', newLang);
        // Synchronize common codes when the language changes
        updateCodeList();
        // Process the language change
        const langChangePromise = i18n.changeLanguage(newLang);
        // Get the messages for that language
        const currentMessages = messages[newLang] || messages.en;
        // User feedback based on the language change status
        toast.promise(langChangePromise, {
          loading: currentMessages.loading,
          success: () => currentMessages.success,
          error: currentMessages.error,
        });
        // Synchronize the date locale
        if (currentLang) dayjs.locale(currentLang.adapterLocale);
        // Refresh the page to update the UI
        router.refresh();
      } catch (error) {
        console.error(error);
      }
    },
    [currentLang, i18n, router, updateCodeList]
  );

  return {
    t, // translation function
    i18n, // translation engine state
    onChangeLang, // language change handler
    currentLang: currentLang ?? fallback, // current language
  };
}
</code></pre>

### Writing the Message JSON

Message values are written in each language's JSON file within the `src/Locals/langs` folder.

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

### Usage Example on Pages and Components

The following is an example of using multilingual messages on pages and components.

```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) // Saved successfully.
    });
  };

  // ...
};

export default sampleComponents;
```

***

## API Message Handling Method Guide

X2BEE provides the **MessageResolver class** as a method for handling messages in the API. Through this, the server dynamically handles the messages it needs and returns messages appropriate to the situation of the calling server.

### MessageResolver.class

The `getLocaleMessage` function returns a message based on a message key. If the server is a BO-API, the message is returned from that API; otherwise, the default message is returned.

```java
private static String getLocaleMessage(AppError appError, Object[] args, Locale locale) {
    String message = "";
    if (RequestContextUtil.isCallServerBo()) {
        // When called from 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;
}
```

### Writing the ApiError Class File

In this enum class file, define the code value and message key values, and manage the message keys to be returned by the server.

```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;
    // ...
}
```

### Writing the message properties File

Define the message values for each language in files such as `event_ko.properties`, `event_en.properties`, etc.

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

### Usage Example in Business Logic

The following is an example of using MessageResolver in a controller or service.

```java
@GetMapping("/test")
public ResponseEntity<Response> test() throws Exception {
    // When retrieving the message directly. Returns the message key value as is.
    String eventMsg = MessageResolver.getMessage("event.aply.simple.member.limit.message");

    // Same as above, but using the defined ApiError enum Class
    // Returns the messageKey value depending on the calling server name.
    String msg = MessageResolver.getMessage(ApiError.TEST);

    // When throwing an AppException
    // Returns the messageKey value depending on the calling server name.
    AppException.exception(ApiError.TEST);

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