도움 센터

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>> = {};

  for (const namespace of NAMESPACES) {
    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가 해결될 때까지 완료되지 않습니다. 이는 번역 키가 번역된 텍스트 대신 나타나는 것을 방지합니다.

예시: 앱 초기화기 구성

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);
  }
}

파이프는 언어가 변경될 때 업데이트되도록 순수: 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}}." -->

보간(interpolation)을 통해 번역에 동적 값을 삽입할 수 있습니다. 변수를 전달하고, 형식을 지정하기 위해 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를 위한 모범 사례

의미 있는 키 사용하기

앱 구조를 반영하는 점 표기법을 사용하세요: 'auth:login.title' 대신 'loginTitle' 또는 원시 텍스트를 사용합니다.

네임스페이스로 조직하기

번역을 논리적인 네임스페이스(예: 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>}`;

파이프의 변환 메서드를 문자열 대신 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');

    // Check if any namespace is missing for this language
    for (const namespace of NAMESPACES) {
      const currentResources = i18next.default.getResourceBundle(lang, namespace);

      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 워크플로를 간소화할 준비가 되셨나요?