La Gestion des Titres avec TitleStrategy dans Angular 17+ ⚡

La Gestion des Titres avec TitleStrategy dans Angular 17+ ⚡

Cet article fait suite à notre série sur un Projet de Bout en Bout avec Angular 17, que vous pouvez retrouver dans l'épisode précédent. Dans ce nouvel épisode, nous allons explorer comment gérer dynamiquement les titres des pages avec TitleStrategy dans Angular 17+. 🚀

Configuration Initiale 📂

Tout d'abord, nous devons configurer notre application Angular de manière personnalisée et modulable. Voici comment vous pouvez structurer votre projet :

📁 Structure du Projet

📂 ./
┣━━ 📂 src
┃   ┣━━ 📂 app
┃   ┃   ┣━━ 📂 components
┃   ┃   ┃   ┣━━ 📂 layouts
┃   ┃   ┃   ┃   ┣━━ 📂 auth-layout
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 auth-layout.component.html (6.0 kB)
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 auth-layout.component.scss (0 bytes)
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 auth-layout.component.spec.ts (625 bytes)
┃   ┃   ┃   ┃   ┃   ┗━━ 📄 auth-layout.component.ts (337 bytes)
┃   ┃   ┃   ┃   ┣━━ 📂 main-footer
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-footer.component.html (3.8 kB)
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-footer.component.scss (0 bytes)
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-footer.component.spec.ts (616 bytes)
┃   ┃   ┃   ┃   ┃   ┗━━ 📄 main-footer.component.ts (252 bytes)
┃   ┃   ┃   ┃   ┣━━ 📂 main-header
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-header.component.html (257.1 kB)
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-header.component.scss (0 bytes)
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-header.component.spec.ts (616 bytes)
┃   ┃   ┃   ┃   ┃   ┗━━ 📄 main-header.component.ts (252 bytes)
┃   ┃   ┃   ┃   ┣━━ 📂 main-layout
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-layout.component.html (257.4 kB)
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-layout.component.scss (0 bytes)
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 main-layout.component.spec.ts (616 bytes)
┃   ┃   ┃   ┃   ┃   ┗━━ 📄 main-layout.component.ts (810 bytes)
┃   ┃   ┃   ┃   ┗━━ 📂 main-sidebar
┃   ┃   ┃   ┃       ┣━━ 📄 main-sidebar.component.html (50.1 kB)
┃   ┃   ┃   ┃       ┣━━ 📄 main-sidebar.component.scss (0 bytes)
┃   ┃   ┃   ┃       ┣━━ 📄 main-sidebar.component.spec.ts (623 bytes)
┃   ┃   ┃   ┃       ┗━━ 📄 main-sidebar.component.ts (256 bytes)
┃   ┃   ┃   ┣━━ 📂 ui-elements
┃   ┃   ┃   ┃   ┗━━ 📂 buttons
┃   ┃   ┃   ┃       ┗━━ 📂 login-with-oauths
┃   ┃   ┃   ┃           ┣━━ 📄 login-with-oauths.component.html (1.3 kB)
┃   ┃   ┃   ┃           ┣━━ 📄 login-with-oauths.component.scss (0 bytes)
┃   ┃   ┃   ┃           ┣━━ 📄 login-with-oauths.component.spec.ts (661 bytes)
┃   ┃   ┃   ┃           ┗━━ 📄 login-with-oauths.component.ts (1.6 kB)
┃   ┃   ┃   ┗━━ 📂 views
┃   ┃   ┃       ┣━━ 📂 auth
┃   ┃   ┃       ┃   ┣━━ 📂 forgotten-password
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 forgotten-password.component.html (1.5 kB)
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 forgotten-password.component.scss (0 bytes)
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 forgotten-password.component.spec.ts (674 bytes)
┃   ┃   ┃       ┃   ┃   ┗━━ 📄 forgotten-password.component.ts (282 bytes)
┃   ┃   ┃       ┃   ┣━━ 📂 login
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 login.component.html (3.3 kB)
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 login.component.scss (0 bytes)
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 login.component.spec.ts (580 bytes)
┃   ┃   ┃       ┃   ┃   ┗━━ 📄 login.component.ts (2.6 kB)
┃   ┃   ┃       ┃   ┣━━ 📂 reset-password
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 reset-password.component.html (3.6 kB)
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 reset-password.component.scss (0 bytes)
┃   ┃   ┃       ┃   ┃   ┣━━ 📄 reset-password.component.spec.ts (646 bytes)
┃   ┃   ┃       ┃   ┃   ┗━━ 📄 reset-password.component.ts (266 bytes)
┃   ┃   ┃       ┃   ┗━━ 📂 signup
┃   ┃   ┃       ┃       ┣━━ 📄 signup.component.html (4.3 kB)
┃   ┃   ┃       ┃       ┣━━ 📄 signup.component.scss (0 bytes)
┃   ┃   ┃       ┃       ┣━━ 📄 signup.component.spec.ts (596 bytes)
┃   ┃   ┃       ┃       ┗━━ 📄 signup.component.ts (378 bytes)
┃   ┃   ┃       ┣━━ 📂 home
┃   ┃   ┃       ┃   ┗━━ 📂 welcome-dashboard
┃   ┃   ┃       ┃       ┣━━ 📄 welcome-dashboard.component.html (12.2 kB)
┃   ┃   ┃       ┃       ┣━━ 📄 welcome-dashboard.component.scss (350 bytes)
┃   ┃   ┃       ┃       ┣━━ 📄 welcome-dashboard.component.spec.ts (667 bytes)
┃   ┃   ┃       ┃       ┗━━ 📄 welcome-dashboard.component.ts (278 bytes)
┃   ┃   ┃       ┗━━ 📂 tenders
┃   ┃   ┃           ┗━━ 📂 offres-list-view
┃   ┃   ┃               ┣━━ 📄 offres-list-view.component.html (163.7 kB)
┃   ┃   ┃               ┣━━ 📄 offres-list-view.component.scss (0 bytes)
┃   ┃   ┃               ┣━━ 📄 offres-list-view.component.spec.ts (667 bytes)
┃   ┃   ┃               ┗━━ 📄 offres-list-view.component.ts (521 bytes)
┃   ┃   ┣━━ 📂 core
┃   ┃   ┃   ┣━━ 📂 configs
┃   ┃   ┃   ┃   ┣━━ 📂 routes
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 app.routes.ts (2.5 kB)
┃   ┃   ┃   ┃   ┃   ┗━━ 📄 routes.types.ts (369 bytes)
┃   ┃   ┃   ┃   ┣━━ 📂 seo
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 aops-title.strategy.ts (1.1 kB)
┃   ┃   ┃   ┃   ┃   ┗━━ 📄 seo.config.ts (411 bytes)
┃   ┃   ┃   ┃   ┗━━ 📄 app.config.ts (2.3 kB)
┃   ┃   ┃   ┣━━ 📂 errors
┃   ┃   ┃   ┃   ┣━━ 📄 http-error-handler.helper.ts (2.4 kB)
┃   ┃   ┃   ┃   ┗━━ 📄 orbit-error-handler.helper.ts (1.7 kB)
┃   ┃   ┃   ┣━━ 📂 lang
┃   ┃   ┃   ┃   ┣━━ 📄 messages.en.ts (2.6 kB)
┃   ┃   ┃   ┃   ┗━━ 📄 messages.fr.ts (3.1 kB)
┃   ┃   ┃   ┣━━ 📂 libs
┃   ┃   ┃   ┃   ┗━━ 📄 appwrite.ts (1.2 kB)
┃   ┃   ┃   ┗━━ 📂 security
┃   ┃   ┣━━ 📂 helpers
┃   ┃   ┃   ┣━━ 📂 alerts
┃   ┃   ┃   ┃   ┣━━ 📄 alert-helper.service.spec.ts (383 bytes)
┃   ┃   ┃   ┃   ┣━━ 📄 alert-helper.service.ts (3.4 kB)
┃   ┃   ┃   ┃   ┗━━ 📄 alert.types.ts (428 bytes)
┃   ┃   ┃   ┣━━ 📂 translator
┃   ┃   ┃   ┃   ┣━━ 📄 translation-helper.service.spec.ts (413 bytes)
┃   ┃   ┃   ┃   ┗━━ 📄 translation-helper.service.ts (2.2 kB)
┃   ┃   ┃   ┣━━ 📂 types
┃   ┃   ┃   ┃   ┗━━ 📄 types.helper.ts (2.0 kB)
┃   ┃   ┃   ┗━━ 📄 miscs.helper.ts (1.7 kB)
┃   ┃   ┣━━ 📂 services
┃   ┃   ┃   ┗━━ 📂 auth
┃   ┃   ┃       ┣━━ 📄 auth.service.spec.ts (347 bytes)
┃   ┃   ┃       ┣━━ 📄 auth.service.ts (2.2 kB)
┃   ┃   ┃       ┗━━ 📄 auth.types.ts (183 bytes)
┃   ┃   ┣━━ 📄 app.component.html (17 bytes)
┃   ┃   ┣━━ 📄 app.component.scss (0 bytes)
┃   ┃   ┣━━ 📄 app.component.spec.ts (910 bytes)
┃   ┃   ┗━━ 📄 app.component.ts (437 bytes)
┃   ┣━━ 📂 environments
┃   ┃   ┣━━ 📄 environment.development.ts (804 bytes)
┃   ┃   ┣━━ 📄 environment.example.ts (606 bytes)
┃   ┃   ┗━━ 📄 environment.ts (804 bytes)
┃   ┣━━ 📂 locales
┃   ┃   ┣━━ 📄 messages.en.xlf (10.3 kB)
┃   ┃   ┗━━ 📄 messages.xlf (10.3 kB)
┃   ┣━━ 📄 favicon.ico (15.1 kB)
┃   ┣━━ 📄 index.html (6.9 kB)
┃   ┣━━ 📄 main.ts (411 bytes)
┃   ┗━━ 📄 styles.scss (3.6 kB)
┣━━ 📄 angular.json (3.5 kB)
┣━━ 📄 compose.yaml (1.7 kB)
┣━━ 📄 DevLog-Plateforme-AOPS-Front.md (11.7 kB)
┣━━ 📄 Dockerfile (2.7 kB)
┣━━ 📄 LICENSE (16.4 kB)
┣━━ 📄 package.json (2.1 kB)
┣━━ 📄 README.Docker.md (848 bytes)
┣━━ 📄 README.md (2.8 kB)
┣━━ 📄 step-logs.orbit (4.0 kB)
┣━━ 📄 tsconfig.app.json (266 bytes)
┣━━ 📄 tsconfig.json (857 bytes)
┣━━ 📄 tsconfig.spec.json (270 bytes)
┣━━ 📄 yarn-error.log (336.3 kB)
┃━━ 📄 yarn.lock (381.6 kB)
┃━━ 📄 .angular
┃━━ 📄 .devcontainer
┃━━ 📄 .git
┃━━ 📄 .github
┃━━ 📄 .husky
┃━━ 📄 .vscode
┃━━ 📄 .commitlintrc.json
┃━━ 📄 .dockerignore
┃━━ 📄 .editorconfig
┃━━ 📄 .eslintignore
┃━━ 📄 .eslintrc.json
┃━━ 📄 .gitignore
┃━━ 📄 .prettierignore
┗━━ 📄 .prettierrc.yml

Pourquoi Utiliser TitleStrategy et Gérer les Titres de Pages ? 🤔

La gestion des titres de pages dans une application web est cruciale pour plusieurs raisons :

  1. SEO (Search Engine Optimization) 🌐 : Les moteurs de recherche utilisent les titres de pages pour comprendre le contenu de chaque page et les indexer correctement. Des titres pertinents et bien structurés améliorent la visibilité de votre site.

  2. Accessibilité 🧑‍🦯 : Les titres aident les utilisateurs à naviguer facilement sur votre site, en particulier ceux utilisant des technologies d'assistance.

  3. Expérience Utilisateur 🎨 : Un titre de page clair et pertinent aide les utilisateurs à comprendre immédiatement le contenu de la page.

  4. UX : Des titres bien définis améliorent l'expérience utilisateur en fournissant une navigation claire.

  5. Historique: Les utilisateurs auront plus de facilité à parcourir l'historique des pages visité sur notre app.

La Galère du SEO dans Angular 📈

Gérer le SEO dans une application Angular peut être compliqué. Angular étant une application Single Page Application (SPA), les moteurs de recherche peuvent avoir du mal à indexer correctement le contenu. En gérant dynamiquement les titres et les méta descriptions, nous aidons les moteurs de recherche à comprendre et indexer nos pages correctement.

Définir les Routes avec des Titres Dynamiques 📍

Pour commencer, nous avons créé un type personnalisé OrbitRoutes qui étend l'interface Route d'Angular pour inclure des propriétés supplémentaires telles que description, moduleName, et d'autres options de configuration.

// routes.types.ts
import { Route } from '@angular/router';
import { PageTitleKeys } from '../../../helpers/translator/translation-helper.service';

export interface OrbitRoutes extends Route {
    title?: PageTitleKeys | '-';
    children?: OrbitRoutes[];
    moduleName?: string;
    description?: string;
    dontDisplayOnMenu?: boolean;
    icon?: string;
    isDisabled?: boolean;
}

Notre Fichier Routing (app.routes.ts)

import { OrbitRoutes } from './routes.types';
import { MainLayoutComponent } from '../../../components/layouts/main-layout/main-layout.component';
import { OffresListViewComponent } from '../../../components/views/tenders/offres-list-view/offres-list-view.component';
import { LoginComponent } from '../../../components/views/auth/login/login.component';
import { AuthLayoutComponent } from '../../../components/layouts/auth-layout/auth-layout.component';
import { AppModulesList } from '../seo/seo.config';
import { WelcomeDashboardComponent } from '../../../components/views/home/welcome-dashboard/welcome-dashboard.component';

export const routes: OrbitRoutes[] = [
  /**
   * ========================
   * APP DEFAULT LAYOUT
   * ========================
   */
  {
    path: '',
    component: MainLayoutComponent,
    children: [
      { path: '', redirectTo: 'home', pathMatch: 'full' },
      {
        path: 'home',
        moduleName: AppModulesList.HOME,
        title: 'HOME',
        component: WelcomeDashboardComponent,
      },


      // ------------------- //
      // OFFERS
      // ------------------- //
      {
        path: 'offers',
        moduleName: AppModulesList.TENDERS,
        title: 'OFFERS',
        children: [
          { path: '', redirectTo: 'list', pathMatch: 'full' },
          {
            path: 'list',
            title: 'OFFERSLIST',
            component: OffresListViewComponent
          }
        ]
      },
    ]
  },

  /**
 * ========================
 * AUTH
 * ========================
 */
  {
    path: 'auth',
    component: AuthLayoutComponent,
    moduleName: AppModulesList.AUTH,
    title: 'AUTH',
    dontDisplayOnMenu: true,
    children: [
      {
        path: 'login',
        title: 'LOGIN',
        component: LoginComponent
      },
      {
        path: 'reset-password',
        title: 'RESETPWD',
        component: LoginComponent
      },
      {
        path: 'forgot-password',
        title: 'FORGOTTENPWD',
        component: LoginComponent
      },
      {
        path: 'register',
        title: 'SIGNUP',
        component: LoginComponent
      }
    ]
  },
  // ...
];

Dynamiser les Routes 🌐

En définissant nos propres routes de cette maniere (Ex:OrbitRoutes), nous prévoyons de rendre les routes dynamiques et de les enrichir avec des métadonnées utiles. Cela nous permet d'adapter facilement nos routes pour différents modules et d'améliorer la gestion du SEO mais aussi des futures logiques de notre app .

Créer une Stratégie de Titre Personnalisée 🏗️

Dans Angular, la gestion des titres de pages peut se faire via:

  • le TitleService dans chaque component

  • L'attribut title sur la route,
    Mais cela peut vite devenir difficile à maintenir si l'on a un grand nombre de routes et des besoins de traduction. Angular 13 a introduit la TitleStrategy, une manière plus structurée et dynamique de gérer les titres des pages. Cela devient particulièrement utile pour des applications complexes et multilingues comme la nôtre.

Qu'est-ce que la TitleStrategy dans Angular ?

TitleStrategy est une classe de base que vous pouvez étendre pour personnaliser la manière dont les titres des pages sont définis. Angular utilise cette stratégie pour construire le titre de chaque page en fonction de la route active. En étendant cette classe, nous pouvons définir une logique centralisée et cohérente pour nos titres de pages.

Voici comment Angular utilise la TitleStrategy :

  1. Définir une stratégie personnalisée : Nous créons une nouvelle classe qui étend TitleStrategy.

  2. Override de la méthode updateTitle : Nous redéfinissons cette méthode pour appliquer notre propre logique de titre.

  3. Injection de la stratégie personnalisée : Nous configurons Angular pour utiliser notre stratégie personnalisée en l'injectant dans la configuration du routeur.

Étape par Étape : Implémentation de AopsPageTitleStrategy

1. Créer une nouvelle classe qui étend TitleStrategy :
    import { Injectable, inject } from '@angular/core';
    import { TitleStrategy, RouterStateSnapshot } from '@angular/router';
    import { Title } from '@angular/platform-browser';
    import { TranslationHelperService, PageTitleKeys } from '../../../helpers/translator/translation-helper.service';
    import { AppTitleSeparator, PageTitlesConfs } from './seo.config';

    @Injectable({ providedIn: 'root' })
    export class AopsPageTitleStrategy extends TitleStrategy {
        private readonly titleService = inject(Title);
        private readonly translator = inject(TranslationHelperService);

        constructor() {
            super();
        }

        override updateTitle(snapshot: RouterStateSnapshot): void {
            // Utiliser la méthode buildTitle pour obtenir le titre de la route actuelle
            const title = this.buildTitle(snapshot);
            if (title) {
                // Récupérer les titres traduits
                const pageTitles = this.translator.translate().PageTitles;
                const translatedTitle = pageTitles[title as PageTitleKeys] || title;
                // Définir le titre de la page avec des séparateurs et des tags personnalisés
                this.titleService.setTitle(`${PageTitlesConfs.TitleEmoji} ${translatedTitle} ${AppTitleSeparator} ${PageTitlesConfs.TitleMainTag}`);
            }
        }
    }
2. Pourquoi inject ? :
  • inject permet d'injecter des dépendances dans les classes sans avoir besoin de les passer par le constructeur, simplifiant ainsi le code et rendant les dépendances plus explicites.
3. Configurer Angular pour utiliser notre TitleStrategy :
// app.config.ts
import { Routes, TitleStrategy, provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './routes/app.routes';
import { AopsPageTitleStrategy } from './seo/aops-title.strategy';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes as Routes, withComponentInputBinding()),
    { provide: TitleStrategy, useClass: AopsPageTitleStrategy },
    // ... Your Other Stuffs
  ]
};
    ```

##### 4. **Comment ça marche ?** :
   - `updateTitle` est appelé automatiquement par Angular lors de la navigation. Nous redéfinissons cette méthode pour appliquer notre logique personnalisée de gestion des titres.
   - `buildTitle` est une méthode de `TitleStrategy` qui construit le titre en fonction de la route active. Nous utilisons cette méthode pour obtenir le titre de la route actuelle.
   - Nous récupérons ensuite la traduction du titre via le service de traduction et définissons le titre de la page avec `TitleService`.

Nous utilisons la propriété `title` des routes Angular comme clé pour chercher les traductions dans notre dictionnaire. Cela permet de centraliser la gestion des titres et de faciliter la maintenance des traductions.

##### 5. **Avantages** :
   - **Centralisation** : La logique de gestion des titres est centralisée dans une seule classe, facilitant la maintenance.
   - **Dynamisme** : Les titres peuvent être dynamiquement définis et traduits en fonction de la route active.
   - **Scalabilité** : Cette approche est scalable et peut facilement s'adapter à des applications plus complexes avec de nombreuses routes et langues.

### Utiliser une Configuration de SEO 📜

Nous avons également un fichier de configuration `seo.config.ts` qui contient des constantes et des définitions utilisées par nos différents utilitaires de SEO.

```typescript
// seo.config.ts
import { environment } from "../../../../environments/environment";

// ===========[ CONSTANTS ]===========
export const AppTitleSeparator = '➤';

export enum AppModules {
    HOME = 'HOME',
    TENDERS = 'TENDERS',
    AUTH = 'AUTH',
    ERROR = 'ERROR',
    TOOLS = 'TOOLS',
    MENU = 'MENU',
}

export const PageTitlesConfs = {
    TitleEmoji: `🟢`,
    TitleMainTag: `${environment.appName}`,
};

La Gestion de la Traduction 🌍

Dans notre application, nous avons opté pour un système de traduction personnalisé au lieu d'utiliser le service de traduction ou d'i18n d'Angular pour certains aspects de l'application (services, utils, helpers). Cela nous offre plus de flexibilité et une meilleure expérience de développement. Voici comment nous avons mis en place ce système.

Aperçu du Helper de Traduction

Nous avons créé un service de traduction appelé TranslationHelperService. Ce service charge les traductions en fonction de la langue préférée de l'utilisateur et fournit les méthodes nécessaires pour accéder à ces traductions.

Code du service translation-helper.service.ts
import { Injectable } from '@angular/core';
import { TranslationsKeys as EnglishTranslations } from '../../core/lang/messages.en';
import { TranslationsKeys as FrenchTranslations } from '../../core/lang/messages.fr';
import { environment } from '../../../environments/environment';

// ================[ TYPES ]===========
type AppTranslations = EnglishTranslations & FrenchTranslations;
type PageTitleKeys = keyof AppTranslations["PageTitles"];

export type { AppTranslations, PageTitleKeys };
// =====================================

@Injectable({
  providedIn: 'root'
})
export class TranslationHelperService {
  // ===========[ PROPERTIES ]===========
  private translations!: AppTranslations;
  public readonly dictionnary!: EnglishTranslations | FrenchTranslations;

  // ===========[ INIT ]===========
  async loadTranslations(language: string): Promise<void> {
    const translationsModule = await import(`../../core/lang/messages.${language}.ts`);
    this.translations = translationsModule.default;
  }

  // ===========[ METHODS ]===========
  // TODO : Use Getters instead of this method
  translate(): AppTranslations {
    return this.translations;
  }
}

/**
 * Returns the preferred language of the user.
 * If the language is set in the path, it is returned.
 * If the language is set in the local storage, it is returned.
 * Otherwise, 'fr' is returned.
 * 
 * @returns {string} The preferred language of the user.
 * @memberof TranslationHelperService
 * 
 * @example
 * const preferredLanguage = getPreferredLanguage();
 * console.log(preferredLanguage); // 'fr'
 * 
 * @public
 */
export function getPreferredLanguage(): string {
  const pathArray = window.location.pathname.split('/');
  const langFromPath = pathArray[1]; // Assumant que le segment de langue est toujours le premier
  if (langFromPath === 'fr' || langFromPath === 'en') {
    return langFromPath;
  }

  const langFromStorage = localStorage.getItem(environment.preferedLangKey);
  if (langFromStorage) {
    return langFromStorage;
  }

  return environment.appDefaultLang;
}
Initialisation dans AppConfig

Pour que notre service de traduction soit initialisé au démarrage de l'application avec la langue préférée de l'utilisateur, nous l'avons configuré dans appConfig :

// app.config.ts
import { ApplicationConfig, TitleStrategy, provideRouter, withComponentInputBinding } from '@angular/core';
import { routes } from './routes/app.routes';
import { AopsPageTitleStrategy } from './seo/aops-title.strategy';
import { provideHttpClient } from '@angular/common/http';
import { LoggerModule, NgxLoggerLevel } from 'ngx-logger';
import { environment } from '../../../environments/environment';
import { TranslationHelperService, getPreferredLanguage } from '../../helpers/translator/translation-helper.service';

export const appConfig: ApplicationConfig = {
  // Global Providers
  providers: [
    provideRouter(routes as Routes, withComponentInputBinding()),
    { provide: TitleStrategy, useClass: AopsPageTitleStrategy },
    provideHttpClient(),
    { provide: LOCALE_ID, useValue: 'fr-FR' },
    {
      provide: APP_INITIALIZER,
      useFactory: (translationService: TranslationHelperService) => () => translationService.loadTranslations(getPreferredLanguage()),
      deps: [TranslationHelperService],
      multi: true
    },
    // other providers...
  ]
};

Fichiers de Traductions ou Messages

Nous avons choisi d'utiliser des fichiers de traduction en TypeScript (.ts) plutôt qu'en JSON ou XLF pour plusieurs raisons. Premièrement, les fichiers .ts offrent une meilleure intégration avec le système de typage de TypeScript, permettant ainsi une autocomplétion et une validation de type directement dans l'IDE, ce qui améliore la productivité des développeurs et réduit les erreurs. Deuxièmement, en utilisant des fichiers .ts, nous pouvons bénéficier des fonctionnalités d'importation dynamique de modules, ce qui nous permet de charger les traductions en fonction de la langue préférée de l'utilisateur de manière asynchrone, optimisant ainsi les performances de l'application. Enfin, cette approche permet une meilleure organisation et structure des fichiers de traduction, facilitant la maintenance et l'évolution des traductions au fur et à mesure que l'application grandit.

Voici un exemple de fichier de traduction :

// messages.en.ts
const translations = {
    PageTitles: {
        HOME: 'Home',
        OFFERSLIST: 'Offers List',
        LOGIN: 'Login',
        RESETPWD: 'Reset Password',
        FORGOTTENPWD: 'Forgotten Password',
        SIGNUP: 'Sign Up',
    },
    // ---------------------------------------
    // Errors
    // ---------------------------------------
    errors: {
        unauthorized: 'You are not authorized to access this resource.',
        forbidden: 'Access forbidden.',
        notFound: 'The requested resource is not found.',
        internalServerError: 'Internal server error. Please try again later.',
        generic: 'An error occurred. Please try again.',
        verboseGenericError: "An error occurred.\n>> Details : ",
        reportGenericFailureTitle: 'Oops! Something went wrong!',
        reportGenericFailureBtn: 'Ok, Report this error',
    },
    // ...
};

// ---------------------------------------
// Exports
// ---------------------------------------
type TranslationsKeys = typeof translations;
export default translations;
export type { TranslationsKeys };

Nous plaçons ces fichiers de traduction sous lang/ et nous les chargeons dynamiquement en fonction de la langue préférée de l'utilisateur.

Avantages de cette approche
  1. Flexibilité : Nous pouvons facilement ajouter ou modifier des traductions sans dépendre d'une bibliothèque externe.

  2. Optimisation : Les traductions sont chargées dynamiquement en fonction de la langue préférée de l'utilisateur lors du premier chargement.

  3. Personnalisation : Nous pouvons adapter notre système de traduction à nos besoins spécifiques et ajouter des fonctionnalités supplémentaires si nécessaire.

Conclusion 🎉

Nous avons maintenant une stratégie de gestion des titres de pages robuste et dynamique, intégrée dans notre application Angular 17. En combinant les meilleures pratiques de SEO, de traduction et de gestion des routes, nous avons créé une architecture scalable et maintenable. Cette approche nous permet non seulement d'améliorer le SEO et l'accessibilité de notre application, mais aussi de faciliter son développement et sa maintenance. 🚀

Merci de suivre cette série d'articles sur Angular 17. Restez à l'écoute pour plus de guides et de bonnes pratiques ! 😊