Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(module:theme:i18n): add i18n pipe #238

Merged
merged 3 commits into from
Oct 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ switchLanguage() {
}
```

### ALAIN_I18N_TOKEN
## ALAIN_I18N_TOKEN

`@delon/*` 类库有许多带有 _i18n_ 字样的数据接口属性(例如:`page-header`、`st` 列描述、`Menu` 菜单数据等等),当你希望这些组件的数据接口能动态根据 Key 值按当前语言自动切换时,你还需要对 `ALAIN_I18N_TOKEN` 定义一个自实现服务接口(例如:[I18NService](https://github.com/ng-alain/ng-alain/blob/master/src/app/core/i18n/i18n.service.ts)),并在根模块下注册。

Expand All @@ -104,6 +104,12 @@ import { I18NService } from '@core/i18n/i18n.service';
export class AppModule {}
```

### i18n管道

为了不受第三方各自管道的命名方式,脚手架包含一个 `i18n` 的管道,它相当于直接调用 `ALAIN_I18N_TOKEN` 的 `fanyi` 方法。

他和 `@ngx-translate` 的 `| translate` 有所不同,它会监听语言的变化并自动更新。而 `| i18n` 不会监听语言变更通知所以会有更好的性能,当你明确在切换语言后会重新渲染 Angular 项目时 `| i18n` 会更适合。

## 如何添加

创建脚手架[命令行](/cli/add) `ng add ng-alain@next` 时允许指定 `--i18n` 表示是否包含国际化示例代码(采用 `@ngx-translate/core` 方式)。
Expand All @@ -114,7 +120,7 @@ export class AppModule {}

- `src/app/core/i18n` 目录
- `app.module.ts` 对 `TranslateModule` 相关声明
- 替换掉默认布局可能出现的 I18n 的 Pipe 使用 `| translate`
- 替换掉默认布局可能出现的 I18n 的 Pipe 使用 `| translate` 或 `| i18n`
- 移除 `@ngx-translate/core`、`@ngx-translate/http-loader` 包体

## 默认语言
Expand Down
11 changes: 11 additions & 0 deletions packages/theme/src/services/i18n/i18n.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PipeTransform, Pipe, Inject } from '@angular/core';
import { ALAIN_I18N_TOKEN, AlainI18NService } from './i18n';

@Pipe({ name: 'i18n' })
export class I18nPipe implements PipeTransform {
constructor(@Inject(ALAIN_I18N_TOKEN) private i18n: AlainI18NService) {}

transform(key: string, interpolateParams?: Object, isSafe?: boolean): string {
return this.i18n.fanyi(key, interpolateParams, isSafe);
}
}
96 changes: 95 additions & 1 deletion packages/theme/src/services/i18n/i18n.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { en_US } from 'ng-zorro-antd';
import { AlainI18NServiceFake } from './i18n';
import {
AlainI18NServiceFake,
AlainI18NService,
ALAIN_I18N_TOKEN,
} from './i18n';
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AlainThemeModule } from '../../theme.module';
import { By } from '@angular/platform-browser';

describe('theme: i18n', () => {
const i18n = new AlainI18NServiceFake();
Expand All @@ -16,4 +24,90 @@ describe('theme: i18n', () => {
it('#fanyi', () => {
expect(i18n.fanyi('index')).toBe('index');
});

describe('[i18n pipe]', () => {
let fixture: ComponentFixture<TestComponent>;
let srv: AlainI18NService;

class MockI18NService extends AlainI18NServiceFake {
data: any = {};
use(lang: string) {
this.data = {
simple: 'a',
param: 'a-{{value}}',
html: '<i>asdf</i>',
};
}
fanyi(key: string, data?: Object, isSafe?: boolean) {
let res = this.data[key] || '';
if (data) {
Object.keys(data).forEach(
key =>
(res = res.replace(new RegExp(`{{${key}}}`, 'g'), data[key])),
);
}
return res;
}
}

function genModule() {
TestBed.configureTestingModule({
imports: [AlainThemeModule.forRoot()],
declarations: [TestComponent],
providers: [
{
provide: ALAIN_I18N_TOKEN,
useClass: MockI18NService,
multi: false,
},
],
});
fixture = TestBed.createComponent(TestComponent);
srv = fixture.debugElement.injector.get(ALAIN_I18N_TOKEN);
srv.use('en');
fixture.detectChanges();
}

function check(result: string, id = 'simple') {
const el = fixture.debugElement.query(By.css('#' + id))
.nativeElement as HTMLElement;

expect(el.textContent.trim()).toBe(result);
}

it('should working', () => {
genModule();
check('a');
});

it('should be param', () => {
genModule();
fixture.componentInstance.key = 'param';
fixture.componentInstance.params = { value: '1' };
fixture.detectChanges();
check('a-1', 'param');
});

it('should be safeHtml', () => {
genModule();
fixture.componentInstance.key = 'html';
fixture.componentInstance.params = { value: '1' };
fixture.componentInstance.isSafe = true;
fixture.detectChanges();
check('asdf', 'html');
});
});
});

@Component({
template: `
<div id="simple">{{ key | i18n }}</div>
<div id="param">{{ key | i18n : params }}</div>
<div id="html" [innerHTML]="key | i18n : params : isSafe"></div>
`,
})
class TestComponent {
key = 'simple';
params = {};
isSafe = true;
}
22 changes: 19 additions & 3 deletions packages/theme/src/services/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,28 @@ import { filter } from 'rxjs/operators';
export interface AlainI18NService {
[key: string]: any;

use(lang: string): void;

/**
* 变更语言
* @param lang 语言代码
* @param emit 是否触发 `change`,默认:true
*/
use(lang: string, emit?: boolean): void;

/**
* 返回当前语言列表
*/
getLangs(): any[];

fanyi(key: string): any;
/**
* 翻译
* - `interpolateParams` 模板所需要的参数对象
* - `isSafe` 是否返回安全字符,自动调用 `bypassSecurityTrustHtml`
*/
fanyi(key: string, interpolateParams?: Object, isSafe?: boolean): string;

/**
* 调用 `use` 触发变更通知
*/
readonly change: Observable<string>;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/theme/src/theme.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import { DatePipe } from './pipes/date/date.pipe';
import { CNCurrencyPipe } from './pipes/currency/cn-currency.pipe';
import { KeysPipe } from './pipes/keys/keys.pipe';
import { YNPipe } from './pipes/yn/yn.pipe';
import { I18nPipe } from './services/i18n/i18n.pipe';
import { HTMLPipe } from './pipes/html/html.pipe';
const PIPES = [DatePipe, CNCurrencyPipe, KeysPipe, YNPipe, HTMLPipe];
const PIPES = [DatePipe, CNCurrencyPipe, KeysPipe, YNPipe, I18nPipe, HTMLPipe];

// #endregion

Expand Down
10 changes: 8 additions & 2 deletions src/app/core/i18n/service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Subject } from 'rxjs';
import { DomSanitizer } from '@angular/platform-browser';

import { en_US, zh_CN, NzI18nService } from 'ng-zorro-antd';
import {
Expand Down Expand Up @@ -28,6 +29,7 @@ export class I18NService implements AlainI18NService {
private zorroI18n: NzI18nService,
private delonI18n: DelonLocaleService,
private translate: TranslateService,
private dom: DomSanitizer,
) {
this.translate.setTranslation('en-US', ENUS);
this.translate.setTranslation('zh-CN', ZHCN);
Expand Down Expand Up @@ -92,8 +94,12 @@ export class I18NService implements AlainI18NService {
return ['zh-CN', 'en-US'];
}

fanyi(key: string) {
return this.translate.instant(key);
fanyi(key: string, interpolateParams?: Object, isSafe?: boolean) {
const res = this.translate.instant(key, interpolateParams);
if (isSafe === true) {
return this.dom.bypassSecurityTrustHtml(res);
}
return res;
}

get(i: any) {
Expand Down