Skip to content

Commit fbb2cd1

Browse files
committed
add translations, fix routes
1 parent c70c3d7 commit fbb2cd1

File tree

14 files changed

+139
-40
lines changed

14 files changed

+139
-40
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Two methods for downloading files are demonstrated:
1515
* Using the **GetJsonFileAsync** method, which returns the file inside a JSON object. The file is converted to a Base64 string to preserve encoding.
1616

1717
# Menu
18+
- [Clean Architecture FileApi](#clean-architecture-fileapi)
19+
- [Menu](#menu)
1820
- [Prerequisites](#prerequisites)
1921
- [Installation](#installation)
2022
- [Get Started](#get-started)
@@ -81,7 +83,7 @@ Important part of every project are **[tests](https://github.com/Gramli/WeatherA
8183
In this solution, each 'code' project has its own unit test project and every **unit test** project copy the same directory structure as 'code' project, which is very helpful for orientation in test project. Infrastructure project has also **integration tests**, because for format conversion is used third party library and we want to know that conversion works always as expected (for example when we update library version).
8284

8385
## Frontend Example
84-
The frontend is a simple Angular 18 project that demonstrates how to upload and download files as blobs or FormData from the C# API. Files are saved to the Downloads folder using the [file-saver](https://www.npmjs.com/package/file-saver) library. For styling, the project utilizes [Bootstrap](https://getbootstrap.com/). Additionally, there are examples of displaying modals with [ng-bootstrap](https://www.npmjs.com/package/@ng-bootstrap/ng-bootstrap) and toasts/notifications with [angular-notifier](https://www.npmjs.com/package/gramli-angular-notifier).
86+
The frontend is a simple Angular project that demonstrates how to upload and download files as blobs or FormData from the C# API. Files are saved to the Downloads folder using the [file-saver](https://www.npmjs.com/package/file-saver) library. For styling, the project utilizes [Bootstrap](https://getbootstrap.com/). It also includes examples of displaying modals with [ng-bootstrap](https://www.npmjs.com/package/@ng-bootstrap/ng-bootstrap) and showing toasts/notifications with [angular-notifier](https://www.npmjs.com/package/gramli-angular-notifier). Additionally, it demonstrates how to use translations in an Angular project with the [@ngx-translate/core](https://ngx-translate.org/) library, including a custom implementation of TranslateLoader to handle multiple translation files.
8587

8688
## Technologies
8789
* [ASP.NET Core 9](https://learn.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-9.0)

src/File.API/EndpointBuilders/FileEndpointsBuilder.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public static class FileEndpointsBuilder
1818
public static IEndpointRouteBuilder BuildFileEndpoints(this IEndpointRouteBuilder endpointRouteBuilder)
1919
{
2020
return endpointRouteBuilder
21-
.MapGroup("file")
2221
.MapVersionGroup(1)
22+
.MapGroup("files")
2323
.BuildUploadEndpoints()
2424
.BuildDownloadEndpoints()
2525
.BuildGetEndpoints()
@@ -29,7 +29,7 @@ public static IEndpointRouteBuilder BuildFileEndpoints(this IEndpointRouteBuilde
2929

3030
private static IEndpointRouteBuilder BuildUploadEndpoints(this IEndpointRouteBuilder endpointRouteBuilder)
3131
{
32-
endpointRouteBuilder.MapPost("upload",
32+
endpointRouteBuilder.MapPost("/upload",
3333
async (IFormFile file, [FromServices] IAddFilesCommandHandler handler, CancellationToken cancellationToken) =>
3434
await handler.SendAsync(new AddFilesCommand([new FormFileProxy(file)]), cancellationToken))
3535
.DisableAntiforgery()
@@ -41,16 +41,16 @@ await handler.SendAsync(new AddFilesCommand([new FormFileProxy(file)]), cancella
4141

4242
private static IEndpointRouteBuilder BuildDownloadEndpoints(this IEndpointRouteBuilder endpointRouteBuilder)
4343
{
44-
endpointRouteBuilder.MapGet("download",
45-
async ([FromQuery] int id, [FromServices] IDownloadFileQueryHandler handler, CancellationToken cancellationToken) =>
44+
endpointRouteBuilder.MapGet("/{id}/download",
45+
async (int id, [FromServices] IDownloadFileQueryHandler handler, CancellationToken cancellationToken) =>
4646
await handler.GetFileAsync(new DownloadFileQuery(id), cancellationToken))
4747
.DisableAntiforgery()
4848
.Produces<FileContentHttpResult>()
4949
.WithName("DownloadFile")
5050
.WithTags("Get");
5151

52-
endpointRouteBuilder.MapGet("downloadAsJson",
53-
async ([FromQuery] int id, [FromServices] IDownloadFileQueryHandler handler, CancellationToken cancellationToken) =>
52+
endpointRouteBuilder.MapGet("/{id}/download/json",
53+
async (int id, [FromServices] IDownloadFileQueryHandler handler, CancellationToken cancellationToken) =>
5454
await handler.GetJsonFileAsync(new DownloadFileQuery(id), cancellationToken))
5555
.DisableAntiforgery()
5656
.ProducesDataResponse<StringContentFileDto>()
@@ -62,7 +62,7 @@ await handler.GetJsonFileAsync(new DownloadFileQuery(id), cancellationToken))
6262

6363
private static IEndpointRouteBuilder BuildGetEndpoints(this IEndpointRouteBuilder endpointRouteBuilder)
6464
{
65-
endpointRouteBuilder.MapGet("files-info",
65+
endpointRouteBuilder.MapGet("/",
6666
async ([FromServices] IGetFilesInfoQueryHandler handler, CancellationToken cancellationToken) =>
6767
await handler.SendAsync(EmptyRequest.Instance, cancellationToken))
6868
.ProducesDataResponse<IEnumerable<FileInfoDto>>()
@@ -73,9 +73,13 @@ await handler.SendAsync(EmptyRequest.Instance, cancellationToken))
7373

7474
private static IEndpointRouteBuilder BuildParseEndpoints(this IEndpointRouteBuilder endpointRouteBuilder)
7575
{
76-
endpointRouteBuilder.MapPost("export",
77-
async ([FromBody]ExportFileQuery parseFileQuery,[FromServices] IExportFileQueryHandler handler, CancellationToken cancellationToken) =>
78-
await handler.GetFileAsync(parseFileQuery, cancellationToken))
76+
endpointRouteBuilder.MapGet("{id}/export",
77+
async (int id, [FromQuery]string extension,[FromServices] IExportFileQueryHandler handler, CancellationToken cancellationToken) =>
78+
await handler.GetFileAsync(new ExportFileQuery
79+
{
80+
Id = id,
81+
Extension = extension
82+
}, cancellationToken))
7983
.DisableAntiforgery()
8084
.ProducesDataResponse<FileContentHttpResult>()
8185
.WithName("Export")
@@ -85,7 +89,7 @@ await handler.GetFileAsync(parseFileQuery, cancellationToken))
8589

8690
private static IEndpointRouteBuilder BuildExportEndpoints(this IEndpointRouteBuilder endpointRouteBuilder)
8791
{
88-
endpointRouteBuilder.MapPost("convert",
92+
endpointRouteBuilder.MapPost("/convert",
8993
async (IFormFile file, [FromForm]string formatToConvert, [FromServices] IConvertToQueryHandler handler, CancellationToken cancellationToken) =>
9094
await handler.GetFileAsync(new ConvertToQuery(new FormFileProxy(file), formatToConvert), cancellationToken))
9195
.DisableAntiforgery()

src/File.API/File.API.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="Ardalis.GuardClauses" Version="5.0.0" />
1111
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" />
12-
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
12+
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
1313
<PackageReference Include="SmallApiToolkit" Version="1.0.0.7" />
1414
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
1515
</ItemGroup>

src/File.Frontend/angular.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
{
3636
"glob": "**/*",
3737
"input": "public"
38-
}
38+
},
39+
"src/assets"
3940
],
4041
"styles": [
4142
"node_modules/bootstrap/dist/css/bootstrap.min.css",

src/File.Frontend/package-lock.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/File.Frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
"@fortawesome/free-solid-svg-icons": "^6.6.0",
2424
"@fortawesome/react-fontawesome": "^0.2.2",
2525
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
26+
"@ngx-translate/core": "^17.0.0",
27+
"@ngx-translate/http-loader": "^17.0.0",
2628
"@popperjs/core": "^2.11.8",
2729
"@types/node": "^22.7.2",
2830
"bootstrap": "^5.3.3",

src/File.Frontend/src/app/app.component.html

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class="container-fluid">
22
<h1 class="mx-1">File.Frontend</h1>
33
<div *ngIf="fileService.loading" class="m-2 p-4">
4-
<h4>Processing...</h4>
4+
<h4>{{ 'processing' | translate}}</h4>
55
<div class="progress">
66
<div
77
class="progress-bar progress-bar-striped progress-bar-animated"
@@ -16,11 +16,11 @@ <h4>Processing...</h4>
1616
<table class="table table-bordered table-hover">
1717
<thead>
1818
<tr>
19-
<td scope="col">Id</td>
20-
<td scope="col">Name</td>
21-
<td scope="col">FileName</td>
22-
<td scope="col">Type</td>
23-
<td scope="col">Action</td>
19+
<td scope="col">{{ 'table.id' | translate }}</td>
20+
<td scope="col">{{ 'table.name' | translate }}</td>
21+
<td scope="col">{{ 'table.fileName' | translate }}</td>
22+
<td scope="col">{{ 'table.type' | translate }}</td>
23+
<td scope="col">{{ 'table.action' | translate }}</td>
2424
</tr>
2525
</thead>
2626
<tbody>
@@ -34,16 +34,16 @@ <h4>Processing...</h4>
3434
class="btn btn-info btn-sm mx-1"
3535
(click)="onDownloadFile(item.id)"
3636
>
37-
Download
37+
{{ 'download' | translate }}
3838
</button>
3939
<button
4040
class="btn btn-info btn-sm mx-1"
4141
(click)="onDownloadFileAsJson(item.id)"
4242
>
43-
Download as Json
43+
{{ 'downloadAsJson' | translate }}
4444
</button>
4545
<button class="btn btn-info btn-sm mx-1" (click)="export(item.id)">
46-
Export
46+
{{ 'export' | translate }}
4747
</button>
4848
</td>
4949
</tr>
@@ -67,10 +67,10 @@ <h4>Processing...</h4>
6767
<div>
6868
<button class="btn btn-primary mx-1" (click)="fileUpload.click()">
6969
<fa-icon [icon]="faUpload"></fa-icon>
70-
Upload
70+
{{ 'upload' | translate }}
7171
</button>
7272
<button class="btn btn-primary mx-1" (click)="fileConvert.click()">
73-
<fa-icon [icon]="faFileImport" class="mx-1"></fa-icon>Convert
73+
<fa-icon [icon]="faFileImport" class="mx-1"></fa-icon>{{ 'convert' | translate }}
7474
</button>
7575
</div>
7676
</div>

src/File.Frontend/src/app/app.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { saveAs } from 'file-saver';
99
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
1010
import { FileLoadingService } from './services/file-loading.service';
1111
import { NotificationAdapterService } from './services/notification-adapter.service';
12+
import { TranslateService } from '@ngx-translate/core';
1213

1314
@Component({
1415
selector: 'app-root',
@@ -26,7 +27,8 @@ export class AppComponent implements OnInit {
2627
constructor(
2728
protected fileService: FileLoadingService,
2829
private ngbModal: NgbModal,
29-
private notifierService: NotificationAdapterService
30+
private notifierService: NotificationAdapterService,
31+
private translateService: TranslateService
3032
) {}
3133

3234
public ngOnInit(): void {

src/File.Frontend/src/app/app.module.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@ import { NgModule } from '@angular/core';
22
import { BrowserModule } from '@angular/platform-browser';
33
import { AppRoutingModule } from './app-routing.module';
44
import { AppComponent } from './app.component';
5-
import { provideHttpClient } from '@angular/common/http';
5+
import { HttpClient, provideHttpClient } from '@angular/common/http';
66
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
77
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
88
import { FormsModule } from '@angular/forms';
99
import { SelectExtensionModalComponent } from './components/select-extension-modal.component';
1010
import { NotifierModule } from 'gramli-angular-notifier';
11+
import { MultiTranslateHttpLoader } from './translate/multi-translate-http-loader';
12+
import {
13+
TranslateLoader,
14+
TranslateModule,
15+
} from '@ngx-translate/core';
16+
17+
export function HttpLoaderFactory(http: HttpClient): TranslateLoader {
18+
return new MultiTranslateHttpLoader(http, [
19+
{ prefix: './assets/i18n/', suffix: '.json' },
20+
{ prefix: './assets/i18n/components/', suffix: '.json' },
21+
]);
22+
}
1123

1224
@NgModule({
1325
declarations: [AppComponent, SelectExtensionModalComponent],
@@ -17,6 +29,15 @@ import { NotifierModule } from 'gramli-angular-notifier';
1729
FontAwesomeModule,
1830
NgbModule,
1931
FormsModule,
32+
TranslateModule.forRoot({
33+
loader: {
34+
provide: TranslateLoader,
35+
useFactory: HttpLoaderFactory,
36+
deps: [HttpClient],
37+
},
38+
lang: 'en',
39+
fallbackLang: 'en'
40+
}),
2041
NotifierModule.withConfig({
2142
position: {
2243
horizontal: {
@@ -25,7 +46,9 @@ import { NotifierModule } from 'gramli-angular-notifier';
2546
},
2647
}),
2748
],
28-
providers: [provideHttpClient()],
49+
providers: [
50+
provideHttpClient()
51+
],
2952
bootstrap: [AppComponent],
3053
})
3154
export class AppModule {}

src/File.Frontend/src/app/components/select-extension-modal.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="fluid-container">
22
<div class="row m-2">
3-
<label for="extensions">Choose a extension:</label>
3+
<label for="extensions">{{ 'extension' | translate }}</label>
44
<select
55
class="form-select my-1"
66
name="extensions"
@@ -16,7 +16,7 @@
1616
<div class="col"></div>
1717
<div class="col">
1818
<button class="w-50 btn btn-primary float-end" (click)="submit()">
19-
OK
19+
{{ 'ok' | translate }}
2020
</button>
2121
</div>
2222
</div>

0 commit comments

Comments
 (0)