帮助中心

Angular i18next

国际化(i18n)对于构建全球 Angular 应用至关重要。本指南向您展示如何在 Angular 中实现 i18next,包括正确的资源加载、初始化和使用模式。

为什么选择 i18next 来进行 Angular 国际化?

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/预渲染。

当您:

  • 需要在不重新部署的情况下更新翻译。
  • 翻译由外部管理。
  • 有一个 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}}." -->

插值允许您将动态值插入到翻译中。您可以传递变量,使用 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 的内置复数支持,正确处理所有语言中的单数/复数形式。

等待初始化

始终确保在渲染应用之前初始化 i18next,使用 provideAppInitializer() 在应用引导期间运行初始化。

翻译键的类型安全

使用 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 方法更改为接受 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 工作流程了吗?