UI5 i18n 삽질 끝 — 다국어 지원 한 번에 #shorts #SAP #UI5

Moderator · 조회 2
#SAP #UI5 #SAPUI5 #i18n #Internationalization

UI5 i18n 삽질 끝 — 다국어 지원 한 번에 #shorts #SAP #UI5

Moderator · 조회 2
#SAP #UI5 #SAPUI5 #i18n #Internationalization

개요 및 학습 목표

SAPUI5 애플리케이션을 글로벌 사용자에게 제공하려면 텍스트를 코드에서 분리하여 언어별로 관리해야 합니다. 본 튜토리얼은 Resource Bundle(i18n.properties)을 중심으로 manifest.json 설정, XML View 바인딩, Controller 동적 처리까지 다국어(i18n) 구현의 전 과정을 다룹니다.

학습 체크리스트

  • i18n 모델을 manifest.json에 선언하고 supportedLocales, fallbackLocale을 구성할 수 있다
  • XML View에서 {i18n>key} 바인딩으로 정적 텍스트를 처리할 수 있다
  • Controller에서 getResourceBundle()을 호출해 동적 텍스트를 생성할 수 있다
  • 플레이스홀더, 복수형, 날짜/숫자 포맷을 로케일별로 처리할 수 있다
  • 흔한 실수(누락 키, fallback 미설정 등)를 디버깅할 수 있다

선수 지식

본 튜토리얼은 SAPUI5의 MVC 구조에 대한 기본 이해를 전제로 합니다. XML View에서 컨트롤을 선언하고 Controller에서 이벤트를 처리해본 경험, manifest.json(앱 디스크립터)의 기본 구조, 그리고 SAPUI5 모델(JSONModel, ResourceModel) 개념을 알고 있으면 좋습니다. SAP Fiori Launchpad 또는 SAP Business Application Studio 환경에서 UI5 프로젝트를 생성한 경험을 권장합니다.

환경 / 버전 / 준비물

  • SAPUI5 버전: 1.120 LTS 이상 권장 (1.71 LTS도 호환)
  • 개발 IDE: SAP Business Application Studio 또는 VS Code + Fiori Tools 확장
  • 빌드 도구: UI5 Tooling (@ui5/cli) 3.x 이상
  • Node.js: 18.x LTS 이상
  • 프로젝트 템플릿: SAP Fiori Freestyle 또는 Elements 기반 앱
  • 인코딩: i18n.properties 파일은 일반적으로 UTF-8을 권장하며, 일부 ABAP 백엔드와 연동 시 ISO-8859-1 또는 유니코드 이스케이프(\uXXXX)가 필요할 수 있음

로컬에서 빠르게 시작하려면 npm init @sap/ui5-application 또는 BAS의 "SAP Fiori application" 위저드를 사용해 기본 폴더 구조(webapp/i18n/, webapp/manifest.json)를 생성하세요.

핵심 개념

1. 왜 텍스트를 하드코딩하면 안 되는가?

XML View에 text="주문 내역"처럼 직접 적어 두면 영어/일본어 사용자에게도 동일한 한글이 노출됩니다. 더 큰 문제는 유지보수 비용입니다. "주문 내역"을 "주문 이력"으로 바꾸려면 모든 View와 Controller를 검색해 수정해야 합니다. 또한 SAP의 다국어 표준(예: ABAP CDS Annotation, Fiori Elements)과의 일관성도 깨집니다.

비유하자면, 텍스트 하드코딩은 옷에 글자를 자수로 박아 넣는 것과 같습니다. 반면 i18n은 옷에 이름표(라벨)를 핀으로 꽂는 방식이어서 시즌(언어)이 바뀌면 라벨만 갈아 끼우면 됩니다.

2. Resource Bundle이란?

Resource Bundle은 키-값 쌍으로 구성된 .properties 텍스트 파일들의 묶음입니다. 자바 표준(java.util.ResourceBundle)에서 유래했으며, SAPUI5는 sap.ui.model.resource.ResourceModel이 이를 로드합니다. 파일명은 i18n_<language>_<region>.properties 컨벤션을 따릅니다.

webapp/
├── i18n/
│   ├── i18n.properties          # 기본(폴백)
│   ├── i18n_en.properties       # 영어
│   ├── i18n_en_US.properties    # 영어(미국)
│   ├── i18n_ko.properties       # 한국어
│   ├── i18n_ja.properties       # 일본어
│   └── i18n_de.properties       # 독일어
├── manifest.json
└── view/
    └── Main.view.xml

UI5 런타임은 브라우저 언어(또는 URL 파라미터 sap-language)를 감지하여 가장 적합한 파일을 선택합니다. 매칭 우선순위는 언어+지역 → 언어 → fallback 순서로 동작합니다.

3. supportedLocales와 fallbackLocale

SAPUI5 1.77부터는 manifest.json에 supportedLocalesfallbackLocale을 명시적으로 선언하는 것이 권장됩니다. 미선언 시 UI5는 사용자가 요청한 언어에 해당하는 파일이 없을 때 추가 HTTP 요청을 시도(404 발생 가능)하므로 성능과 콘솔 깔끔함 측면에서 손해입니다.

실전 코드 3단계

1단계: 기본 예제 — Hello, World 다국어

i18n/i18n.properties (기본/폴백)

appTitle=My Shop
appDescription=A simple shopping app
welcomeMessage=Welcome!
buttonSubmit=Submit

i18n/i18n_ko.properties

appTitle=마이샵
appDescription=간단한 쇼핑 앱입니다
welcomeMessage=환영합니다!
buttonSubmit=제출

manifest.json (요지)

{
  "_version": "1.59.0",
  "sap.app": {
    "id": "com.example.myshop",
    "type": "application",
    "i18n": "i18n/i18n.properties",
    "title": "{{appTitle}}",
    "description": "{{appDescription}}"
  },
  "sap.ui5": {
    "models": {
      "i18n": {
        "type": "sap.ui.model.resource.ResourceModel",
        "settings": {
          "bundleName": "com.example.myshop.i18n.i18n",
          "supportedLocales": ["", "ko", "en", "ja", "de"],
          "fallbackLocale": ""
        }
      }
    }
  }
}

주의 포인트: sap.app.title처럼 manifest 자체에서 i18n을 쓰려면 {{key}} 이중 중괄호 문법을 사용합니다. 또한 supportedLocales의 빈 문자열 ""은 폴백 파일(i18n.properties)을 의미합니다.

view/Main.view.xml

<mvc:View
    controllerName="com.example.myshop.controller.Main"
    xmlns="sap.m"
    xmlns:mvc="sap.ui.core.mvc">
  <Page title="{i18n>appTitle}">
    <Text text="{i18n>welcomeMessage}" />
    <Button text="{i18n>buttonSubmit}" press=".onSubmit" />
  </Page>
</mvc:View>

2단계: 실무 시나리오 — 동적 텍스트 + 에러 메시지

장바구니 합계처럼 런타임에 값이 변하는 텍스트는 Controller에서 ResourceBundle을 직접 가져와 포맷팅합니다.

i18n/i18n_ko.properties

cart.itemCount=장바구니에 {0}개 상품이 있습니다
cart.totalPrice=총 금액: {0}
error.networkFail=네트워크 오류가 발생했습니다. (코드 {0})
error.required={0} 항목은 필수입니다

controller/Main.controller.js

sap.ui.define([
  "sap/ui/core/mvc/Controller",
  "sap/m/MessageToast",
  "sap/base/Log"
], function (Controller, MessageToast, Log) {
  "use strict";

  return Controller.extend("com.example.myshop.controller.Main", {

    _getBundle: function () {
      // 권장: ResourceModel에서 ResourceBundle을 한 번만 캐싱
      return this.getOwnerComponent()
                 .getModel("i18n")
                 .getResourceBundle();
    },

    onShowCartCount: function (count) {
      var oBundle = this._getBundle();
      var sMsg = oBundle.getText("cart.itemCount", [count]);
      MessageToast.show(sMsg);
    },

    onAjaxError: function (oError) {
      var oBundle = this._getBundle();
      var sMsg = oBundle.getText("error.networkFail", [oError.statusCode]);
      Log.error(sMsg, oError.responseText, "MainController");
      sap.m.MessageBox.error(sMsg);
    }
  });
});

getText(key, [args])의 두 번째 인자는 배열이며 {0}, {1} 자리에 순서대로 치환됩니다. 로깅 시 sap/base/Log를 사용하면 UI5 표준 로그 채널로 흐르므로 운영에서 필터링이 쉬워집니다.

3단계: 프로덕션 — 비동기 로딩 + 포맷터 + 테스트

대형 앱에서는 i18n 번들이 커져 초기 로드를 지연시킬 수 있으므로 비동기 로딩포맷터 분리를 권장합니다.

manifest.json — async 활성화

"i18n": {
  "type": "sap.ui.model.resource.ResourceModel",
  "settings": {
    "bundleName": "com.example.myshop.i18n.i18n",
    "supportedLocales": ["", "ko", "en", "ja", "de"],
    "fallbackLocale": "",
    "async": true
  }
}

model/formatter.js — 재사용 가능한 포맷터

sap.ui.define([
  "sap/ui/core/format/DateFormat",
  "sap/ui/core/format/NumberFormat"
], function (DateFormat, NumberFormat) {
  "use strict";
  return {
    statusText: function (sStatus) {
      var oBundle = this.getView()
                        .getModel("i18n")
                        .getResourceBundle();
      // 키 컨벤션: status.OPEN, status.CLOSED ...
      return oBundle.getText("status." + sStatus) || sStatus;
    },

    localizedDate: function (oDate) {
      if (!oDate) { return ""; }
      var oFmt = DateFormat.getDateInstance({ style: "medium" });
      return oFmt.format(oDate);
    },

    localizedPrice: function (nValue, sCurrency) {
      var oFmt = NumberFormat.getCurrencyInstance({ showMeasure: true });
      return oFmt.format(nValue, sCurrency || "EUR");
    }
  };
});

XML View에서 포맷터 사용

<ObjectStatus
    text="{ path: 'order>status', formatter: '.formatter.statusText' }" />
<Text
    text="{ path: 'order>createdAt', formatter: '.formatter.localizedDate' }" />

OPA5/QUnit 단위 테스트 — 키가 누락됐는지 확인합니다.

QUnit.test("필수 i18n 키가 모두 존재한다", function (assert) {
  var oBundle = this.oComponent.getModel("i18n").getResourceBundle();
  ["appTitle", "buttonSubmit", "cart.itemCount"].forEach(function (k) {
    var v = oBundle.getText(k);
    assert.ok(v && v !== k, "키 " + k + " 가 정의되어 있어야 한다");
  });
});

보안 측면에서는 i18n 텍스트에 사용자 입력을 그대로 끼워 넣지 말고, 반드시 htmlEscape 또는 UI5 컨트롤의 기본 이스케이프를 신뢰하세요. 특히 FormattedTextHTML 컨트롤에 i18n 값을 바인딩할 때는 XSS 위험이 있습니다.

흔한 실수 / 트러블슈팅

FAQ 1. "키가 그대로 화면에 출력됩니다 (예: appTitle 그대로 보임)"

대부분 세 가지 원인 중 하나입니다. (1) bundleName 경로 오타 — 점(.)으로 구분하며 .properties 확장자는 빼야 합니다. (2) 해당 키가 선택된 언어 파일에 없고 폴백 파일에도 없는 경우. (3) View에서 모델명을 {i18n>key}가 아니라 {key}로 적은 경우. 브라우저 개발자도구의 Network 탭에서 i18n 파일 요청 상태(200/404)를 먼저 확인하세요.

FAQ 2. "한글이 깨져서 ?? 로 표시됩니다"

파일 인코딩 문제입니다. 일반적으로 UI5는 UTF-8을 권장하지만, 일부 레거시 빌드 파이프라인이나 ABAP 게이트웨이 경유 시 native2ascii 처리가 필요할 수 있습니다. 임시 해결로 환영합니다환영합니다처럼 유니코드 이스케이프로 적으면 안전합니다. UI5 Tooling 빌드 단계에서 ui5.yaml의 task로 자동화할 수도 있습니다.

FAQ 3. "404 i18n_en_GB.properties 가 콘솔에 뜹니다"

supportedLocales를 선언하지 않으면 UI5가 사용자의 정확한 로케일에 해당하는 파일을 시도하기 때문입니다. ["", "en", "ko"]처럼 명시하면 UI5가 매칭 로직을 클라이언트에서 처리해 불필요한 요청을 줄여 줍니다.

기타 자주 빠뜨리는 점

  • manifest 텍스트도 i18n 처리: title, description은 반드시 {{key}}
  • 플레이스홀더 작은따옴표 이슈: properties 파일에서 ''' 두 개로 이스케이프 (MessageFormat 규칙)
  • 복수형: UI5 1.71+의 sap.base.i18n.ResourceBundle은 ICU MessageFormat의 {count, plural, one {...} other {...}}을 부분적으로 지원하므로 키를 단/복수로 나눠 두는 편이 호환성이 좋습니다.
  • RTL 언어: 아랍어/히브리어 지원 시 sap-ui-rtl=true URL 파라미터 또는 Configuration API로 방향 전환

다음 단계 / 관련 주제

  • SAP Translation Hub로 i18n.properties를 클라우드 번역 워크플로에 연결
  • CAP(Cloud Application Programming) i18n: 백엔드 OData 메시지/라벨도 동일 키 컨벤션으로 통합
  • Fiori Elements Annotation 다국어: CDS의 @title, @Common.Label을 i18n 번들과 연결
  • UI5 Tooling Custom Task로 빌드 시 누락 키 검증 자동화
  • Accessibility(접근성) 텍스트: tooltip, ariaLabelledBy도 i18n 처리

참고 자료


⚠️ 비공식 콘텐츠 안내

본 게시글은 btpstacks.com의 독립 학습 콘텐츠이며 SAP SE와 무관합니다. 공식 문서는 help.sap.com을 참고하세요.

SAP, ABAP, SAP BTP, SAPUI5, SAP Fiori는 독일 및 기타 국가에서 SAP SE의 상표 또는 등록상표입니다.