국제화(i18n)는 글로벌 Angular 애플리케이션을 구축하는 데 필수적입니다. 이 가이드에서는 적절한 리소스 로딩, 초기화 및 사용 패턴을 통해 Angular에서 i18next를 구현하는 방법을 보여줍니다.
i18next는 가장 인기 있는 i18n 프레임워크 중 하나로, 강력한 기능과 유연성을 제공합니다:
npm 또는 yarn을 사용하여 i18next를 설치하세요:
npm install i18next이게 전부입니다! i18next는 프레임워크별 종속성이 없는 독립형 라이브러리입니다. angular-i18next나 다른 래퍼가 필요하지 않습니다. Angular 애플리케이션에서 i18next를 직접 사용하세요.
Angular에서 i18next로 번역 리소스를 로딩하는 두 가지 주요 접근 방식이 있습니다:
동적 가져오기를 사용하면 Webpack의 코드 분할 기능을 활용하여 필요할 때 번역을 로드할 수 있습니다. 이 방식은 언어가 많거나 번역 파일이 큰 애플리케이션에 이상적입니다.
장점:
예시: 동적 가져오기로 로딩
private async loadLanguageResources(
lang: string,
): Promise<Record<string, Record<string, unknown>>> {
const languageResources: Record<string, Record<string, unknown>> = {};
// Load all namespaces for the given language in parallel
await Promise.all(
NAMESPACES.map(async (namespace) => {
const translationModule = await this.loadJson(lang, namespace);
languageResources[namespace] = translationModule.default;
}),
);
return languageResources;
}
private async loadJson(
language: string,
namespace: string,
): Promise<{ default: Record<string, unknown> }> {
// Webpack automatically code-splits this into separate chunks
return import(`../../i18n/${language}/${namespace}.json`);
}이 방식은 번역 파일을 ES 모듈로 가져오므로 Webpack이 자동으로 이를 별도의 청크로 코드 분할하여 필요할 때 로드할 수 있게 합니다.
HTTP 백엔드 플러그인은 서버나 CDN에서 HTTP 요청을 통해 번역을 로드합니다. 이는 앱을 다시 빌드하지 않고도 번역을 업데이트하려는 경우 유용합니다.
장점:
단점:
예시: HTTP 백엔드로 로딩
import HttpBackend from 'i18next-http-backend';
await init({
lng: initialLanguage,
fallbackLng: DEFAULT_LANGUAGE,
backend: {
loadPath: '/assets/i18n/{{lng}}/{{ns}}.json',
},
use: [HttpBackend],
ns: NAMESPACES,
defaultNS: DEFAULT_NAMESPACE,
});이 방식은 런타임에 서버에서 번역 파일을 가져옵니다. SSR을 위해 추가 구성이 필요하다는 점에 유의하세요.
i18next를 올바르게 초기화하는 것은 런타임 오류를 방지하고 앱이 렌더링될 때 번역을 사용할 수 있도록 하는 데 중요합니다.
Angular의 provideAppInitializer 함수(Angular 18에서 도입됨)는 애플리케이션 렌더링이 시작되기 전에 i18next가 완전히 초기화되도록 보장합니다. 제공된 함수는 앱 부트스트랩 중에 실행되며, Promise가 해결될 때까지 초기화가 완료되지 않습니다. 이를 통해 번역된 텍스트 대신 번역 키가 표시되는 것을 방지합니다.
예시: App Initializer 구성
export const appConfig: ApplicationConfig = {
providers: [
// ... other providers
provideAppInitializer(initializeI18n),
// ... more providers
],
};이것이 중요한 이유:
초기화 함수는 리소스 로딩과 i18next 설정 구성을 처리합니다.
예시: I18next 초기화
public async initializeI18n(): Promise<void> {
const initialLanguage = this.getInitialLanguage();
const resources: Record<string, Record<string, Record<string, unknown>>> = {};
if (isPlatformBrowser(this.platformId)) {
// Browser: Only load the specific language needed
const languageResources = await this.loadLanguageResources(initialLanguage);
resources[initialLanguage] = languageResources;
} else {
// SSR: Load all languages for prerendering
for (const lang of SUPPORTED_LANGUAGES) {
const languageResources = await this.loadLanguageResources(lang);
resources[lang] = languageResources;
}
}
await init({
lng: initialLanguage,
fallbackLng: DEFAULT_LANGUAGE,
resources,
ns: NAMESPACES,
defaultNS: DEFAULT_NAMESPACE,
interpolation: {
escapeValue: false,
},
});
}핵심 포인트:
Angular 번역 파이프는 템플릿에서 번역을 사용하는 깔끔한 방법을 제공합니다.
예시: 번역 파이프
import { Pipe, PipeTransform } from '@angular/core';
import { t } from 'i18next';
@Pipe({
name: 't',
standalone: true,
pure: false, // Need to update when language changes
})
export class TranslatePipe implements PipeTransform {
transform(key: string, options?: Record<string, unknown>): string {
return t(key, options);
}
}파이프는 언어가 변경될 때 업데이트되도록 pure: false로 표시됩니다. 언어 변경이 번역 키 자체를 수정하지 않기 때문에 이는 중요합니다.
템플릿 내:
<!-- With namespace -->
<p>{{ "common:welcomeMessage" | t }}</p>
<!-- With nested keys -->
<p>{{ "dashboard.title" | t }}</p>
<!-- Simple interpolation -->
<p>{{ "greeting" | t: { name: userName } }}</p>
<!-- Result: "Hello, John!" from greeting: "Hello, {{name}}!" -->
<!-- With Angular pipes -->
<p>{{ "price" | t: { amount: 29.99 | currency } }}</p>
<!-- Result: "Price: $29.99" from: "Price: {{amount}}" -->
<!-- Multiple variables -->
<p>{{ "updated" | t: { date: lastModified | date, user: currentUser } }}</p>
<!-- Result: "Updated on Jan 15, 2024 by Alice" from: "Updated on {{date}} by {{user}}." -->
보간을 사용하면 번역에 동적 값을 삽입할 수 있습니다. 변수를 전달하고, 서식 지정을 위해 Angular 파이프를 사용하며, 단일 번역 문자열에 여러 값을 결합할 수 있습니다.
컴포넌트 코드 내:
import { Component } from '@angular/core';
import { t } from 'i18next';
@Component({
selector: 'app-dashboard',
template: `
<h1>{{ title }}</h1>
<p>{{ welcomeMsg }}</p>
`,
})
export class DashboardComponent {
title = t('dashboard.title');
welcomeMsg = t('greeting', { name: 'John' });
}템플릿 사용의 경우 파이프 구문이 깔끔하고 읽기 쉽습니다. 프로그래밍 방식의 액세스를 위해서는 t()를 직접 호출하세요. 이는 알림 표시, 동적 제목 설정 또는 컴포넌트 로직 내에서 번역을 처리할 때 유용합니다.
'loginTitle'이나 원시 텍스트 대신 'auth:login.title'과 같이 앱 구조를 반영하는 점 표기법을 사용하세요.
더 나은 유지 관리성을 위해 번역을 논리적 네임스페이스(예: common.json, auth.json, dashboard.json)로 분할하세요.
모든 언어에서 단수/복수 형태를 올바르게 처리하려면 i18next의 내장 복수형 지원을 사용하세요.
앱 부트스트랩 중에 초기화를 실행하려면 항상 provideAppInitializer()를 사용하여 앱을 렌더링하기 전에 i18next가 초기화되었는지 확인하세요.
TypeScript를 사용하여 JSON 파일에서 직접 번역 키를 추출하여 완벽한 타입 안전성을 확보하세요. 타입 정의를 수동으로 유지 관리하는 대신, TypeScript의 typeof와 동적 가져오기를 활용하여 실제 번역 파일에서 타입을 자동으로 생성하세요.
재귀적 NestedKeys 도우미 타입을 사용하여 JSON 번역 파일에서 가능한 모든 키를 추출하는 타입을 정의하세요.
import common from './en/common.json'; // defaultNS: DEFAULT_NAMESPACE,
import auth from './en/auth.json'; // namespaces
type NestedKeys<T, Prefix extends string = ''> = T extends object
? {
[K in keyof T & (string | number)]: T[K] extends object
?
| NestedKeys<T[K], `${Prefix}${K & (string | number)}.`>
| `${Prefix}${K & (string | number)}`
: `${Prefix}${K & (string | number)}`;
}[keyof T & (string | number)]
: never;
export type TranslationKey =
| `common:${NestedKeys<typeof common>}`
| `auth:${NestedKeys<typeof auth>}`;파이프의 transform 메서드가 string 대신 TranslationKey를 허용하도록 변경하세요. 이는 템플릿에서 타입 안전성을 제공하고 유효한 번역 키만 사용되도록 보장합니다.
import { Pipe, PipeTransform } from '@angular/core';
import { t } from 'i18next';
import { TranslationKey } from '../../i18n/translation-key';
@Pipe({
name: 't',
standalone: true,
pure: false, // Need to update when language changes
})
export class TranslatePipe implements PipeTransform {
transform(key: TranslationKey, options?: Record<string, unknown>): string {
return t(key, options);
}
}
이제 TypeScript 코드와 템플릿 모두에서 유효하지 않은 키에 대한 전체 IntelliSense 자동 완성 및 컴파일 타임 오류를 얻을 수 있습니다.
t('common:greeting'); // ✅ Autocomplete
t('auth:login.title'); // ✅ Valid
t('invalid.key'); // ❌ IDE shows error
{{ "common:greeting" | t }} // ✅ Valid in templates
{{ "invalid.key" | t }} // ❌ IDE shows error타입 안전 번역 키의 이점:
지능형 언어 감지를 구현하고 사용자가 런타임에 자동 리소스 로딩과 함께 언어를 전환할 수 있도록 하세요.
우선순위에 따라 여러 소스를 확인하여 앱이 시작될 때 사용할 사용자의 언어를 결정하세요.
private getInitialLanguage(): string {
if (isPlatformBrowser(this.platformId)) {
// Try to get language from localStorage (user preference)
const savedLang = localStorage.getItem('language');
if (savedLang) {
return savedLang;
}
// Fall back to browser language
return navigator.language ?? DEFAULT_LANGUAGE;
}
// Default language for SSR
return DEFAULT_LANGUAGE;
}이 방식은 먼저 localStorage(사용자 기본 설정)를 확인한 다음 브라우저의 언어(navigator.language)로 대체하고, 마지막으로 앱의 기본 언어를 사용합니다.
런타임에 언어를 전환하면서 필요에 따라 번역 리소스를 자동으로 로드하세요.
public async switchLanguage(
language: string
): Promise<void> {
// In browser, check if we need to load the translation first
if (isPlatformBrowser(this.platformId)) {
const i18next = await import('i18next');
await Promise.all(
NAMESPACES.map(async (namespace) => {
const currentResources = i18next.default.getResourceBundle(
lang,
namespace,
);
// Check if any namespace is missing for this language
if (!currentResources) {
// Translation not loaded yet, load it dynamically
const translationModule = await this.loadJson(lang, namespace);
i18next.default.addResourceBundle(
lang,
namespace,
translationModule.default,
);
}
}),
);
}
// Switch to the new language
await changeLanguage(lang);
// Save preference to localStorage
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem('language', lang);
}
}동적 가져오기를 사용할 때 번역은 필요할 때 로드됩니다. switchLanguage() 메서드는 getResourceBundle()을 사용하여 리소스가 이미 로드되었는지 확인합니다. 누락된 경우 addResourceBundle()을 사용하여 동적으로 가져와 추가합니다. 이는 중복된 네트워크 요청을 방지하면서 필요한 모든 번역을 사용할 수 있도록 합니다.
핵심 구현 세부 사항:
여러 언어에 걸쳐 수동으로 번역을 관리하는 것은 시간이 많이 걸리고 오류가 발생하기 쉽습니다. 이때 AI 기반 현지화가 필요합니다.
l10n.dev는 i18next JSON 파일을 위해 특별히 설계된 AI 기반 번역 서비스입니다:
수동 번역 작업 시간을 절약하고 깨진 플레이스홀더나 잘못된 복수형과 같은 일반적인 오류를 방지하세요.
Angular i18n 워크플로를 간소화할 준비가 되셨나요?
적절한 초기화 및 리소스 로딩 전략을 사용하여 Angular에서 i18next를 구현하면 원활한 국제화 경험을 보장할 수 있습니다.
l10n.dev와 같은 AI 기반 번역 서비스와 결합하면 더 빠르고 오류 없이 진정한 글로벌 애플리케이션을 구축할 수 있습니다.
오늘 바로 다국어 Angular 앱 구축을 시작하세요!