帮助中心

Angular i18next

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

为什么在Angular中使用i18next?

i18next是最受欢迎的国际化框架之一,提供强大的功能和灵活性:

  • 框架无关 - 可与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最佳实践

使用有意义的翻译键

使用反映应用结构的点符号:例如 '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>}`;

将管道的transform方法修改为接受TranslationKey而不是string。这在模板中提供了类型安全,并确保只能使用有效的翻译键。

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国际化工作流程了吗?