도움말 센터

Angular i18next

국제화(i18n)는 글로벌 Angular 애플리케이션을 구축하는 데 필수적입니다. 이 가이드에서는 적절한 리소스 로딩, 초기화 및 사용 패턴을 통해 Angular에서 i18next를 구현하는 방법을 보여줍니다.

왜 Angular에 i18next인가요?

i18next는 가장 인기 있는 i18n 프레임워크 중 하나로, 강력한 기능과 유연성을 제공합니다:

  • 프레임워크 독립적 - Angular, React, Vue 등과 원활하게 작동합니다.
  • 강력한 보간, 복수형 및 컨텍스트 지원.
  • 지연 로딩 및 코드 분할 지원.
  • 타입 안전성을 갖춘 TypeScript 지원.
  • 플러그인과 확장 기능이 풍부한 거대한 생태계.

설치

npm 또는 yarn을 사용하여 i18next를 설치하세요:

npm install i18next

이게 전부입니다! i18next는 프레임워크별 종속성이 없는 독립형 라이브러리입니다. angular-i18next나 다른 래퍼가 필요하지 않습니다. Angular 애플리케이션에서 i18next를 직접 사용하세요.

번역 리소스 로딩

Angular에서 i18next로 번역 리소스를 로딩하는 두 가지 주요 접근 방식이 있습니다:

동적 가져오기 (권장)

동적 가져오기를 사용하면 Webpack의 코드 분할 기능을 활용하여 필요할 때 번역을 로드할 수 있습니다. 이 방식은 언어가 많거나 번역 파일이 큰 애플리케이션에 이상적입니다.

장점:

  • 번들 크기 최적화 - 필요한 번역만 로드합니다.
  • 더 나은 성능 - 초기 로딩 시간이 단축됩니다.
  • SSR 및 사전 렌더링과 완벽하게 작동합니다.
  • 추가 HTTP 요청이 필요하지 않습니다.

예시: 동적 가져오기로 로딩

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이 자동으로 이를 별도의 청크로 코드 분할하여 필요할 때 로드할 수 있게 합니다.

i18next-http-backend

HTTP 백엔드 플러그인은 서버나 CDN에서 HTTP 요청을 통해 번역을 로드합니다. 이는 앱을 다시 빌드하지 않고도 번역을 업데이트하려는 경우 유용합니다.

장점:

  • 재배포 없이 번역 업데이트 가능.
  • 외부 CDN에서 번역 로드.
  • 런타임 번역 관리에 유용.

단점:

  • 추가 HTTP 요청으로 초기 로딩 속도 저하.
  • 네트워크 연결 필요.
  • 더 복잡한 SSR 설정.

예시: 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을 위해 추가 구성이 필요하다는 점에 유의하세요.

어떤 방식을 선택해야 할까요?

다음 경우에 동적 가져오기를 사용하세요:

  • 최적의 번들 크기와 성능을 원할 때.
  • 번역이 빌드 프로세스의 일부일 때.
  • SSR/사전 렌더링 지원이 필요할 때.

다음 경우에 HTTP 백엔드를 사용하세요:

  • 재배포 없이 번역을 업데이트해야 할 때.
  • 번역이 외부에서 관리될 때.
  • 번역 파일을 제공할 CDN이 있을 때.

올바른 i18next 초기화

i18next를 올바르게 초기화하는 것은 런타임 오류를 방지하고 앱이 렌더링될 때 번역을 사용할 수 있도록 하는 데 중요합니다.

provideAppInitializer 사용

Angular의 provideAppInitializer 함수(Angular 18에서 도입됨)는 애플리케이션 렌더링이 시작되기 전에 i18next가 완전히 초기화되도록 보장합니다. 제공된 함수는 앱 부트스트랩 중에 실행되며, Promise가 해결될 때까지 초기화가 완료되지 않습니다. 이를 통해 번역된 텍스트 대신 번역 키가 표시되는 것을 방지합니다.

예시: App Initializer 구성

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers
    provideAppInitializer(initializeI18n),
    // ... more providers
  ],
};

이것이 중요한 이유:

  • 함수가 애플리케이션 시작 시 주입 컨텍스트 내에서 실행됩니다.
  • 초기 로딩 시 깜박임이나 번역 키 표시를 방지합니다.
  • 처음부터 모든 컴포넌트에서 번역을 사용할 수 있도록 보장합니다.
  • SSR, 사전 렌더링 및 클라이언트 측 렌더링과 올바르게 작동합니다.

i18next 초기화 함수

초기화 함수는 리소스 로딩과 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,
    },
  });
}

핵심 포인트:

  • 최적의 성능을 위해 브라우저에서는 현재 언어만 로드하세요.
  • 사전 렌더링 지원을 위해 SSR 중에는 모든 언어를 로드하세요.
  • 누락된 번역을 원활하게 처리하려면 fallbackLng을 사용하세요.
  • 더 나은 구성을 위해 네임스페이스를 설정하세요.

번역 파이프

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()를 직접 호출하세요. 이는 알림 표시, 동적 제목 설정 또는 컴포넌트 로직 내에서 번역을 처리할 때 유용합니다.

Angular i18next 모범 사례

의미 있는 키 사용

'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

타입 안전 번역 키의 이점:

  • 모든 네임스페이스에서 사용 가능한 모든 번역 키에 대한 IDE 자동 완성.
  • 존재하지 않는 키를 사용할 경우 컴파일 타임 오류 발생, 런타임 전 오타 포착.
  • 리팩토링 지원 - 코드베이스 전체에서 키를 안전하게 이름 변경.
  • 제로 런타임 오버헤드 - 컴파일 중에 타입이 완전히 제거됨.
  • 실제 JSON 파일에서 자동 생성 - 수동 타입 유지 관리 불필요.

언어 감지 및 전환

지능형 언어 감지를 구현하고 사용자가 런타임에 자동 리소스 로딩과 함께 언어를 전환할 수 있도록 하세요.

우선순위에 따라 여러 소스를 확인하여 앱이 시작될 때 사용할 사용자의 언어를 결정하세요.

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()을 사용하여 동적으로 가져와 추가합니다. 이는 중복된 네트워크 요청을 방지하면서 필요한 모든 번역을 사용할 수 있도록 합니다.

핵심 구현 세부 사항:

  • 사용자의 선호 언어를 복원하기 위해 먼저 localStorage를 확인하세요.
  • 브라우저의 언어 기본 설정을 위해 navigator.language를 대체 수단으로 사용하세요.
  • addResourceBundle()을 사용하여 누락된 리소스를 동적으로 로드하세요.

AI로 번역 자동화

여러 언어에 걸쳐 수동으로 번역을 관리하는 것은 시간이 많이 걸리고 오류가 발생하기 쉽습니다. 이때 AI 기반 현지화가 필요합니다.

왜 l10n.dev인가요?

l10n.dev는 i18next JSON 파일을 위해 특별히 설계된 AI 기반 번역 서비스입니다:

  • JSON 구조, 키 및 중첩 객체를 보존합니다.
  • {{name}}, {{count}}와 같은 플레이스홀더를 올바르게 유지합니다.
  • 모든 언어에 대한 복수형을 자동으로 생성합니다.
  • 컨텍스트와 보간을 지능적으로 처리합니다.
  • 네임스페이스를 포함한 모든 i18next 기능을 지원합니다.

간단한 워크플로

  1. 기본 언어 JSON 파일(예: en/common.json)을 내보내세요.
  2. l10n.dev에 업로드하고 대상 언어를 선택하세요.
  3. AI가 컨텍스트 인식 및 적절한 서식으로 번역합니다.
  4. Angular 앱에서 사용할 준비가 된 번역된 파일을 다운로드하세요.

수동 번역 작업 시간을 절약하고 깨진 플레이스홀더나 잘못된 복수형과 같은 일반적인 오류를 방지하세요.

Angular i18n 워크플로를 간소화할 준비가 되셨나요?