AngularJS 앱을 국제화하는 방법

게시 됨: 2022-03-11

앱을 국제화하는 것은 소프트웨어 개발을 고통스러운 경험으로 만들 수 있습니다. 특히 처음부터 시작하지 않거나 그것에 대해 무작정 접근하는 경우에는 더욱 그렇습니다.

프론트엔드와 백엔드가 서로 뚜렷하게 분리되어 있는 최신 앱은 국제화와 관련하여 처리하기가 훨씬 더 까다로울 수 있습니다. 갑자기 기존의 서버 측 페이지에서 생성된 웹 앱을 국제화하는 데 도움이 되었던 오랜 시간 동안 검증된 도구에 더 이상 액세스할 수 없습니다.

따라서 AngularJS 앱은 국제화(i18n) 및 현지화(l10n) 데이터의 주문형 전달이 클라이언트에 전달되어 적절한 로케일에서 자체적으로 렌더링되어야 합니다. 기존의 서버 측 렌더링 앱과 달리 이미 현지화된 페이지를 제공하기 위해 더 이상 서버에 의존할 수 없습니다. 여기에서 다국어 PHP 애플리케이션 구축에 대해 배울 수 있습니다.

이 기사에서는 AngularJS 앱을 국제화하는 방법과 프로세스를 쉽게 하는 데 사용할 수 있는 도구에 대해 알아봅니다. AngularJS 앱을 다국어로 만드는 것은 몇 가지 흥미로운 문제를 제기할 수 있지만 특정 접근 방식을 사용하면 이러한 대부분의 문제를 보다 쉽게 ​​해결할 수 있습니다.

간단한 i18n 지원 AngularJS 앱

클라이언트가 사용자 기본 설정에 따라 즉석에서 언어와 로케일을 변경할 수 있도록 하려면 여러 가지 주요 디자인 결정을 내려야 합니다.

  • 처음부터 앱을 언어와 로케일에 구애받지 않도록 디자인하는 방법은 무엇입니까?
  • i18n 및 l10n 데이터를 어떻게 구성합니까?
  • 이 데이터를 어떻게 클라이언트에게 효율적으로 전달합니까?
  • 개발자 워크플로를 단순화하기 위해 저수준 구현 세부 정보를 최대한 추상화하는 방법은 무엇입니까?

이러한 질문에 가능한 한 빨리 답변하면 개발 프로세스의 차질을 피할 수 있습니다. 이러한 각 과제는 이 기사에서 다룰 것입니다. 일부는 강력한 AngularJS 라이브러리를 통해, 다른 일부는 특정 전략 및 접근 방식을 통해 이루어집니다.

AngularJS용 국제화 라이브러리

AngularJS 앱을 국제화하기 위해 특별히 제작된 많은 JavaScript 라이브러리가 있습니다.

angular-translate 는 i18n 데이터를 비동기식으로 로드하는 기능과 함께 필터 및 지시문을 제공하는 AngularJS 모듈입니다. MessageFormat 을 통한 복수화를 지원하며, 확장성과 구성이 가능하도록 설계되었습니다.

프로젝트에서 angular-translate 를 사용하는 경우 다음 패키지 중 일부가 매우 유용할 수 있습니다.

  • angular-sanitize : 번역에서 XSS 공격을 방어하는 데 사용할 수 있습니다.
  • angular-translate-interpolation-messageformat : 성별에 민감한 텍스트 형식을 지원하는 복수화.
  • angular-translate-loader-partial : 번역된 문자열을 클라이언트에 전달하는 데 사용됩니다.

진정한 동적 경험을 위해 angular-dynamic-locale 을 무리에 추가할 수 있습니다. 이 라이브러리를 사용하면 날짜, 숫자, 통화 등이 모두 형식화되는 방식을 포함하여 로케일을 동적으로 변경할 수 있습니다.

시작하기: 관련 패키지 설치

AngularJS 상용구가 이미 준비되어 있다고 가정하면 NPM을 사용하여 국제화 패키지를 설치할 수 있습니다.

 npm i -S angular-translate angular-translate-interpolation-messageformat angular-translate-loader-partial angular-sanitize messageformat

패키지가 설치되면 모듈을 앱의 종속 항목으로 추가하는 것을 잊지 마십시오.

 // /src/app/core/core.module.js app.module('app.core', ['pascalprecht.translate', ...]);

모듈 이름은 패키지 이름과 다릅니다.

첫 번째 문자열 번역

앱에 일부 텍스트가 있는 도구 모음과 일부 자리 표시자 텍스트가 있는 필드가 있다고 가정합니다.

 <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">Hello</a> </div> <div class="collapse navbar-collapse"> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" ng-model="vm.query" placeholder="Search"> </div> ... </div> </div> </nav>

위의 보기에는 국제화할 수 있는 "Hello" 및 "Search"의 두 가지 텍스트가 있습니다. HTML 측면에서 하나는 앵커 태그의 내부 텍스트로 나타나고 다른 하나는 속성 값으로 나타납니다.

이를 국제화하려면 두 문자열 리터럴을 AngularJS가 페이지를 렌더링하는 동안 사용자의 기본 설정에 따라 실제 번역된 문자열로 바꿀 수 있는 토큰으로 바꿔야 합니다.

AngularJS는 토큰을 사용하여 제공한 번역 테이블에서 조회를 수행하여 이를 수행할 수 있습니다. angular-translate 모듈은 이러한 번역 테이블이 일반 JavaScript 객체 또는 JSON 객체(원격으로 로드하는 경우)로 제공될 것으로 예상합니다.

다음은 이러한 번역 테이블이 일반적으로 어떻게 보이는지에 대한 예입니다.

 // /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello", "SEARCH": "Search" } } // /src/app/toolbar/i18n/tr.json { "TOOLBAR": { "HELLO": "Merhaba", "SEARCH": "Ara" } }

위에서 툴바 보기를 국제화하려면 문자열 리터럴을 AngularJS가 번역 테이블에서 조회하는 데 사용할 수 있는 토큰으로 교체해야 합니다.

 <!-- /src/app/toolbar/toolbar.html --> <a class="navbar-brand" href="#" translate="TOOLBAR.HELLO"></a> <!-- or --> <a class="navbar-brand" href="#">{{'TOOLBAR.HELLO' | translate}}</a>

내부 텍스트에 대해 어떻게 translate 지시문이나 translate 필터를 사용할 수 있는지 주목하십시오. (여기에서 translate 지시문과 translate 필터에 대해 자세히 알아볼 수 있습니다.)

이러한 변경으로 보기가 렌더링될 때 angular-translate 는 현재 언어를 기반으로 하는 DOM에 TOOLBAR.HELLO 에 해당하는 적절한 번역을 자동으로 삽입합니다.

속성 값으로 나타나는 문자열 리터럴을 토큰화하려면 다음 접근 방식을 사용할 수 있습니다.

 <!-- /src/app/toolbar/toolbar.html --> <input type="text" class="form-control" ng-model="vm.query" translate translate-attr-placeholder="TOOLBAR.SEARCH">

이제 토큰화된 문자열에 변수가 포함되어 있으면 어떻게 될까요?

"Hello, {{name}}."와 같은 경우를 처리하기 위해 AngularJS가 이미 지원하는 것과 동일한 보간기 구문을 사용하여 변수 교체를 수행할 수 있습니다.

번역 테이블:

 // /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello, {{name}}." } }

그런 다음 여러 가지 방법으로 변수를 정의할 수 있습니다. 다음은 몇 가지입니다.

 <!-- /src/app/toolbar/toolbar.html --> <a ... translate="TOOLBAR.HELLO" translate-values='{ name: vm.user.name }'></a> <!-- or --> <a ... translate="TOOLBAR.HELLO" translate-value-name='{{vm.user.name}}'></a> <!-- or --> <a ...>{{'TOOLBAR.HELLO | translate:'{ name: vm.user.name }'}}</a>

다원화와 젠더 다루기

i18n과 l10n에서 복수화는 꽤 어려운 주제입니다. 언어와 문화에 따라 언어가 다양한 상황에서 복수화를 처리하는 방법에 대한 규칙이 다릅니다.

이러한 문제 때문에 소프트웨어 개발자는 때때로 단순히 문제를 해결하지 않거나 최소한 적절하게 해결하지 않아 다음과 같은 어리석은 문장을 생성하는 소프트웨어가 생성됩니다.

 He saw 1 person(s) on floor 1. She saw 1 person(s) on floor 3. Number of people seen on floor 2: 2.

다행히 이를 처리하는 방법에 대한 표준이 있으며 표준의 JavaScript 구현은 MessageFormat으로 사용할 수 있습니다.

MessageFormat을 사용하면 위의 잘못 구성된 문장을 다음과 같이 바꿀 수 있습니다.

 He saw 1 person on the 2nd floor. She saw 1 person on the 3rd floor. They saw 2 people on the 5th floor.

MessageFormat 은 다음과 같은 표현식을 허용합니다.

 var message = [ '{GENDER, select, male{He} female{She} other{They}}', 'saw', '{COUNT, plural, =0{no one} one{1 person} other{# people}}', 'on the', '{FLOOR, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}', 'floor.' ].join(' ');

위의 배열로 포맷터를 빌드하고 이를 사용하여 문자열을 생성할 수 있습니다.

 var messageFormatter = new MessageFormat('en').compile(message); messageFormatter({ GENDER: 'male', COUNT: 1, FLOOR: 2 }) // 'He saw 1 person on the 2nd floor.' messageFormatter({ GENDER: 'female', COUNT: 1, FLOOR: 3 }) // 'She saw 1 person on the 3rd floor.' messageFormatter({ COUNT: 2, FLOOR: 5 }) // 'They saw 2 people on the 5th floor.'

angular-translate 와 함께 MessageFormat 을 사용하여 앱 내에서 전체 기능을 활용하려면 어떻게 해야 합니까?

앱 구성에서 다음과 같이 메시지 형식 보간을 사용할 수 있음을 angular-translate 에 알리면 됩니다.

 /src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); });

번역 테이블의 항목은 다음과 같이 표시됩니다.

 // /src/app/main/social/i18n/en.json { "SHARED": "{GENDER, select, male{He} female{She} other{They}} shared this." }

그리고 보기에서:

 <!-- /src/app/main/social/social.html --> <div translate="SHARED" translate-values="{ GENDER: 'male' }" translate-interpolation="messageformat"></div> <div> {{ 'SHARED' | translate:"{ GENDER: 'male' }":'messageformat' }} </div>

여기에서 AngularJS의 기본 보간기 대신 메시지 형식 보간기를 사용해야 함을 명시적으로 지정해야 합니다. 이는 두 보간기의 구문이 약간 다르기 때문입니다. 이에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

앱에 번역 테이블 제공

이제 AngularJS가 번역 테이블에서 토큰에 대한 번역을 조회하는 방법을 알았으므로 앱이 처음에 번역 테이블에 대해 어떻게 알 수 있습니까? 어떤 로케일/언어를 사용해야 하는지 앱에 어떻게 알립니까?

여기에서 $translateProvider 에 대해 배웁니다.

다음과 같이 앱의 core.config.js 파일에서 직접 지원하려는 각 로케일에 대한 번역 테이블을 제공할 수 있습니다.

 // /src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); $translateProvider.translations('en', { TOOLBAR: { HELLO: 'Hello, {{name}}.' } }); $translateProvider.translations('tr', { TOOLBAR: { HELLO: 'Merhaba, {{name}}.' } }); $translateProvider.preferredLanguage('en'); });

여기에서는 현재 언어를 영어(en)로 선언하면서 영어(en) 및 터키어(tr)에 대한 JavaScript 개체로 번역 테이블을 제공합니다. 사용자가 언어를 변경하려면 $translate 서비스를 사용하면 됩니다.

 // /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, $translate) { $scope.changeLanguage = function (languageKey) { $translate.use(languageKey); // Persist selection in cookie/local-storage/database/etc... }; });

기본적으로 어떤 언어를 사용해야 하는지에 대한 질문이 여전히 있습니다. 앱의 초기 언어를 하드 코딩하는 것이 항상 허용되는 것은 아닙니다. 이러한 경우 대안은 $translateProvider를 사용하여 자동으로 언어를 결정하는 것입니다.

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.determinePreferredLanguage(); });

determinePreferredLanguage PreferredLanguage는 window.navigator 에서 값을 검색하고 사용자가 명확한 신호를 제공할 때까지 지능형 기본값을 선택합니다.

지연 로딩 번역 테이블

이전 섹션에서는 소스 코드에서 JavaScript 개체로 직접 번역 테이블을 제공하는 방법을 보여주었습니다. 이것은 소규모 응용 프로그램에 적합할 수 있지만 접근 방식은 확장할 수 없습니다. 그래서 번역 테이블은 종종 원격 서버에서 JSON 파일로 다운로드됩니다.

이러한 방식으로 변환 테이블을 유지 관리하면 클라이언트에 전달되는 초기 페이로드 크기가 줄어들지만 추가 복잡성이 발생합니다. 이제 i18n 데이터를 클라이언트에 제공해야 하는 설계 문제에 직면해 있습니다. 이를 주의 깊게 처리하지 않으면 애플리케이션의 성능이 불필요하게 저하될 수 있습니다.

왜 그렇게 복잡합니까? AngularJS 애플리케이션은 모듈로 구성됩니다. 복잡한 응용 프로그램에는 각각 고유한 i18n 데이터가 있는 많은 모듈이 있을 수 있습니다. 따라서 i18n 데이터를 한 번에 로드 및 제공하는 것과 같은 순진한 접근 방식은 피해야 합니다.

필요한 것은 모듈별로 i18n 데이터를 구성하는 방법입니다. 이렇게 하면 필요할 때 필요한 것만 로드할 수 있고, 동일한 데이터를 다시 로드하지 않도록 이전에 로드된 것을 캐시할 수 있습니다(적어도 캐시가 유효하지 않을 때까지).

이것은 partialLoader 가 작동하는 곳입니다.

애플리케이션의 번역 테이블이 다음과 같이 구성되어 있다고 가정해 보겠습니다.

 /src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json

다음 구조와 일치하는 URL 패턴과 함께 partialLoader 를 사용하도록 $translateProvider 를 구성할 수 있습니다.

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoader('$translatePartialLoader', { urlTemplate: '/src/app/{part}/i18n/{lang}.json' }); });

예상대로 "lang"은 런타임에 언어 코드로 대체됩니다(예: "en" 또는 "tr"). "부분"은 어떻습니까? $translateProvider는 로드할 "부분"을 어떻게 압니까?

$translatePartialLoader 를 사용하여 컨트롤러 내부에서 이 정보를 제공할 수 있습니다.

 // /src/app/main/main.controller.js app.controller('MainCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('main'); }); // /src/app/toolbar/toolbar.config.js app.controller('ToolbarCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('toolbar'); });

이제 패턴이 완료되었으며 컨트롤러가 처음 실행될 때 지정된 뷰에 대한 i18n 데이터가 로드됩니다. 이는 정확히 원하는 것입니다.

캐싱: 로드 시간 줄이기

캐싱은 어떻습니까?

$translateProvider 를 사용하여 앱 구성에서 표준 캐시를 활성화할 수 있습니다.

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoaderCache(true); // default is false });

주어진 언어에 대한 캐시를 파괴해야 하는 경우 $translate 를 사용할 수 있습니다.

 $translate.refresh(languageKey); // omit languageKey to refresh all

이러한 부분이 있으면 애플리케이션이 완전히 국제화되고 여러 언어를 지원합니다.

숫자, 통화 및 날짜 현지화

이 섹션에서는 AngularJS 애플리케이션에서 숫자, 통화, 날짜 등과 같은 UI 요소의 형식을 지원하기 위해 angular-dynamic-locale 을 사용하는 방법을 배웁니다.

이를 위해 두 개의 패키지를 더 설치해야 합니다.

 npm i -S angular-dynamic-locale angular-i18n

패키지가 설치되면 앱의 종속성에 모듈을 추가할 수 있습니다.

 // /src/app/core/core.module.js app.module('app.core', ['tmh.dynamicLocale', ...]);

로케일 규칙

로케일 규칙은 날짜, 숫자, 통화 등이 $locale 서비스에 의존하는 구성 요소에 의해 형식화되어야 하는 방법에 대한 사양을 제공하는 간단한 JavaScript 파일입니다.

현재 지원되는 로케일 목록은 여기에서 확인할 수 있습니다.

다음은 월 및 날짜 형식을 보여주는 angular-locale_en-us.js 의 스니펫입니다.

 ... "MONTH": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], "SHORTDAY": [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], ...

i18n 데이터와 달리 로케일 규칙은 애플리케이션에 전역적이므로 주어진 로케일에 대한 규칙을 한 번에 모두 로드해야 합니다.

기본적으로 angular-dynamic-locale 로케일 규칙 파일이 angular/i18n/angular-locale_{{locale}}.js 에 있을 것으로 예상합니다. 다른 위치에 있는 경우 tmhDynamicLocaleProvider 를 사용하여 기본값을 재정의해야 합니다.

 // /src/app/core/core.config.js app.config(function (tmhDynamicLocaleProvider) { tmhDynamicLocaleProvider.localeLocationPattern( '/node_modules/angular-i18n/angular-locale_{{locale}}.js'); });

캐싱은 tmhDynamicLocaleCache 서비스에 의해 자동으로 처리됩니다.

로케일 규칙이 문자열 변환보다 변경될 가능성이 적기 때문에 여기서 캐시 무효화는 문제가 되지 않습니다.

로케일 사이를 전환하기 위해 angular-dynamic-localetmhDynamicLocale 서비스를 제공합니다.

 // /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, tmhDynamicLocale) { $scope.changeLocale = function (localeKey) { tmhDynamicLocale.set(localeKey); // Persist selection in cookie/local-storage/database/etc... }; });

자동 번역으로 번역 테이블 생성

로케일 규칙은 angular-i18n 패키지와 함께 제공되므로 필요에 따라 애플리케이션에서 패키지 콘텐츠를 사용할 수 있도록 하기만 하면 됩니다. 그러나 번역 테이블에 대한 JSON 파일을 어떻게 생성합니까? 다운로드하여 애플리케이션에 연결할 수 있는 패키지가 정확히 없습니다.

한 가지 옵션은 프로그래밍 방식 번역 API를 사용하는 것입니다. 특히 애플리케이션의 문자열이 변수나 복수 표현식이 없는 단순 리터럴인 경우에 그렇습니다.

Gulp와 몇 가지 추가 패키지를 사용하면 애플리케이션에 대한 프로그래밍 방식 번역을 쉽게 요청할 수 있습니다.

 import gulp from 'gulp'; import map from 'map-stream'; import rename from 'gulp-rename'; import traverse from 'traverse'; import transform from 'vinyl-transform'; import jsonFormat from 'gulp-json-format'; function translateTable(to) { return transform(() => { return map((data, done) => { const table = JSON.parse(data); const strings = []; traverse(table).forEach(function (value) { if (typeof value !== 'object') { strings.push(value); } }); Promise.all(strings.map((s) => getTranslation(s, to))) .then((translations) => { let index = 0; const translated = traverse(table).forEach(function (value) { if (typeof value !== 'object') { this.update(translations[index++]); } }); done(null, JSON.stringify(translated)); }) .catch(done); }); }); } function translate(to) { return gulp.src('src/app/**/i18n/en.json') .pipe(translateTable(to)) .pipe(jsonFormat(2)) .pipe(rename({ basename: to })) .pipe(gulp.dest('src/app')); } gulp.task('translate:tr', () => translate('tr')); This task assumes the following folder structure: /src/app/main/i18n/en.json /src/app/toolbar/i18n/en.json /src/app/navigation/i18n/en.json ...

스크립트는 먼저 모든 영어 번역 테이블을 읽고 해당 문자열 리소스에 대한 번역을 비동기적으로 요청한 다음 영어 문자열을 번역된 문자열로 교체하여 새 언어로 번역 테이블을 생성합니다.

마지막으로 새 번역 테이블은 영어 번역 테이블의 형제로 작성되어 다음을 생성합니다.

 /src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json /src/app/navigation/i18n/en.json /src/app/navigation/i18n/tr.json ...

getTranslation 의 구현도 간단합니다.

 import bluebird from 'bluebird'; import MicrosoftTranslator from 'mstranslator'; bluebird.promisifyAll(MicrosoftTranslator.prototype); const Translator = new MicrosoftTranslator({ client_id: process.env.MICROSOFT_TRANSLATOR_CLIENT_ID, client_secret: process.env.MICROSOFT_TRANSLATOR_CLIENT_SECRET }, true); function getTranslation(string, to) { const text = string; const from = 'en'; return Translator.translateAsync({ text, from, to }); }

여기서는 Microsoft 번역을 사용하고 있지만 Google 번역이나 Yandex 번역과 같은 다른 공급자를 쉽게 사용할 수 있습니다.

프로그래밍 방식 번역은 편리하지만 다음과 같은 몇 가지 단점이 있습니다.

  • 로봇 번역은 짧은 문자열에 적합하지만 그렇다고 하더라도 문맥에 따라 의미가 다른 단어에 함정이 있을 수 있습니다(예: "풀"은 수영 또는 그룹화를 의미할 수 있음).
  • API는 변수가 있는 문자열이나 메시지 형식에 의존하는 문자열을 처리하지 못할 수 있습니다.

이러한 경우 및 기타 경우에는 사람의 번역이 필요할 수 있습니다. 그러나 이는 다른 블로그 게시물의 주제입니다.

프론트엔드 국제화는 벅차게만 보입니다 .

이 기사에서는 이러한 패키지를 사용하여 AngularJS 애플리케이션을 국제화하고 현지화하는 방법을 배웠습니다.

angular-translate , angular-dynamic-localegulp 는 고통스러운 저수준 구현 세부 정보를 캡슐화하는 AngularJS 애플리케이션을 국제화하기 위한 강력한 도구입니다.

이 게시물에서 논의된 아이디어를 보여주는 데모 앱은 이 GitHub 리포지토리를 확인하세요.