Differenzansicht 16c-i18n
im Vergleich zu 16-guards

← Zurück zur Übersicht | Demo | Quelltext auf GitHub
angular.buildi18n.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3
+ "version": 1,
4
+ "newProjectRoot": "projects",
5
+ "projects": {
6
+ "book-monkey": {
7
+ "projectType": "application",
8
+ "schematics": {},
9
+ "root": "",
10
+ "sourceRoot": "src",
11
+ "prefix": "bm",
12
+ "architect": {
13
+ "build": {
14
+ "builder": "@angular-devkit/build-angular:browser",
15
+ "options": {
16
+ "outputPath": "dist/book-monkey",
17
+ "index": "src/index.html",
18
+ "main": "src/main.ts",
19
+ "polyfills": [
20
+ "zone.js"
21
+ ],
22
+ "tsConfig": "tsconfig.app.json",
23
+ "assets": [
24
+ "src/favicon.ico",
25
+ "src/assets"
26
+ ],
27
+ "styles": [
28
+ "src/styles.css"
29
+ ],
30
+ "scripts": [],
31
+ "localize": true
32
+ },
33
+ "configurations": {
34
+ "production": {
35
+ "budgets": [
36
+ {
37
+ "type": "initial",
38
+ "maximumWarning": "500kb",
39
+ "maximumError": "1mb"
40
+ },
41
+ {
42
+ "type": "anyComponentStyle",
43
+ "maximumWarning": "2kb",
44
+ "maximumError": "4kb"
45
+ }
46
+ ],
47
+ "outputHashing": "all"
48
+ },
49
+ "development": {
50
+ "buildOptimizer": false,
51
+ "optimization": false,
52
+ "vendorChunk": true,
53
+ "extractLicenses": false,
54
+ "sourceMap": true,
55
+ "namedChunks": true,
56
+ "localize": ["en-US"]
57
+ },
58
+ "locale-de": {
59
+ "localize": ["de"]
60
+ }
61
+ },
62
+ "defaultConfiguration": "production"
63
+ },
64
+ "serve": {
65
+ "builder": "@angular-devkit/build-angular:dev-server",
66
+ "configurations": {
67
+ "production": {
68
+ "browserTarget": "book-monkey:build:production"
69
+ },
70
+ "development": {
71
+ "browserTarget": "book-monkey:build:development"
72
+ },
73
+ "development-de": {
74
+ "browserTarget": "book-monkey:build:development,locale-de"
75
+ }
76
+ },
77
+ "defaultConfiguration": "development"
78
+ },
79
+ "extract-i18n": {
80
+ "builder": "@angular-devkit/build-angular:extract-i18n",
81
+ "options": {
82
+ "browserTarget": "book-monkey:build"
83
+ }
84
+ },
85
+ "test": {
86
+ "builder": "@angular-devkit/build-angular:karma",
87
+ "options": {
88
+ "polyfills": [
89
+ "zone.js",
90
+ "zone.js/testing"
91
+ ],
92
+ "tsConfig": "tsconfig.spec.json",
93
+ "assets": [
94
+ "src/favicon.ico",
95
+ "src/assets"
96
+ ],
97
+ "styles": [
98
+ "src/styles.css"
99
+ ],
100
+ "scripts": []
101
+ }
102
+ },
103
+ "lint": {
104
+ "builder": "@angular-eslint/builder:lint",
105
+ "options": {
106
+ "lintFilePatterns": [
107
+ "src/**/*.ts",
108
+ "src/**/*.html"
109
+ ]
110
+ }
111
+ }
112
+ },
113
+ "i18n": {
114
+ "sourceLocale": "en-US",
115
+ "locales": {
116
+ "de": {
117
+ "translation": "messages.de.xlf"
118
+ }
119
+ }
120
+ }
121
+ }
122
+ },
123
+ "cli": {
124
+ "schematicCollections": [
125
+ "@angular-eslint/schematics"
126
+ ]
127
+ }
128
+ }
package.json CHANGED
@@ -16,6 +16,7 @@
16
  "@angular/compiler": "^17.3.0",
17
  "@angular/core": "^17.3.0",
18
  "@angular/forms": "^17.3.0",
 
19
  "@angular/platform-browser": "^17.3.0",
20
  "@angular/platform-browser-dynamic": "^17.3.0",
21
  "@angular/router": "^17.3.0",
16
  "@angular/compiler": "^17.3.0",
17
  "@angular/core": "^17.3.0",
18
  "@angular/forms": "^17.3.0",
19
+ "@angular/localize": "~17.3.7",
20
  "@angular/platform-browser": "^17.3.0",
21
  "@angular/platform-browser-dynamic": "^17.3.0",
22
  "@angular/router": "^17.3.0",
src/app/admin/book-create/book-create.component.html CHANGED
@@ -1,3 +1,3 @@
1
- <h1>Create Book</h1>
2
 
3
  <bm-book-form (submitBook)="create($event)"></bm-book-form>
1
+ <h1 i18n="title create book|Title for the create book page@@BookCreateComponentTitle">Create Book</h1>
2
 
3
  <bm-book-form (submitBook)="create($event)"></bm-book-form>
src/app/admin/book-create/book-create.component.spec.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { BookCreateComponent } from './book-create.component';
4
+
5
+ describe('BookCreateComponent', () => {
6
+ let component: BookCreateComponent;
7
+ let fixture: ComponentFixture<BookCreateComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ declarations: [ BookCreateComponent ]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(BookCreateComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
src/app/admin/book-edit/book-edit.component.html CHANGED
@@ -1,4 +1,4 @@
1
- <h1>Edit Book</h1>
2
 
3
  <bm-book-form
4
  *ngIf="book$ | async as book"
1
+ <h1 i18n="title edit book|Title for the edit book page@@BookEditComponentTitle">Edit Book</h1>
2
 
3
  <bm-book-form
4
  *ngIf="book$ | async as book"
src/app/admin/book-edit/book-edit.component.spec.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { BookEditComponent } from './book-edit.component';
4
+
5
+ describe('BookEditComponent', () => {
6
+ let component: BookEditComponent;
7
+ let fixture: ComponentFixture<BookEditComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ declarations: [ BookEditComponent ]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(BookEditComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
src/app/admin/book-form/book-form.component.html CHANGED
@@ -1,28 +1,25 @@
1
  <form [formGroup]="form" (ngSubmit)="submitForm()">
2
- <label for="title">Title</label>
3
  <input id="title" formControlName="title">
4
  <bm-form-errors
5
  controlName="title"
6
- [messages]="{ required: 'Title is required' }">
7
  </bm-form-errors>
8
 
9
- <label for="subtitle">Subtitle</label>
10
  <input id="subtitle" formControlName="subtitle">
11
 
12
- <label for="isbn">ISBN</label>
13
  <input id="isbn" formControlName="isbn">
14
  <bm-form-errors
15
  controlName="isbn"
16
- [messages]="{
17
- required: 'ISBN is required',
18
- isbnformat: 'ISBN must have 10 or 13 chars',
19
- isbnexists: 'ISBN already exists'
20
- }">
21
  </bm-form-errors>
22
 
23
- <label>Authors</label>
24
  <button type="button" class="add"
25
- (click)="addAuthorControl()">
 
26
  + Author
27
  </button>
28
  <fieldset formArrayName="authors">
@@ -33,19 +30,19 @@
33
  </fieldset>
34
  <bm-form-errors
35
  controlName="authors"
36
- [messages]="{ atleastonevalue: 'At least one author required' }">
37
  </bm-form-errors>
38
 
39
- <label for="description">Description</label>
40
  <textarea id="description" formControlName="description"></textarea>
41
 
42
- <label for="published">Published</label>
43
  <input type="date" useValueAsLocalIso id="published" formControlName="published">
44
 
45
- <label for="thumbnailUrl">Thumbnail URL</label>
46
  <input type="url" id="thumbnailUrl" formControlName="thumbnailUrl">
47
 
48
- <button type="submit" [disabled]="form.invalid">
49
  Save
50
  </button>
51
  </form>
1
  <form [formGroup]="form" (ngSubmit)="submitForm()">
2
+ <label for="title" i18n="label title|Label for the title input@@BookFormComponentLabelTitle">Title</label>
3
  <input id="title" formControlName="title">
4
  <bm-form-errors
5
  controlName="title"
6
+ [messages]="errorsTranslated('title')">
7
  </bm-form-errors>
8
 
9
+ <label for="subtitle" i18n="label subtitle|Label for the subtitle input@@BookFormComponentLabelSubtitle">Subtitle</label>
10
  <input id="subtitle" formControlName="subtitle">
11
 
12
+ <label for="isbn" i18n="label isbn|Label for the ISBN input@@BookFormComponentLabelIsbn">ISBN</label>
13
  <input id="isbn" formControlName="isbn">
14
  <bm-form-errors
15
  controlName="isbn"
16
+ [messages]="errorsTranslated('isbn')">
 
 
 
 
17
  </bm-form-errors>
18
 
19
+ <label i18n="label authors|Label for the authors inputs@@BookFormComponentLabelAuthors">Authors</label>
20
  <button type="button" class="add"
21
+ (click)="addAuthorControl()"
22
+ i18n="button add author|Text for the button to add an author input@@BookFormComponentAddAuthor">
23
  + Author
24
  </button>
25
  <fieldset formArrayName="authors">
30
  </fieldset>
31
  <bm-form-errors
32
  controlName="authors"
33
+ [messages]="errorsTranslated('authors')">
34
  </bm-form-errors>
35
 
36
+ <label for="description" i18n="label description|Label for the description text@@BookFormComponentLabelDescription">Description</label>
37
  <textarea id="description" formControlName="description"></textarea>
38
 
39
+ <label for="published" i18n="label published|Label for the published input@@BookFormComponentLabelPublished">Published</label>
40
  <input type="date" useValueAsLocalIso id="published" formControlName="published">
41
 
42
+ <label for="thumbnailUrl" i18n="label thumbnail|Label for the thumbnail input@@BookFormComponentLabelThumbnail">Thumbnail URL</label>
43
  <input type="url" id="thumbnailUrl" formControlName="thumbnailUrl">
44
 
45
+ <button type="submit" [disabled]="form.invalid" i18n="button save|Text for save button@@BookFormComponentSave">
46
  Save
47
  </button>
48
  </form>
src/app/admin/book-form/book-form.component.spec.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { BookFormComponent } from './book-form.component';
4
+
5
+ describe('BookFormComponent', () => {
6
+ let component: BookFormComponent;
7
+ let fixture: ComponentFixture<BookFormComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ declarations: [ BookFormComponent ]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(BookFormComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
src/app/admin/book-form/book-form.component.ts CHANGED
@@ -88,4 +88,21 @@ export class BookFormComponent implements OnChanges {
88
 
89
  this.submitBook.emit(newBook);
90
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
88
 
89
  this.submitBook.emit(newBook);
90
  }
91
+
92
+ errorsTranslated(controlName: string) {
93
+ const errorTexts: { [controlName: string]: { [errorCode: string]: string }} = {
94
+ title: {
95
+ required: $localize`:title required error@@BookFormComponentErrorTitleRequired:Title is required`
96
+ },
97
+ isbn: {
98
+ required: $localize`:isbn required error@@BookFormComponentErrorIsbnRequired:ISBN is required`,
99
+ isbnformat: $localize`:isbn format error@@BookFormComponentErrorIsbnFormat:ISBN must have 10 or 13 characters`,
100
+ isbnexists: $localize`:isbn exists error@@BookFormComponentErrorIsbnExists:ISBN already exists`,
101
+ },
102
+ authors: {
103
+ atleastonevalue: $localize`:at least one author error@@BookFormComponentErrorAtLeastOneAuthor:At least one author required`
104
+ }
105
+ }
106
+ return errorTexts[controlName] || {};
107
+ }
108
  }
src/app/admin/form-errors/form-errors.component.spec.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { FormErrorsComponent } from './form-errors.component';
4
+
5
+ describe('FormErrorsComponent', () => {
6
+ let component: FormErrorsComponent;
7
+ let fixture: ComponentFixture<FormErrorsComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ declarations: [ FormErrorsComponent ]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(FormErrorsComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
src/app/admin/shared/async-validators.service.spec.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TestBed } from '@angular/core/testing';
2
+
3
+ import { AsyncValidatorsService } from './async-validators.service';
4
+
5
+ describe('AsyncValidatorsService', () => {
6
+ let service: AsyncValidatorsService;
7
+
8
+ beforeEach(() => {
9
+ TestBed.configureTestingModule({});
10
+ service = TestBed.inject(AsyncValidatorsService);
11
+ });
12
+
13
+ it('should be created', () => {
14
+ expect(service).toBeTruthy();
15
+ });
16
+ });
src/app/app.component.html CHANGED
@@ -1,14 +1,18 @@
1
  <nav>
2
- <a routerLink="/home" routerLinkActive="active" ariaCurrentWhenActive="page">Home</a>
3
- <a routerLink="/books" routerLinkActive="active" ariaCurrentWhenActive="page">Books</a>
4
- <a routerLink="/admin" routerLinkActive="active" ariaCurrentWhenActive="page">Administration</a>
5
  <div class="actions">
6
  <button class="green"
7
  (click)="auth.login()"
8
- *ngIf="!auth.isAuthenticated">Login</button>
 
9
  <button class="red"
10
  (click)="auth.logout()"
11
- *ngIf="auth.isAuthenticated">Logout</button>
 
 
 
12
  </div>
13
  </nav>
14
 
1
  <nav>
2
+ <a routerLink="/home" routerLinkActive="active" ariaCurrentWhenActive="page" i18n="nav home@@AppComponentHome">Home</a>
3
+ <a routerLink="/books" routerLinkActive="active" ariaCurrentWhenActive="page" i18n="nav books@@AppComponentBooks">Books</a>
4
+ <a routerLink="/admin" routerLinkActive="active" ariaCurrentWhenActive="page" i18n="nav admin@@AppComponentAdmin">Administration</a>
5
  <div class="actions">
6
  <button class="green"
7
  (click)="auth.login()"
8
+ *ngIf="!auth.isAuthenticated"
9
+ i18n="login@@AppComponentLogin">Login</button>
10
  <button class="red"
11
  (click)="auth.logout()"
12
+ *ngIf="auth.isAuthenticated"
13
+ i18n="logout@@AppComponentLogout">Logout</button>
14
+ <button (click)="changeLocale('de')">DE</button>
15
+ <button (click)="changeLocale('en')">EN</button>
16
  </div>
17
  </nav>
18
 
src/app/app.component.ts CHANGED
@@ -9,4 +9,9 @@ import { AuthService } from './shared/auth.service';
9
  })
10
  export class AppComponent {
11
  constructor(public auth: AuthService) {}
 
 
 
 
 
12
  }
9
  })
10
  export class AppComponent {
11
  constructor(public auth: AuthService) {}
12
+
13
+ changeLocale(targetLang: string) {
14
+ localStorage.setItem('locale', targetLang);
15
+ location.reload();
16
+ }
17
  }
src/app/books/book-details/book-details.component.html CHANGED
@@ -3,32 +3,34 @@
3
  <p role="doc-subtitle" *ngIf="book.subtitle">{{ book.subtitle }}</p>
4
  <div class="header">
5
  <div>
6
- <h2>Authors</h2>
7
  <ul>
8
  <li *ngFor="let author of book.authors">{{ author }}</li>
9
  </ul>
10
  </div>
11
  <div>
12
- <h2>ISBN</h2>
13
  {{ book.isbn | isbn }}
14
  </div>
15
  <div *ngIf="book.published">
16
- <h2>Published</h2>
17
  {{ book.published | date:'longDate' }}
18
  </div>
19
  </div>
20
- <h2>Description</h2>
21
  <p>{{ book.description }}</p>
22
  <img *ngIf="book.thumbnailUrl"
23
  [src]="book.thumbnailUrl"
24
- alt="Cover">
25
- <a class="button arrow-left" routerLink="..">Back to list</a>
 
26
  <ng-container *bmLoggedinOnly>
27
- <button class="red" bmConfirm="Remove book?" (confirm)="removeBook(book.isbn)">
28
  Remove book
29
  </button>
30
  <a class="button"
31
- [routerLink]="['/admin/edit', book.isbn]">
 
32
  Edit book
33
  </a>
34
  </ng-container>
3
  <p role="doc-subtitle" *ngIf="book.subtitle">{{ book.subtitle }}</p>
4
  <div class="header">
5
  <div>
6
+ <h2 i18n="headline authors|Headline for the authors@@BookDetailsComponentAuthors">Authors</h2>
7
  <ul>
8
  <li *ngFor="let author of book.authors">{{ author }}</li>
9
  </ul>
10
  </div>
11
  <div>
12
+ <h2 i18n="headline ISBN|Headline for the ISBN@@BookDetailsComponentIsbn">ISBN</h2>
13
  {{ book.isbn | isbn }}
14
  </div>
15
  <div *ngIf="book.published">
16
+ <h2 i18n="headline published|Headline for the published date@@BookDetailsComponentPublished">Published</h2>
17
  {{ book.published | date:'longDate' }}
18
  </div>
19
  </div>
20
+ <h2 i18n="headline description|Headline for the description@@BookDetailsComponentDescription">Description</h2>
21
  <p>{{ book.description }}</p>
22
  <img *ngIf="book.thumbnailUrl"
23
  [src]="book.thumbnailUrl"
24
+ alt="Cover"
25
+ i18n-alt="cover alt text|Alternative text when no cover is defined@@BookDetailsComponentAltCover">
26
+ <a class="button arrow-left" routerLink=".." i18n="link back|Link for navigating back to the books list@@BookDetailsComponentBackToList">Back to list</a>
27
  <ng-container *bmLoggedinOnly>
28
+ <button class="red" bmConfirm="Remove book?" (confirm)="removeBook(book.isbn)" i18n="button remove|Button text to remove a book@@BookDetailsComponentRemove">
29
  Remove book
30
  </button>
31
  <a class="button"
32
+ [routerLink]="['/admin/edit', book.isbn]"
33
+ i18n="button edit|Button text to edit a book@@BookDetailsComponentEdit">
34
  Edit book
35
  </a>
36
  </ng-container>
src/app/books/book-details/book-details.component.spec.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { BookDetailsComponent } from './book-details.component';
4
+
5
+ describe('BookDetailsComponent', () => {
6
+ let component: BookDetailsComponent;
7
+ let fixture: ComponentFixture<BookDetailsComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ declarations: [ BookDetailsComponent ]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(BookDetailsComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
src/app/books/book-list/book-list.component.html CHANGED
@@ -1,9 +1,9 @@
1
- <h1>Books</h1>
2
  <ul class="book-list" *ngIf="books$ | async as books">
3
  <li *ngFor="let book of books">
4
  <bm-book-list-item [book]="book"></bm-book-list-item>
5
  </li>
6
- <li *ngIf="!books.length">
7
  No books available.
8
  </li>
9
  </ul>
1
+ <h1 i18n="title books|Title for the books page@@BookListComponentTitle">Books</h1>
2
  <ul class="book-list" *ngIf="books$ | async as books">
3
  <li *ngFor="let book of books">
4
  <bm-book-list-item [book]="book"></bm-book-list-item>
5
  </li>
6
+ <li *ngIf="!books.length" i18n="text no books|Text displayed when no books are available@@BookListComponentNoBooks">
7
  No books available.
8
  </li>
9
  </ul>
src/app/books/book-list/book-list.component.spec.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { BookListComponent } from './book-list.component';
4
+
5
+ describe('BookListComponent', () => {
6
+ let component: BookListComponent;
7
+ let fixture: ComponentFixture<BookListComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ declarations: [ BookListComponent ]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(BookListComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
src/app/books/book-list-item/book-list-item.component.html CHANGED
@@ -1,5 +1,5 @@
1
  <a *ngIf="book" [routerLink]="book.isbn" class="list-item">
2
- <img *ngIf="book.thumbnailUrl" [src]="book.thumbnailUrl" alt="Cover">
3
  <h2>{{ book.title }}</h2>
4
  <p role="doc-subtitle" *ngIf="book.subtitle">
5
  {{ book.subtitle }}
@@ -9,5 +9,5 @@
9
  {{ author }}
10
  </li>
11
  </ul>
12
- <div>ISBN {{ book.isbn | isbn }}</div>
13
  </a>
1
  <a *ngIf="book" [routerLink]="book.isbn" class="list-item">
2
+ <img *ngIf="book.thumbnailUrl" [src]="book.thumbnailUrl" alt="Cover" i18n-alt="cover alt text|Alternative text when no cover is defined@@BookListItemComponentAltCover">
3
  <h2>{{ book.title }}</h2>
4
  <p role="doc-subtitle" *ngIf="book.subtitle">
5
  {{ book.subtitle }}
9
  {{ author }}
10
  </li>
11
  </ul>
12
+ <div><ng-container i18n="book text before ISBN|Text for ISBN right before the number@@BookListItemComponentIsbn">ISBN</ng-container> {{ book.isbn | isbn }}</div>
13
  </a>
src/app/books/shared/loggedin-only.directive.spec.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ import { LoggedinOnlyDirective } from './loggedin-only.directive';
2
+
3
+ describe('LoggedinOnlyDirective', () => {
4
+ it('should create an instance', () => {
5
+ const directive = new LoggedinOnlyDirective();
6
+ expect(directive).toBeTruthy();
7
+ });
8
+ });
src/app/home/home.component.html CHANGED
@@ -1,8 +1,8 @@
1
- <h1>Home</h1>
2
 
3
- <a routerLink="/books" class="button red">
4
  Show book list
5
  </a>
6
 
7
- <h2>Search</h2>
8
  <bm-search></bm-search>
1
+ <h1 i18n="title home|Title for the home page@@HomeComponentTitle">Home</h1>
2
 
3
+ <a routerLink="/books" class="button red" i18n="link books list|Text of the link to the books screen@@HomeComponentBooks">
4
  Show book list
5
  </a>
6
 
7
+ <h2 i18n="headline search|Headline text above the search input@@HomeComponentSearchHeadline">Search</h2>
8
  <bm-search></bm-search>
src/app/home/home.component.spec.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { NO_ERRORS_SCHEMA } from '@angular/core';
2
  import { ComponentFixture, TestBed } from '@angular/core/testing';
3
 
4
  import { HomeComponent } from './home.component';
@@ -9,8 +8,7 @@ describe('HomeComponent', () => {
9
 
10
  beforeEach(async () => {
11
  await TestBed.configureTestingModule({
12
- declarations: [ HomeComponent ],
13
- schemas: [NO_ERRORS_SCHEMA]
14
  })
15
  .compileComponents();
16
 
 
1
  import { ComponentFixture, TestBed } from '@angular/core/testing';
2
 
3
  import { HomeComponent } from './home.component';
8
 
9
  beforeEach(async () => {
10
  await TestBed.configureTestingModule({
11
+ declarations: [ HomeComponent ]
 
12
  })
13
  .compileComponents();
14
 
src/app/search/search.component.html CHANGED
@@ -1,6 +1,7 @@
1
  <input type="search"
2
  autocomplete="off"
3
  aria-label="Search"
 
4
  [class.loading]="isLoading"
5
  #searchInput
6
  (input)="input$.next(searchInput.value)">
1
  <input type="search"
2
  autocomplete="off"
3
  aria-label="Search"
4
+ i18n-aria-label="search input ARIA label|input search@@SearchComponentInput"
5
  [class.loading]="isLoading"
6
  #searchInput
7
  (input)="input$.next(searchInput.value)">
src/app/search/search.component.spec.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { SearchComponent } from './search.component';
4
+
5
+ describe('SearchComponent', () => {
6
+ let component: SearchComponent;
7
+ let fixture: ComponentFixture<SearchComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ declarations: [ SearchComponent ]
12
+ })
13
+ .compileComponents();
14
+
15
+ fixture = TestBed.createComponent(SearchComponent);
16
+ component = fixture.componentInstance;
17
+ fixture.detectChanges();
18
+ });
19
+
20
+ it('should create', () => {
21
+ expect(component).toBeTruthy();
22
+ });
23
+ });
src/app/shared/book-store.service.spec.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TestBed } from '@angular/core/testing';
2
+
3
+ import { BookStoreService } from './book-store.service';
4
+
5
+ describe('BookStoreService', () => {
6
+ let service: BookStoreService;
7
+
8
+ beforeEach(() => {
9
+ TestBed.configureTestingModule({});
10
+ service = TestBed.inject(BookStoreService);
11
+ });
12
+
13
+ it('should be created', () => {
14
+ expect(service).toBeTruthy();
15
+ });
16
+ });
src/assets/messages.de.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "locale": "de",
3
+ "translations": {
4
+ "BookCreateComponentTitle": "Buch anlegen",
5
+ "BookEditComponentTitle": "Buch bearbeiten",
6
+ "BookFormComponentLabelTitle": "Titel",
7
+ "BookFormComponentLabelSubtitle": "Untertitel",
8
+ "BookFormComponentLabelIsbn": "ISBN",
9
+ "BookFormComponentLabelAuthors": "Autorinnen/Autoren",
10
+ "BookFormComponentAddAuthor": " + Autorin/Autor ",
11
+ "BookFormComponentLabelDescription": "Beschreibung",
12
+ "BookFormComponentLabelPublished": "Veröffentlicht",
13
+ "BookFormComponentLabelThumbnail": "Vorschaubild URL",
14
+ "BookFormComponentSave": " Speichern ",
15
+ "BookFormComponentErrorTitleRequired": "Ein Titel muss angegeben werden.",
16
+ "BookFormComponentErrorIsbnRequired": "Eine ISBN muss angegeben werden.",
17
+ "BookFormComponentErrorIsbnFormat": "Die ISBN muss aus 10 oder 13 Zahlen bestehen.",
18
+ "BookFormComponentErrorIsbnExists": "DIe ISBN existiert bereits.",
19
+ "BookFormComponentErrorAtLeastOneAuthor": "Es muss mindestens ein Name angegeben werden.",
20
+ "AppComponentHome": "Startseite",
21
+ "AppComponentBooks": "Bücher",
22
+ "AppComponentAdmin": "Administration",
23
+ "AppComponentLogin": "Anmelden",
24
+ "AppComponentLogout": "Abmelden",
25
+ "BookDetailsComponentAuthors": "Autorinnen/Autoren",
26
+ "BookDetailsComponentIsbn": "ISBN",
27
+ "BookDetailsComponentPublished": "Veröffentlicht",
28
+ "BookDetailsComponentDescription": "Beschreibung",
29
+ "BookDetailsComponentAltCover": "Buchcover",
30
+ "BookDetailsComponentBackToList": "Zurück zur Liste",
31
+ "BookDetailsComponentRemove": " Buch entfernen ",
32
+ "BookDetailsComponentEdit": " Buch bearbeiten ",
33
+ "BookListItemComponentAltCover": "Buchcover",
34
+ "BookListItemComponentIsbn": "ISBN",
35
+ "BookListComponentTitle": "Bücher",
36
+ "BookListComponentNoBooks": " Keine Bücher verfügbar. ",
37
+ "HomeComponentTitle": "Startseite",
38
+ "HomeComponentBooks": " Zur Buchliste\n",
39
+ "HomeComponentSearchHeadline": "Suche",
40
+ "SearchComponentInput": "Suche",
41
+ "AuthGuardAlert": "Nicht angemeldet!"
42
+ }
43
+ }
src/main.ts CHANGED
@@ -1,7 +1,26 @@
 
 
 
1
  import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
 
2
 
3
  import { AppModule } from './app/app.module';
4
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- platformBrowserDynamic().bootstrapModule(AppModule)
7
- .catch(err => console.error(err));
 
 
 
 
 
1
+ import { registerLocaleData } from '@angular/common';
2
+ import { LOCALE_ID } from '@angular/core';
3
+ import { loadTranslations } from '@angular/localize';
4
  import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
5
+ import localeDe from '@angular/common/locales/de';
6
 
7
  import { AppModule } from './app/app.module';
8
 
9
+ async function setupLocale() {
10
+ if (localStorage.getItem('locale') !== 'de') {
11
+ return 'en-US';
12
+ }
13
+ const response = await fetch('assets/messages.de.json');
14
+ const result = await response.json();
15
+ loadTranslations(result.translations);
16
+ registerLocaleData(localeDe);
17
+ return 'de';
18
+ }
19
 
20
+ setupLocale().then(localeValue => {
21
+ platformBrowserDynamic([
22
+ { provide: LOCALE_ID, useValue: localeValue }
23
+ ])
24
+ .bootstrapModule(AppModule)
25
+ .catch(err => console.error(err));
26
+ });
tsconfig.app.json CHANGED
@@ -3,7 +3,9 @@
3
  "extends": "./tsconfig.json",
4
  "compilerOptions": {
5
  "outDir": "./out-tsc/app",
6
- "types": []
 
 
7
  },
8
  "files": [
9
  "src/main.ts"
3
  "extends": "./tsconfig.json",
4
  "compilerOptions": {
5
  "outDir": "./out-tsc/app",
6
+ "types": [
7
+ "@angular/localize"
8
+ ]
9
  },
10
  "files": [
11
  "src/main.ts"
tsconfig.spec.json CHANGED
@@ -4,7 +4,8 @@
4
  "compilerOptions": {
5
  "outDir": "./out-tsc/spec",
6
  "types": [
7
- "jasmine"
 
8
  ]
9
  },
10
  "include": [
4
  "compilerOptions": {
5
  "outDir": "./out-tsc/spec",
6
  "types": [
7
+ "jasmine",
8
+ "@angular/localize"
9
  ]
10
  },
11
  "include": [