带有 ngx-translate 的自定义 Angular 库
Custom Angular library with ngx-translate
我创建了自己的私人 angular 库,其中包含多个组件,让我的生活更轻松。
我按照这个 tutorial 创建了这个库。
一切都很顺利,我什至将它添加到另一个项目中进行测试。
现在是我必须支持多种语言的部分,因为我住在比利时。我过去使用过 ngx-translate 包没有任何问题,所以我认为这很容易。
我在我的项目中添加了一个包含翻译 json 的 assets/i18n 文件夹。
我发现 angular cli 不包含构建中的资产,因此您必须在制作包之前手动或使用 gulp 添加它们到 dist 文件夹。
这样做之后,我注意到标签没有被翻译,除非我将翻译包含在主应用程序的 json 文件中。
我库中的每个组件都有自己的模块,所以我想我只需要在这些模块中添加翻译模块。所以我做了以下。
export function httpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
@NgModule({
imports: [
CommonModule,
FormsModule,
DirectivesModule,
ButtonModule,
IconModule,
PillModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: (httpLoaderFactory),
deps: [HttpClient]
},
isolate: true
})
],
declarations: [
FilterComponent
],
exports: [
FilterComponent
]
})
这让事情变得更糟,不仅标签仍然没有被翻译,我的主要应用程序甚至没有使用自己的翻译。在库模块发生这些变化之前,主应用程序的翻译没有问题...
是的,你猜对了,我被卡住了...
我似乎找不到正确的解决方案。
你可以做这样的东西。
<div (click)="switchLanguage('en')"></div>
switchLanguage(language: string) {
this.selectedLanguage = language
}
在另一个组件中写 HTML 类似这样的东西。
<button><i class="fa fa-history"></i>{{'general.#time'|translate}}</button>
这是en.json
"general": {
"#time": "Time"
}
我怀疑您的构建步骤正在用库文件覆盖主应用程序的 en.json
等。因此缺少主应用程序的翻译。
除了隔离选项(这将需要向服务器发出更多请求以加载翻译)之外,您可以考虑的一件事是更改构建步骤,将库中的文件与主应用程序的翻译文件合并。
我建议以某种方式命名库翻译键以避免任何可能的冲突。
然后在库中你可以直接使用TranslateModule.forChild()
或者,如果您想保持隔离,请尝试将库的翻译文件放在 i18n 的子目录中,并根据需要更改 httpLoaderFactory
因为我无处可去,所以我尝试了此 post 中描述的另一种方法。
所以我将 json 文件转换为返回 json 对象的 ts 文件。
然后,我创建了自己的 translateService,它将翻译添加到现有翻译(由主应用程序的 json 文件添加的翻译)之上,如 post.
中所述
这行得通,但否决了以前的翻译,甚至加载得太晚了。
这导致应用程序只显示翻译键而不是翻译。因此,我没有像 post 那样初始化翻译,而是先使用订阅等待主要翻译。
//Library
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { en } from "../localization/en";
import { nl } from "../localization/nl";
@Injectable()
export class TranslateUiService {
private availableLanguages = { en, nl };
constructor(private translateService: TranslateService) {
}
public init(language: string = null): any {
if (language) {
//initialize one specific language
this.translateService.setTranslation(language, this.availableLanguages[language], true);
} else {
//initialize all
Object.keys(this.availableLanguages).forEach((language) => {
this.translateService.setTranslation(language, this.availableLanguages[language], true);
});
}
}
}
//App
constructor(private translateUiService: TranslateUiService, private translateService: TranslateService) {
this.translateService.setDefaultLang('en');
this.translateService.use('en').subscribe(() => {
this.translateUiService.init('en');
});
}
我的做法和 Beejee 差不多,但是我扩展了翻译服务。
在 TranslateService 的扩展中,我将库的翻译添加到全局应用程序翻译的子级别 (ui.[language]) 下,因为我们使用与根应用程序相同的实例,并且我们不想覆盖翻译根应用程序。
然后我在组件级别提供了扩展而不是普通的 TranslateService,因此它甚至用于该组件内的翻译指令并且它是隔离的,这意味着我们不会通过覆盖 currentLang 和 defautlLang 的 getter 来破坏根应用程序的翻译。
ui-translate.service.ts:
const availableLanguages: any = {
'de' : { ... YOUR DE TRANSLATIONS ... },
'en' : { ... YOUR EN TRANSLATIONS ... }
...
};
const langPrefix = 'ui';
@Injectable({
providedIn: 'root'
})
export class UiTranslateService extends TranslateService implements TranslateService {
constructor(public store: TranslateStore,
public currentLoader: TranslateLoader,
public compiler: TranslateCompiler,
public parser: TranslateParser,
public missingTranslationHandler: MissingTranslationHandler,
@Inject(USE_DEFAULT_LANG) useDefaultLang: boolean = true,
@Inject(USE_STORE) isolate: boolean = false) {
super(store, currentLoader, compiler, parser, missingTranslationHandler, useDefaultLang, isolate);
this.onTranslationChange.subscribe((_res: TranslationChangeEvent) => {
// ensure after translation change we (re-)add our translations
if (_res.lang.indexOf(langPrefix) !== 0) {
this.applyUiTranslations();
}
});
this.applyUiTranslations();
}
private applyUiTranslations() {
for (var lang in availableLanguages) {
if (availableLanguages.hasOwnProperty(lang)) {
this.setTranslation(langPrefix + '.' + lang, availableLanguages[lang], true);
}
}
}
/**
* The default lang to fallback when translations are missing on the current lang
*/
get defaultLang(): string {
return langPrefix + '.' + this.store.defaultLang;
}
/**
* The lang currently used
*/
get currentLang(): string {
return this.store.currentLang === undefined ? undefined : langPrefix + '.' + this.store.currentLang;
}
public getParsedResult(translations: any, key: any, interpolateParams?: Object): any {
// apply our translations here
var lang = (this.currentLang || this.defaultLang).split('.')[1];
translations = lang == 'de' ? de : en;
return super.getParsedResult(translations, key, interpolateParams);
}
public get(key: string | Array<string>, interpolateParams?: Object): Observable<string | any> {
return super.get(key, interpolateParams);
}
}
my.component.ts:
@Component({
selector: 'xxx',
template: 'xxx',
providers: [
{ provide: TranslateService, useClass: UiTranslateService }
]
})
export class MyComponent implements OnInit { }
my.module.ts:
@NgModule({
imports: [
CommonModule,
TranslateModule
]
})
export class ComponentsModule {}
我创建了自己的私人 angular 库,其中包含多个组件,让我的生活更轻松。 我按照这个 tutorial 创建了这个库。 一切都很顺利,我什至将它添加到另一个项目中进行测试。
现在是我必须支持多种语言的部分,因为我住在比利时。我过去使用过 ngx-translate 包没有任何问题,所以我认为这很容易。
我在我的项目中添加了一个包含翻译 json 的 assets/i18n 文件夹。
我发现 angular cli 不包含构建中的资产,因此您必须在制作包之前手动或使用 gulp 添加它们到 dist 文件夹。 这样做之后,我注意到标签没有被翻译,除非我将翻译包含在主应用程序的 json 文件中。
我库中的每个组件都有自己的模块,所以我想我只需要在这些模块中添加翻译模块。所以我做了以下。
export function httpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
@NgModule({
imports: [
CommonModule,
FormsModule,
DirectivesModule,
ButtonModule,
IconModule,
PillModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: (httpLoaderFactory),
deps: [HttpClient]
},
isolate: true
})
],
declarations: [
FilterComponent
],
exports: [
FilterComponent
]
})
这让事情变得更糟,不仅标签仍然没有被翻译,我的主要应用程序甚至没有使用自己的翻译。在库模块发生这些变化之前,主应用程序的翻译没有问题...
是的,你猜对了,我被卡住了... 我似乎找不到正确的解决方案。
你可以做这样的东西。
<div (click)="switchLanguage('en')"></div>
switchLanguage(language: string) {
this.selectedLanguage = language
}
在另一个组件中写 HTML 类似这样的东西。
<button><i class="fa fa-history"></i>{{'general.#time'|translate}}</button>
这是en.json
"general": {
"#time": "Time"
}
我怀疑您的构建步骤正在用库文件覆盖主应用程序的 en.json
等。因此缺少主应用程序的翻译。
除了隔离选项(这将需要向服务器发出更多请求以加载翻译)之外,您可以考虑的一件事是更改构建步骤,将库中的文件与主应用程序的翻译文件合并。
我建议以某种方式命名库翻译键以避免任何可能的冲突。
然后在库中你可以直接使用TranslateModule.forChild()
或者,如果您想保持隔离,请尝试将库的翻译文件放在 i18n 的子目录中,并根据需要更改 httpLoaderFactory
因为我无处可去,所以我尝试了此 post 中描述的另一种方法。 所以我将 json 文件转换为返回 json 对象的 ts 文件。 然后,我创建了自己的 translateService,它将翻译添加到现有翻译(由主应用程序的 json 文件添加的翻译)之上,如 post.
中所述这行得通,但否决了以前的翻译,甚至加载得太晚了。 这导致应用程序只显示翻译键而不是翻译。因此,我没有像 post 那样初始化翻译,而是先使用订阅等待主要翻译。
//Library
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { en } from "../localization/en";
import { nl } from "../localization/nl";
@Injectable()
export class TranslateUiService {
private availableLanguages = { en, nl };
constructor(private translateService: TranslateService) {
}
public init(language: string = null): any {
if (language) {
//initialize one specific language
this.translateService.setTranslation(language, this.availableLanguages[language], true);
} else {
//initialize all
Object.keys(this.availableLanguages).forEach((language) => {
this.translateService.setTranslation(language, this.availableLanguages[language], true);
});
}
}
}
//App
constructor(private translateUiService: TranslateUiService, private translateService: TranslateService) {
this.translateService.setDefaultLang('en');
this.translateService.use('en').subscribe(() => {
this.translateUiService.init('en');
});
}
我的做法和 Beejee 差不多,但是我扩展了翻译服务。 在 TranslateService 的扩展中,我将库的翻译添加到全局应用程序翻译的子级别 (ui.[language]) 下,因为我们使用与根应用程序相同的实例,并且我们不想覆盖翻译根应用程序。 然后我在组件级别提供了扩展而不是普通的 TranslateService,因此它甚至用于该组件内的翻译指令并且它是隔离的,这意味着我们不会通过覆盖 currentLang 和 defautlLang 的 getter 来破坏根应用程序的翻译。
ui-translate.service.ts:
const availableLanguages: any = {
'de' : { ... YOUR DE TRANSLATIONS ... },
'en' : { ... YOUR EN TRANSLATIONS ... }
...
};
const langPrefix = 'ui';
@Injectable({
providedIn: 'root'
})
export class UiTranslateService extends TranslateService implements TranslateService {
constructor(public store: TranslateStore,
public currentLoader: TranslateLoader,
public compiler: TranslateCompiler,
public parser: TranslateParser,
public missingTranslationHandler: MissingTranslationHandler,
@Inject(USE_DEFAULT_LANG) useDefaultLang: boolean = true,
@Inject(USE_STORE) isolate: boolean = false) {
super(store, currentLoader, compiler, parser, missingTranslationHandler, useDefaultLang, isolate);
this.onTranslationChange.subscribe((_res: TranslationChangeEvent) => {
// ensure after translation change we (re-)add our translations
if (_res.lang.indexOf(langPrefix) !== 0) {
this.applyUiTranslations();
}
});
this.applyUiTranslations();
}
private applyUiTranslations() {
for (var lang in availableLanguages) {
if (availableLanguages.hasOwnProperty(lang)) {
this.setTranslation(langPrefix + '.' + lang, availableLanguages[lang], true);
}
}
}
/**
* The default lang to fallback when translations are missing on the current lang
*/
get defaultLang(): string {
return langPrefix + '.' + this.store.defaultLang;
}
/**
* The lang currently used
*/
get currentLang(): string {
return this.store.currentLang === undefined ? undefined : langPrefix + '.' + this.store.currentLang;
}
public getParsedResult(translations: any, key: any, interpolateParams?: Object): any {
// apply our translations here
var lang = (this.currentLang || this.defaultLang).split('.')[1];
translations = lang == 'de' ? de : en;
return super.getParsedResult(translations, key, interpolateParams);
}
public get(key: string | Array<string>, interpolateParams?: Object): Observable<string | any> {
return super.get(key, interpolateParams);
}
}
my.component.ts:
@Component({
selector: 'xxx',
template: 'xxx',
providers: [
{ provide: TranslateService, useClass: UiTranslateService }
]
})
export class MyComponent implements OnInit { }
my.module.ts:
@NgModule({
imports: [
CommonModule,
TranslateModule
]
})
export class ComponentsModule {}