- Search Users
+ {{ 'app.searchUser.title' | translate }}
search
diff --git a/client/libs/web/users/src/lib/user-task-list.component.ts b/client/libs/web/users/src/lib/user-task-list.component.ts
index fd64041..5733f99 100644
--- a/client/libs/web/users/src/lib/user-task-list.component.ts
+++ b/client/libs/web/users/src/lib/user-task-list.component.ts
@@ -8,17 +8,24 @@ import {
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { TaskResponse } from '@org/shared/api';
+import { AppTranslatePipe } from '@org/shared/i18n';
import { TaskFormComponent } from '@org/web/tasks';
@Component({
selector: 'lib-user-task-list',
- imports: [CommonModule, MatIconModule, MatListModule, TaskFormComponent],
+ imports: [
+ CommonModule,
+ MatIconModule,
+ MatListModule,
+ TaskFormComponent,
+ AppTranslatePipe,
+ ],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
assignment
- User Tasks
+ {{ 'app.userTaskList' | translate }}
@if (tasks().length) {
@@ -32,7 +39,8 @@ import { TaskFormComponent } from '@org/web/tasks';
{{ task.tasks.title }}
- {{ task.tasks.description || 'No description' }}
+ {{ task.tasks.description }} ||
+ {{ 'app.noDescription' | translate }}
}
@@ -40,7 +48,7 @@ import { TaskFormComponent } from '@org/web/tasks';
} @else {
assignment_late
-
No tasks found for this user.
+
{{ 'app.noTasksFound' | translate }}
}
diff --git a/client/package-lock.json b/client/package-lock.json
index 3e7a347..d1ecf9d 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -19,6 +19,7 @@
"@angular/platform-browser": "~20.0.0",
"@angular/platform-browser-dynamic": "~20.0.0",
"@angular/router": "~20.0.0",
+ "@formatjs/intl": "^3.1.6",
"@mmstack/primitives": "^20.0.1",
"@mmstack/translate": "^20.0.1",
"rxjs": "~7.8.0",
@@ -2692,7 +2693,6 @@
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz",
"integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@formatjs/fast-memoize": "2.2.7",
"@formatjs/intl-localematcher": "0.6.1",
@@ -2705,7 +2705,6 @@
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
@@ -2715,7 +2714,6 @@
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz",
"integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"@formatjs/icu-skeleton-parser": "1.8.14",
@@ -2727,7 +2725,6 @@
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz",
"integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"tslib": "^2.8.0"
@@ -2738,7 +2735,6 @@
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-3.1.6.tgz",
"integrity": "sha512-tDkXnA4qpIFcDWac8CyVJq6oW8DR7W44QDUBsfXWIIJD/FYYen0QoH46W7XsVMFfPOVKkvbufjboZrrWbEfmww==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"@formatjs/fast-memoize": "2.2.7",
@@ -2760,7 +2756,6 @@
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz",
"integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"tslib": "^2.8.0"
}
@@ -11282,7 +11277,6 @@
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz",
"integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==",
"license": "BSD-3-Clause",
- "peer": true,
"dependencies": {
"@formatjs/ecma402-abstract": "2.3.4",
"@formatjs/fast-memoize": "2.2.7",
diff --git a/client/package.json b/client/package.json
index ff9ca47..471bd3d 100644
--- a/client/package.json
+++ b/client/package.json
@@ -19,6 +19,7 @@
"@angular/platform-browser": "~20.0.0",
"@angular/platform-browser-dynamic": "~20.0.0",
"@angular/router": "~20.0.0",
+ "@formatjs/intl": "^3.1.6",
"@mmstack/primitives": "^20.0.1",
"@mmstack/translate": "^20.0.1",
"rxjs": "~7.8.0",
diff --git a/client/src/app/app.config.ts b/client/src/app/app.config.ts
index 8ed3578..8b5b83a 100644
--- a/client/src/app/app.config.ts
+++ b/client/src/app/app.config.ts
@@ -7,6 +7,7 @@ import {
} from '@angular/core';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { provideRouter } from '@angular/router';
+import { provideIntlConfig } from '@mmstack/translate';
import { API, apiUrls } from '@org/shared/api';
import { appRoutes } from './app.routes';
@@ -21,6 +22,8 @@ export const appConfig: ApplicationConfig = {
provide: API,
useValue: apiUrls,
},
- // No need to provide ThemeService and LanguageService as they use providedIn: 'root'
+ provideIntlConfig({
+ defaultLocale: 'en',
+ }),
],
};
diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts
index a68a555..f32112c 100644
--- a/client/src/app/app.routes.ts
+++ b/client/src/app/app.routes.ts
@@ -1,23 +1,39 @@
-import { Route } from '@angular/router';
-import { TaskListComponent } from '@org/web/tasks';
-import { UserDetailsComponent, UserListComponent } from '@org/web/users';
+import { inject, LOCALE_ID } from '@angular/core';
+import { ActivatedRouteSnapshot, Route } from '@angular/router';
+import { LocaleStore, resolveAppTranslations } from '@org/shared/i18n';
export const appRoutes: Route[] = [
{
- path: 'users',
- component: UserListComponent,
+ path: ':locale',
+ resolve: {
+ localeId: (route: ActivatedRouteSnapshot) => {
+ const store = inject(LocaleStore);
+
+ const locale = route.params['locale'] || 'en';
+
+ store.locale.set(locale);
+
+ return locale;
+ },
+ resolveAppTranslations,
+ },
+ providers: [
+ {
+ provide: LOCALE_ID,
+ useFactory: (store: LocaleStore) => {
+ return store.getCurrentLocale();
+ },
+ deps: [LocaleStore],
+ },
+ ],
+ loadComponent: () =>
+ import('./locale-shell.component').then((m) => m.LocaleShellComponent),
+ loadChildren: () =>
+ import('./locale-shell.routes').then((m) => m.shellRoutes),
},
{
- path: 'users/:id',
- component: UserDetailsComponent,
- },
- {
- path: 'tasks',
- component: TaskListComponent,
- },
- {
- path: '**',
- redirectTo: 'users',
+ path: '',
+ redirectTo: '/en',
pathMatch: 'full',
},
];
diff --git a/client/src/app/app.ts b/client/src/app/app.ts
index be8fc71..d1dde4d 100644
--- a/client/src/app/app.ts
+++ b/client/src/app/app.ts
@@ -1,25 +1,9 @@
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
-import { NavbarComponent } from '@org/shared/navbar';
@Component({
- imports: [RouterModule, NavbarComponent],
+ imports: [RouterModule],
selector: 'app-root',
- template: `
-
-
-
-
- `,
- styles: [
- `
- .content-container {
- padding: 1.5rem;
- max-width: 1200px;
- margin: 0 auto;
- min-height: calc(100vh - 64px);
- }
- `,
- ],
+ template: `
`,
})
export class App {}
diff --git a/client/src/app/locale-shell.component.ts b/client/src/app/locale-shell.component.ts
new file mode 100644
index 0000000..7095769
--- /dev/null
+++ b/client/src/app/locale-shell.component.ts
@@ -0,0 +1,25 @@
+import { Component } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { NavbarComponent } from '@org/shared/navbar';
+
+@Component({
+ selector: 'app-locale-shell',
+ imports: [RouterModule, NavbarComponent],
+ template: `
+
+
+
+
+ `,
+ styles: [
+ `
+ .content-container {
+ padding: 1.5rem;
+ max-width: 1200px;
+ margin: 0 auto;
+ min-height: calc(100vh - 64px);
+ }
+ `,
+ ],
+})
+export class LocaleShellComponent {}
diff --git a/client/src/app/locale-shell.routes.ts b/client/src/app/locale-shell.routes.ts
new file mode 100644
index 0000000..42a91c2
--- /dev/null
+++ b/client/src/app/locale-shell.routes.ts
@@ -0,0 +1,24 @@
+import { Route } from '@angular/router';
+
+export const shellRoutes: Route[] = [
+ {
+ path: 'users',
+ loadComponent: () =>
+ import('@org/web/users').then((m) => m.UserListComponent),
+ },
+ {
+ path: 'users/:id',
+ loadComponent: () =>
+ import('@org/web/users').then((m) => m.UserDetailsComponent),
+ },
+ {
+ path: 'tasks',
+ loadComponent: () =>
+ import('@org/web/tasks').then((m) => m.TaskListComponent),
+ },
+ {
+ path: '**',
+ redirectTo: 'users',
+ pathMatch: 'full',
+ },
+];
diff --git a/client/tsconfig.base.json b/client/tsconfig.base.json
index 7bdd942..170c44f 100644
--- a/client/tsconfig.base.json
+++ b/client/tsconfig.base.json
@@ -5,7 +5,7 @@
"emitDecoratorMetadata": false,
"target": "es2022",
"module": "preserve",
- "lib": ["es2020", "dom"],
+ "lib": ["es2022", "dom"],
"baseUrl": ".",
"paths": {
"@org/shared/api": ["libs/shared/api/src/index.ts"],