国际化(i18n)和本地化(l10n)是构建全球可用应用的关键技术,让应用能够适应不同语言、地区和文化习惯。
点击切换语言查看实时翻译效果
// angular.json - 配置多语言构建
{
"projects": {
"my-app": {
"i18n": {
"sourceLocale": "zh-Hans", // 源语言
"locales": {
"en": "src/locale/messages.en.xlf", // 英语翻译
"es": "src/locale/messages.es.xlf", // 西班牙语
"ja": "src/locale/messages.ja.xlf", // 日语
"fr": "src/locale/messages.fr.xlf" // 法语
}
},
"architect": {
"build": {
"configurations": {
"en": {
"localize": ["en"], // 构建英语版本
"outputPath": "dist/my-app/en/"
},
"es": {
"localize": ["es"], // 构建西班牙语版本
"outputPath": "dist/my-app/es/"
}
}
},
"serve": {
"configurations": {
"en": {
"browserTarget": "my-app:build:en" // 服务英语版本
}
}
}
}
}
}
}
// package.json - 添加构建脚本
{
"scripts": {
"build:zh": "ng build --localize", // 构建所有语言
"build:en": "ng build --configuration=en",
"build:es": "ng build --configuration=es",
"serve:en": "ng serve --configuration=en",
"extract-i18n": "ng extract-i18n --output-path src/locale"
}
}
// 提取翻译字符串
ng extract-i18n --output-path src/locale
// 构建特定语言版本
ng build --configuration=ja
// 构建所有语言版本
ng build --localize
<!-- 基本翻译标记 -->
<h1 i18n>欢迎来到我的应用</h1>
<p i18n>这是一个多语言演示应用</p>
<!-- 带有描述的翻译 -->
<button i18n="按钮描述|提交按钮@@submitButton">
提交
</button>
<!-- 带有上下文的翻译 -->
<span i18n="用户菜单|欢迎词">欢迎,{{user.name}}</span>
<!-- 复数形式 -->
<span i18n>
{count, plural,
=0 {没有消息}
=1 {有一条消息}
other {有{{count}}条消息}}
</span>
<!-- 选择形式(性别) -->
<span i18n>
{gender, select,
male {他}
female {她}
other {他们}}
分享了照片
</span>
<!-- 嵌套翻译 -->
<div i18n>
价格: <strong>{{price | currency}}</strong>
数量: <strong>{{quantity}}</strong>
</div>
<!-- 属性翻译 -->
<input
[placeholder]="'搜索...'"
i18n-placeholder
i18n-title="输入框标题|搜索输入@@searchTitle"
title="输入关键字搜索">
<img
[src]="logoUrl"
i18n-alt
alt="应用Logo">
翻译工作流程:
1. 提取: ng extract-i18n → messages.xlf
2. 翻译: 编辑XLIFF文件
3. 构建: ng build --localize
4. 部署: 不同语言的构建版本
XLIFF文件结构:
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="zh-Hans" datatype="plaintext">
<body>
<trans-unit id="welcomeHeader">
<source>欢迎来到我的应用</source>
<target>Welcome to My App</target>
<note>首页标题</note>
</trans-unit>
<trans-unit id="submitButton">
<source>提交</source>
<target>Submit</target>
</trans-unit>
</body>
</file>
</xliff>
<!-- messages.en.xlf -->
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="zh-Hans" target-language="en" datatype="plaintext">
<body>
<!-- 简单文本翻译 -->
<trans-unit id="welcomeHeader" datatype="html">
<source>欢迎来到我的应用</source>
<target>Welcome to My Application</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<!-- 带插值的翻译 -->
<trans-unit id="userGreeting" datatype="html">
<source>欢迎,<x id="INTERPOLATION" equiv-text="{{user.name}}"/></source>
<target>Welcome, <x id="INTERPOLATION" equiv-text="{{user.name}}"/></target>
</trans-unit>
<!-- 复数形式 -->
<trans-unit id="messageCount" datatype="html">
<source>{VAR_PLURAL, plural, =0 {没有消息} =1 {有一条消息} other {有<x id="INTERPOLATION" equiv-text="{{count}}"/>条消息}}</source>
<target>{VAR_PLURAL, plural, =0 {No messages} =1 {One message} other {<x id="INTERPOLATION" equiv-text="{{count}}"/> messages}}</target>
</trans-unit>
<!-- 选择形式 -->
<trans-unit id="genderMessage" datatype="html">
<source>{VAR_SELECT, select, male {他} female {她} other {他们}}分享了照片</source>
<target>{VAR_SELECT, select, male {He} female {She} other {They}} shared a photo</target>
</trans-unit>
</body>
</file>
</xliff>
对于需要运行时语言切换的应用,推荐使用ngx-translate库。
# 安装ngx-translate
npm install @ngx-translate/core @ngx-translate/http-loader
// app.module.ts - 配置ngx-translate
import { NgModule } from '@angular/core';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
// 创建翻译加载器
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
@NgModule({
imports: [
HttpClientModule,
TranslateModule.forRoot({
defaultLanguage: 'zh',
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
})
],
exports: [TranslateModule]
})
export class AppModule { }
// app.component.ts - 初始化翻译
import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(private translate: TranslateService) {
// 设置支持的语言
translate.addLangs(['zh', 'en', 'es', 'ja', 'fr']);
// 设置默认语言
translate.setDefaultLang('zh');
// 获取浏览器语言
const browserLang = translate.getBrowserLang();
// 使用浏览器语言或默认语言
const useLang = translate.getLangs().includes(browserLang)
? browserLang
: 'zh';
translate.use(useLang);
}
// 切换语言
switchLanguage(lang: string): void {
this.translate.use(lang);
localStorage.setItem('userLang', lang);
}
}
// assets/i18n/zh.json - 中文翻译
{
"HEADER": {
"TITLE": "我的应用",
"WELCOME": "欢迎,{{name}}",
"LOGIN": "登录",
"LOGOUT": "退出"
},
"HOME": {
"TITLE": "首页",
"DESCRIPTION": "这是一个多语言演示应用",
"FEATURES": "主要特性",
"FEATURES_LIST": [
"多语言支持",
"响应式设计",
"高性能",
"易于使用"
]
},
"USER": {
"PROFILE": "用户资料",
"SETTINGS": "设置",
"MESSAGES": {
"ZERO": "没有消息",
"ONE": "有一条新消息",
"OTHER": "有{{count}}条新消息",
"GENDER": {
"MALE": "他有{{count}}条消息",
"FEMALE": "她有{{count}}条消息",
"OTHER": "他们有{{count}}条消息"
}
}
},
"BUTTONS": {
"SAVE": "保存",
"CANCEL": "取消",
"DELETE": "删除",
"CONFIRM": "确认"
},
"VALIDATION": {
"REQUIRED": "此字段为必填项",
"EMAIL": "请输入有效的邮箱地址",
"MIN_LENGTH": "最少需要{{min}}个字符",
"MAX_LENGTH": "最多允许{{max}}个字符"
}
}
// assets/i18n/en.json - 英文翻译
{
"HEADER": {
"TITLE": "My Application",
"WELCOME": "Welcome, {{name}}",
"LOGIN": "Login",
"LOGOUT": "Logout"
},
"HOME": {
"TITLE": "Home",
"DESCRIPTION": "This is a multilingual demo application",
"FEATURES": "Main Features",
"FEATURES_LIST": [
"Multilingual support",
"Responsive design",
"High performance",
"Easy to use"
]
},
"USER": {
"PROFILE": "User Profile",
"SETTINGS": "Settings",
"MESSAGES": {
"ZERO": "No messages",
"ONE": "One new message",
"OTHER": "{{count}} new messages",
"GENDER": {
"MALE": "He has {{count}} messages",
"FEMALE": "She has {{count}} messages",
"OTHER": "They have {{count}} messages"
}
}
},
"BUTTONS": {
"SAVE": "Save",
"CANCEL": "Cancel",
"DELETE": "Delete",
"CONFIRM": "Confirm"
},
"VALIDATION": {
"REQUIRED": "This field is required",
"EMAIL": "Please enter a valid email address",
"MIN_LENGTH": "Minimum {{min}} characters required",
"MAX_LENGTH": "Maximum {{max}} characters allowed"
}
}
<!-- 使用translate管道 -->
<h1>{{ 'HEADER.TITLE' | translate }}</h1>
<p>{{ 'HOME.DESCRIPTION' | translate }}</p>
<!-- 带参数的翻译 -->
<span>{{ 'HEADER.WELCOME' | translate:{name: userName} }}</span>
<!-- 复数形式 -->
<span>
{{ 'USER.MESSAGES.ZERO' | translate }}
{{ 'USER.MESSAGES.ONE' | translate }}
{{ 'USER.MESSAGES.OTHER' | translate:{count: messageCount} }}
</span>
<!-- 使用translate指令 -->
<button translate>BUTTONS.SAVE</button>
<h2 translate>HOME.TITLE</h2>
<!-- 属性翻译 -->
<input
[placeholder]="'VALIDATION.EMAIL' | translate"
[title]="'VALIDATION.REQUIRED' | translate">
<!-- 复数指令 -->
<span
[translate]="'USER.MESSAGES.ZERO'"
[translateParams]="{count: messageCount}">
</span>
<!-- 复杂结构翻译 -->
<ul>
<li *ngFor="let feature of 'HOME.FEATURES_LIST' | translate">
{{feature}}
</li>
</ul>
<!-- 动态键名 -->
<span>{{ translationKey | translate }}</span>
// translation.service.ts - 翻译服务
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class TranslationService {
constructor(private translate: TranslateService) {}
// 获取当前语言
getCurrentLanguage(): string {
return this.translate.currentLang;
}
// 获取支持的语言列表
getSupportedLanguages(): string[] {
return this.translate.getLangs();
}
// 设置语言
setLanguage(lang: string): Observable<any> {
return this.translate.use(lang);
}
// 获取翻译(同步)
getTranslation(key: string, params?: any): string {
return this.translate.instant(key, params);
}
// 获取翻译(异步)
getTranslationAsync(key: string, params?: any): Observable<string> {
return this.translate.get(key, params);
}
// 获取JSON文件的所有翻译
getTranslationFile(lang: string): Observable<any> {
return this.translate.getTranslation(lang);
}
// 重新加载翻译
reloadTranslations(): void {
this.translate.reloadLang(this.translate.currentLang);
}
// 监听语言变化
onLanguageChange(): Observable<string> {
return this.translate.onLangChange;
}
}
// 在组件中使用
@Component({
selector: 'app-example',
templateUrl: './example.component.html'
})
export class ExampleComponent implements OnInit {
translatedText: string = '';
constructor(
private translate: TranslateService,
private translationService: TranslationService
) {}
ngOnInit(): void {
// 方式1:使用translate服务
this.translate.get('HOME.TITLE').subscribe((translation: string) => {
this.translatedText = translation;
});
// 带参数的翻译
this.translate.get('HEADER.WELCOME', {name: '张三'})
.subscribe((text: string) => {
console.log(text); // "欢迎,张三"
});
// 监听语言变化
this.translate.onLangChange.subscribe((event: LangChangeEvent) => {
console.log('语言已切换:', event.lang);
this.updateTranslations();
});
}
// 获取多个翻译
getMultipleTranslations(): void {
this.translate.get(['BUTTONS.SAVE', 'BUTTONS.CANCEL'])
.subscribe((translations: any) => {
console.log('保存:', translations['BUTTONS.SAVE']);
console.log('取消:', translations['BUTTONS.CANCEL']);
});
}
// 使用服务方法
updateTranslations(): void {
this.translatedText = this.translationService.getTranslation('HOME.TITLE');
}
}
// locale-aware.component.ts - 区域感知格式化
import { Component, Inject, LOCALE_ID } from '@angular/core';
import { formatDate, formatCurrency, formatNumber, formatPercent } from '@angular/common';
@Component({
selector: 'app-locale-aware',
template: `
<div class="locale-demo">
<h3>区域设置: {{currentLocale}}</h3>
<div class="format-row">
<strong>日期:</strong> {{formattedDate}}
</div>
<div class="format-row">
<strong>货币:</strong> {{formattedCurrency}}
</div>
<div class="format-row">
<strong>数字:</strong> {{formattedNumber}}
</div>
<div class="format-row">
<strong>百分比:</strong> {{formattedPercent}}
</div>
</div>
`
})
export class LocaleAwareComponent {
currentLocale: string;
formattedDate: string;
formattedCurrency: string;
formattedNumber: string;
formattedPercent: string;
constructor(@Inject(LOCALE_ID) private locale: string) {
this.currentLocale = locale;
const now = new Date();
const amount = 1234.5678;
// 使用formatXXX函数进行格式化
this.formattedDate = formatDate(now, 'fullDate', this.locale);
this.formattedCurrency = formatCurrency(amount, this.locale, '¥');
this.formattedNumber = formatNumber(amount, this.locale, '1.2-2');
this.formattedPercent = formatPercent(0.1234, this.locale, '1.2-2');
}
}
// 动态切换区域设置
@Component({
selector: 'app-dynamic-locale',
template: `
<div class="dynamic-locale">
<select [(ngModel)]="selectedLocale" (change)="changeLocale()">
<option value="zh-Hans">简体中文</option>
<option value="en-US">English (US)</option>
<option value="es-ES">Español</option>
<option value="ja-JP">日本語</option>
<option value="fr-FR">Français</option>
<option value="de-DE">Deutsch</option>
</select>
<div class="demo-output">
<p>日期: {{currentDate | date:'fullDate'}}</p>
<p>货币: {{price | currency}}</p>
<p>数字: {{numberValue | number}}</p>
<p>百分比: {{percentage | percent}}</p>
</div>
</div>
`
})
export class DynamicLocaleComponent {
selectedLocale = 'zh-Hans';
currentDate = new Date();
price = 1234.56;
numberValue = 1234567.89;
percentage = 0.75;
constructor() {
// 从本地存储或用户设置获取区域
const savedLocale = localStorage.getItem('userLocale');
if (savedLocale) {
this.selectedLocale = savedLocale;
this.changeLocale();
}
}
changeLocale(): void {
// 注意:需要重新加载应用才能更改区域设置
localStorage.setItem('userLocale', this.selectedLocale);
// 在实际应用中,可能需要重新加载页面或使用服务动态更新
console.log('区域设置已更改为:', this.selectedLocale);
// 这里可以触发事件通知其他组件
}
}
// rtl-support.component.ts - RTL语言支持
import { Component, HostBinding, Renderer2 } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-rtl-support',
template: `
<div class="rtl-demo">
<div class="language-selector">
<button (click)="setLanguage('en')">English (LTR)</button>
<button (click)="setLanguage('ar')">العربية (RTL)</button>
<button (click)="setLanguage('he')">עברית (RTL)</button>
</div>
<div class="content">
<h3>{{ 'TITLE' | translate }}</h3>
<p>{{ 'DESCRIPTION' | translate }}</p>
<div class="text-example">
{{ 'SAMPLE_TEXT' | translate }}
</div>
</div>
</div>
`,
styles: [`
.rtl-demo {
padding: 20px;
border: 1px solid #dee2e6;
border-radius: 8px;
margin: 20px 0;
}
.language-selector {
margin-bottom: 20px;
}
.language-selector button {
margin-right: 10px;
padding: 8px 16px;
}
.content {
text-align: inherit; /* 根据方向继承 */
}
.text-example {
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
margin-top: 15px;
}
`]
})
export class RtlSupportComponent {
@HostBinding('attr.dir') direction = 'ltr';
constructor(
private translate: TranslateService,
private renderer: Renderer2
) {
// 监听语言变化
this.translate.onLangChange.subscribe((event: LangChangeEvent) => {
this.updateDirection(event.lang);
});
}
setLanguage(lang: string): void {
this.translate.use(lang);
}
private updateDirection(lang: string): void {
// 判断是否为RTL语言
const rtlLanguages = ['ar', 'he', 'fa', 'ur'];
const isRTL = rtlLanguages.includes(lang);
// 更新方向
this.direction = isRTL ? 'rtl' : 'ltr';
// 更新HTML元素的dir属性
this.renderer.setAttribute(document.documentElement, 'dir', this.direction);
// 更新body类
if (isRTL) {
this.renderer.addClass(document.body, 'rtl');
this.renderer.removeClass(document.body, 'ltr');
} else {
this.renderer.addClass(document.body, 'ltr');
this.renderer.removeClass(document.body, 'rtl');
}
console.log(`语言方向已更新: ${this.direction}`);
}
}
// RTL样式示例
/* rtl-styles.scss */
body.rtl {
text-align: right;
direction: rtl;
.navbar-nav {
padding-right: 0;
}
.float-left {
float: right !important;
}
.float-right {
float: left !important;
}
.text-left {
text-align: right !important;
}
.text-right {
text-align: left !important;
}
.ml-1 { margin-right: 0.25rem !important; margin-left: 0 !important; }
.ml-2 { margin-right: 0.5rem !important; margin-left: 0 !important; }
.ml-3 { margin-right: 1rem !important; margin-left: 0 !important; }
.mr-1 { margin-left: 0.25rem !important; margin-right: 0 !important; }
.mr-2 { margin-left: 0.5rem !important; margin-right: 0 !important; }
.mr-3 { margin-left: 1rem !important; margin-right: 0 !important; }
.pl-1 { padding-right: 0.25rem !important; padding-left: 0 !important; }
.pl-2 { padding-right: 0.5rem !important; padding-left: 0 !important; }
.pl-3 { padding-right: 1rem !important; padding-left: 0 !important; }
.pr-1 { padding-left: 0.25rem !important; padding-right: 0 !important; }
.pr-2 { padding-left: 0.5rem !important; padding-right: 0 !important; }
.pr-3 { padding-left: 1rem !important; padding-right: 0 !important; }
}
// i18n-best-practices.service.ts
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: 'root'
})
export class I18nBestPracticesService {
private supportedLangs = ['zh', 'en', 'es', 'ja', 'fr'];
constructor(private translate: TranslateService) {}
// 1. 语言检测和回退
initializeLanguage(): void {
// 优先级:用户选择 > 浏览器语言 > 默认语言
const userLang = localStorage.getItem('userLang');
const browserLang = this.translate.getBrowserLang();
const defaultLang = 'zh';
let langToUse = userLang || browserLang || defaultLang;
// 确保语言在支持列表中
if (!this.supportedLangs.includes(langToUse)) {
langToUse = defaultLang;
}
this.translate.setDefaultLang(defaultLang);
this.translate.use(langToUse);
}
// 2. 懒加载翻译
async loadLanguage(lang: string): Promise {
if (!this.supportedLangs.includes(lang)) {
throw new Error(`语言 ${lang} 不受支持`);
}
try {
// 动态导入翻译文件
const translations = await import(`../assets/i18n/${lang}.json`);
// 设置翻译
this.translate.setTranslation(lang, translations, true);
this.translate.use(lang);
localStorage.setItem('userLang', lang);
} catch (error) {
console.error(`加载语言 ${lang} 失败:`, error);
throw error;
}
}
// 3. 翻译键名管理
getTranslationKey(module: string, component: string, key: string): string {
// 使用一致的命名约定
return `${module.toUpperCase()}.${component.toUpperCase()}.${key.toUpperCase()}`;
}
// 4. 处理缺失翻译
getSafeTranslation(key: string, params?: any): string {
const translation = this.translate.instant(key, params);
// 如果翻译不存在,返回键名作为后备
if (translation === key) {
console.warn(`缺少翻译: ${key}`);
return this.getFallbackTranslation(key);
}
return translation;
}
private getFallbackTranslation(key: string): string {
// 实现智能回退逻辑
const fallbacks: { [key: string]: string } = {
'COMMON.BUTTONS.SAVE': '保存',
'COMMON.BUTTONS.CANCEL': '取消',
'COMMON.BUTTONS.DELETE': '删除'
};
return fallbacks[key] || key;
}
// 5. 处理文本扩展
getAdaptiveText(key: string, maxLength?: number): string {
const text = this.translate.instant(key);
if (maxLength && text.length > maxLength) {
// 获取缩写版本
const shortKey = `${key}_SHORT`;
const shortText = this.translate.instant(shortKey);
if (shortText !== shortKey) {
return shortText;
}
// 如果缩写不存在,截断文本
return text.substring(0, maxLength - 3) + '...';
}
return text;
}
// 6. 获取当前语言信息
getLanguageInfo(lang: string): {
code: string;
name: string;
nativeName: string;
direction: 'ltr' | 'rtl';
} {
const languages: any = {
'zh': { code: 'zh', name: 'Chinese', nativeName: '中文', direction: 'ltr' },
'en': { code: 'en', name: 'English', nativeName: 'English', direction: 'ltr' },
'es': { code: 'es', name: 'Spanish', nativeName: 'Español', direction: 'ltr' },
'ja': { code: 'ja', name: 'Japanese', nativeName: '日本語', direction: 'ltr' },
'fr': { code: 'fr', name: 'French', nativeName: 'Français', direction: 'ltr' },
'ar': { code: 'ar', name: 'Arabic', nativeName: 'العربية', direction: 'rtl' },
'he': { code: 'he', name: 'Hebrew', nativeName: 'עברית', direction: 'rtl' }
};
return languages[lang] || languages['zh'];
}
}
<!-- index.html - 多语言SEO优化 -->
<!doctype html>
<html lang="zh-Hans">
<head>
<meta charset="utf-8">
<title>我的应用 - 多语言演示</title>
<!-- 多语言SEO -->
<link rel="alternate" hreflang="zh-Hans" href="https://example.com/zh/">
<link rel="alternate" hreflang="en" href="https://example.com/en/">
<link rel="alternate" hreflang="es" href="https://example.com/es/">
<link rel="alternate" hreflang="ja" href="https://example.com/ja/">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/">
<link rel="alternate" hreflang="x-default" href="https://example.com/">
<meta name="description" content="多语言演示应用">
<meta property="og:locale" content="zh_CN">
<meta property="og:locale:alternate" content="en_US">
<meta property="og:locale:alternate" content="es_ES">
<!-- 语言切换元标签 -->
<meta http-equiv="content-language" content="zh-CN">
</head>
<body>
<app-root></app-root>
</body>
</html>