如何利用Angular 18内置i18n实现路由路径国际化?
Angular 18 内置i18n实现语言专属路由路径配置指南
一、两种实现方案选择
Angular内置i18n(@angular/localize)支持编译时多语言和运行时动态切换两种模式,对应不同的路由实现方式:
方案1:编译时多语言(推荐,性能最优)
这种模式下每个语言会被打包成独立的应用包,路由路径在构建阶段就完成翻译,适合不需要运行时切换语言的场景。
1.1 配置i18n与路由翻译键
- 在
angular.json中启用localize并配置语言:"projects": { "your-app": { "i18n": { "sourceLocale": "en-US", "locales": { "fr": "src/locale/messages.fr.xlf" } }, "architect": { "build": { "options": { "localize": true }, "configurations": { "en": { "localize": ["en-US"] }, "fr": { "localize": ["fr"] } } } } } } - 在路由配置中使用
$localize标记翻译键:import { Routes } from '@angular/router'; export const routes: Routes = [ { path: $localize`:@@ROUTE.LOGIN:login`, loadComponent: () => import('./login/login.component') }, { path: $localize`:@@ROUTE.DASHBOARD:dashboard`, loadComponent: () => import('./dashboard/dashboard.component') }, { path: '', redirectTo: $localize`:@@ROUTE.LOGIN:login`, pathMatch: 'full' } ]; - 提取并翻译路由键:
运行ng extract-i18n --output-path src/locale生成默认语言的翻译源messages.xlf,复制为messages.fr.xlf并添加路由翻译:<trans-unit id="ROUTE.LOGIN" datatype="html"> <source>login</source> <target>se-connecter</target> </trans-unit> <trans-unit id="ROUTE.DASHBOARD" datatype="html"> <source>dashboard</source> <target>tableau-de-bord</target> </trans-unit>
1.2 构建与部署
- 分别构建各语言包:
ng build --configuration=en --output-path dist/en ng build --configuration=fr --output-path dist/fr - 将构建产物部署到服务器的对应子目录(如
/en和/fr),访问yourdomain/en/login或yourdomain/fr/se-connecter即可进入对应语言的路由。
方案2:运行时动态切换语言
如果需要在应用运行中切换语言并实时更新路由路径,需结合自定义路由序列化器和翻译服务实现。
2.1 配置基础路由与翻译服务
- 定义带语言参数的路由结构:
import { Routes } from '@angular/router'; export const routes: Routes = [ { path: ':lang', children: [ { path: 'login', loadComponent: () => import('./login/login.component') }, { path: 'dashboard', loadComponent: () => import('./dashboard/dashboard.component') } ] }, { path: '', redirectTo: '/en/login', pathMatch: 'full' } ]; - 创建路由翻译服务,维护路由键与各语言的映射:
import { Injectable } from '@angular/core'; import { LOCALE_ID, Inject } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class RouteTranslationService { private routeMap = { en: { login: 'login', dashboard: 'dashboard' }, fr: { login: 'se-connecter', dashboard: 'tableau-de-bord' } }; constructor(@Inject(LOCALE_ID) private currentLocale: string) {} // 获取当前语言的翻译路径 getTranslatedPath(routeKey: string): string { return this.routeMap[this.currentLocale]?.[routeKey] || routeKey; } // 根据路径反向查找路由键 getRouteKeyFromPath(path: string, lang: string): string { const langMap = this.routeMap[lang]; return Object.keys(langMap).find(key => langMap[key] === path) || path; } }
2.2 自定义URL序列化器
通过重写UrlSerializer实现路径的动态翻译:
import { Injectable } from '@angular/core'; import { DefaultUrlSerializer, UrlTree } from '@angular/router'; import { RouteTranslationService } from './route-translation.service'; import { TranslateService } from '@ngx-translate/core'; @Injectable() export class TranslatedUrlSerializer extends DefaultUrlSerializer { constructor( private routeTranslationService: RouteTranslationService, private translateService: TranslateService ) { super(); } override parse(url: string): UrlTree { // 解析URL时将翻译路径转为原始路由键 const segments = url.split('/').filter(seg => seg); if (segments.length < 2) return super.parse(url); const lang = segments[0]; const translatedPath = segments[1]; const routeKey = this.routeTranslationService.getRouteKeyFromPath(translatedPath, lang); const parsedUrl = `/${lang}/${routeKey}`; return super.parse(parsedUrl); } override serialize(tree: UrlTree): string { // 序列化URL时将原始路由键转为翻译路径 const primarySegments = tree.root.children['primary']?.segments; if (!primarySegments || primarySegments.length < 2) return super.serialize(tree); const lang = primarySegments[0].path; const routeKey = primarySegments[1].path; const translatedPath = this.routeTranslationService.getTranslatedPath(routeKey); return `/${lang}/${translatedPath}`; } }
2.3 注册序列化器与导航处理
- 在
app.config.ts中提供自定义序列化器:import { ApplicationConfig, provideRouter } from '@angular/core'; import { routes } from './app.routes'; import { UrlSerializer } from '@angular/router'; import { TranslatedUrlSerializer } from './translated-url-serializer'; import { provideHttpClient } from '@angular/common/http'; import { provideTranslateCore } from '@ngx-translate/core'; import { TranslateLoader } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { HttpClient } from '@angular/common/http'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); } export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), provideHttpClient(), { provide: UrlSerializer, useClass: TranslatedUrlSerializer }, provideTranslateCore(), { provide: TranslateLoader, useFactory: HttpLoaderFactory, deps: [HttpClient] } ] }; - 在组件中处理语言切换与导航:
import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { RouteTranslationService } from './route-translation.service'; import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-header', template: ` <a [routerLink]="getLoginPath()">Login</a> <a [routerLink]="getDashboardPath()">Dashboard</a> <button (click)="switchLang('en')">English</button> <button (click)="switchLang('fr')">Français</button> ` }) export class HeaderComponent { constructor( private router: Router, private routeTranslationService: RouteTranslationService, private translateService: TranslateService ) {} getLoginPath() { const lang = this.translateService.currentLang; return `/${lang}/${this.routeTranslationService.getTranslatedPath('login')}`; } getDashboardPath() { const lang = this.translateService.currentLang; return `/${lang}/${this.routeTranslationService.getTranslatedPath('dashboard')}`; } switchLang(lang: string) { this.translateService.use(lang); // 切换语言后重定向到当前路由的翻译路径 const currentSegments = this.router.url.split('/').filter(seg => seg); if (currentSegments.length < 2) return; const routeKey = this.routeTranslationService.getRouteKeyFromPath(currentSegments[1], this.translateService.currentLang); const newPath = this.routeTranslationService.getTranslatedPath(routeKey); this.router.navigate([`/${lang}/${newPath}`]); } }
二、最佳实践
- 优先选择编译时多语言:编译时生成的包体积更小、性能更好,且无需运行时翻译逻辑,适合大多数无需动态切换语言的场景。
- 统一管理路由翻译键:将所有路由翻译键集中放在xlf或json文件中,避免分散在代码里,便于维护和批量更新。
- 验证语言参数合法性:在路由守卫(如
CanActivate)中检查lang参数是否为支持的语言,非法参数自动重定向到默认语言路由。 - 持久化用户语言选择:用
localStorage或cookie保存用户选择的语言,应用初始化时自动加载对应语言和路由。 - SEO优化:为每个语言路由添加
hreflang标签,帮助搜索引擎识别多语言内容:<link rel="alternate" hreflang="en" href="https://yourdomain.com/en/login"> <link rel="alternate" hreflang="fr" href="https://yourdomain.com/fr/se-connecter"> - 避免过度嵌套路由:带语言参数的路由已增加一层路径,尽量保持路由结构扁平化,减少维护复杂度。
- 全面测试多语言路由:针对每个支持的语言,测试路由跳转、刷新、反向解析等场景,确保无404或路径错误。
内容的提问来源于stack exchange,提问作者Gladitor
相关产品推荐
相关产品推荐

