You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular Universal与ngx-translate SSR先显示翻译键的问题求助

Angular SSR + ngx-translate 解决初始渲染显示翻译键的问题

问题核心是SSR阶段服务端未在渲染组件前完成翻译文件加载,导致初始HTML输出翻译键而非译文。以下是可行的解决方案:

1. 实现服务端专用的同步翻译加载器

服务端无法像浏览器那样异步请求翻译文件,需要用文件系统同步读取本地翻译文件,避免渲染流程提前执行。

创建server-translate-loader.ts:

import { TranslateLoader } from '@ngx-translate/core';
import * as fs from 'fs';
import * as path from 'path';

export class ServerTranslateLoader implements TranslateLoader {
  getTranslation(lang: string): Promise<any> {
    // 翻译文件路径,根据你的项目结构调整
    const filePath = path.join(process.cwd(), 'dist/your-project-name/browser/assets/i18n', `${lang}.json`);
    const translation = JSON.parse(fs.readFileSync(filePath, 'utf8'));
    return Promise.resolve(translation);
  }
}

2. 区分环境配置TranslateModule

在app.module.ts和app.server.module.ts中分别配置浏览器和服务端的加载器,同时用APP_INITIALIZER确保翻译文件在应用初始化前加载完成。

浏览器端配置(app.module.ts)

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClient } from '@angular/common/http';
import { AppComponent } from './app.component';

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule.withServerTransition({ appId: 'your-app-id' }),
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

服务端配置(app.server.module.ts)

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { ServerTranslateLoader } from './server-translate-loader';
import { APP_INITIALIZER } from '@angular/core';

// 初始化翻译服务,确保服务端渲染前加载完成
export function translateInitializer(translateService: TranslateService) {
  return () => {
    // 这里可以根据请求头/URL参数动态设置语言,示例默认用'en'
    const lang = 'en';
    translateService.setDefaultLang(lang);
    return translateService.use(lang).toPromise();
  };
}

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useClass: ServerTranslateLoader
      }
    })
  ],
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: translateInitializer,
      deps: [TranslateService],
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppServerModule {}

3. 用TransferState传递服务端翻译数据到客户端

避免客户端重复请求翻译文件,同时确保客户端初始化时直接使用服务端已加载的翻译数据。

修改服务端加载器,存入TransferState

更新server-translate-loader.ts:

import { TranslateLoader } from '@ngx-translate/core';
import * as fs from 'fs';
import * as path from 'path';
import { TransferState, makeStateKey } from '@angular/platform-browser';

const TRANSLATION_KEY = makeStateKey<any>('translations');

export class ServerTranslateLoader implements TranslateLoader {
  constructor(private transferState: TransferState) {}

  getTranslation(lang: string): Promise<any> {
    const filePath = path.join(process.cwd(), 'dist/your-project-name/browser/assets/i18n', `${lang}.json`);
    const translation = JSON.parse(fs.readFileSync(filePath, 'utf8'));
    // 将翻译数据存入TransferState
    this.transferState.set(TRANSLATION_KEY, { [lang]: translation });
    return Promise.resolve(translation);
  }
}

客户端加载器优先读取TransferState

更新app.module.ts中的HttpLoaderFactory:

import { TransferState, makeStateKey } from '@angular/platform-browser';

const TRANSLATION_KEY = makeStateKey<any>('translations');

export function HttpLoaderFactory(http: HttpClient, transferState: TransferState) {
  const httpLoader = new TranslateHttpLoader(http, './assets/i18n/', '.json');
  return {
    getTranslation(lang: string): Promise<any> {
      // 先检查TransferState中是否有服务端传递的翻译数据
      const storedTranslations = transferState.get(TRANSLATION_KEY, {});
      if (storedTranslations[lang]) {
        return Promise.resolve(storedTranslations[lang]);
      }
      // 没有则发起HTTP请求
      return httpLoader.getTranslation(lang);
    }
  } as TranslateLoader;
}

// 在TranslateModule的loader配置中,deps添加TransferState

4. 服务端动态获取用户语言

如果需要根据用户请求动态设置语言,在server.ts中处理请求时获取语言(比如从Accept-Language头或URL参数),并传递给Angular应用:

// server.ts中修改render函数
server.get('*', (req, res) => {
  // 从请求头获取语言
  const acceptLang = req.headers['accept-language']?.split(',')[0] || 'en';
  const lang = acceptLang.split('-')[0]; // 简化为'en'/'zh'等

  res.render(indexHtml, {
    req,
    providers: [
      { provide: 'REQUEST_LANG', useValue: lang }
    ]
  });
});

然后在app.server.module.ts的translateInitializer中注入REQUEST_LANG来设置语言:

import { Inject } from '@angular/core';

export function translateInitializer(translateService: TranslateService, @Inject('REQUEST_LANG') lang: string) {
  return () => {
    translateService.setDefaultLang('en');
    return translateService.use(lang).toPromise();
  };
}

// 同时在providers中添加REQUEST_LANG的注入声明

关键注意事项

  • 服务端必须同步读取翻译文件,异步读取会导致SSR渲染提前完成,依然显示翻译键。
  • APP_INITIALIZER的工厂函数必须返回Promise,确保翻译加载完成后再初始化应用。
  • 确保翻译文件的路径正确,特别是服务端编译后的文件位置(通常在dist/项目名/browser/assets/i18n下)。

内容的提问来源于stack exchange,提问作者Sallommea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 22:49:53