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

Feature(transloco): Routes localization #669

Open
1 task done
SvenBudak opened this issue Jul 2, 2023 · 7 comments
Open
1 task done

Feature(transloco): Routes localization #669

SvenBudak opened this issue Jul 2, 2023 · 7 comments

Comments

@SvenBudak
Copy link

SvenBudak commented Jul 2, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Which Transloco package(s) will this feature affect?

Transloco, Persist Lang

Is your feature request related to a problem? Please describe

I want to add the current Lang to the route of my website like: domain.com/currentLang/blabla.

My idea was now to write a guard like this:

export class LanguageGuard {
  constructor(
    private translocoService: TranslocoService,
    private router: Router
  ) {}

  canActivate(): (
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ) => boolean | UrlTree {
    return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
      const currentLang = this.translocoService.getActiveLang();
      if (!state.url.startsWith(`/${currentLang}`)) {
        return this.router.parseUrl(`/${currentLang}${state.url}`);
      }
      return true;
    };
  }
}

And have a router like this:

export const appRoutes: Route[] = [
  {
    path: ':lang',
    canActivate: [() => inject(LanguageGuard).canActivate()],
    loadComponent: () =>
      import('./frames/frame-default/frame-default.component').then(
        (m) => m.FrameDefaultComponent
      ),
    children: [
      {
        path: '',
        loadComponent: () =>
          import('./views/view-home/view-home.component').then(
            (m) => m.ViewHomeComponent
          ),
      },
    ],
  },
  { path: '**', redirectTo: '/' },
];

And now i need to add the currentLang somehow to ALL of my routerLinks in my entire App:

<li><a [routerLink]="['/', currentLang, 'gallery']">{{ 'common.gallery' | transloco }}</a></li>

Because i dont want to inject in all my 1000 components the currentLang like

currentLang = this.translocoService.getActiveLang();

i thinked about using a global state manager. so i found Elf. Because i use the Persist module for transloco: https://jsverse.github.io/transloco/docs/plugins/persist-lang/ i ask my self now, if it makes maybe sense to ask you, to add Elf support for it? Something like useValue: Elf

Describe the solution you'd like

No response

Describe alternatives you've considered

And... I asked a year or two ago about a module that automatically prefixes the current language to every route. At that time, this request was rejected on the grounds that anyone could program this themselves with a few lines of code. However, I find this to be extremely cumbersome. Therefore, I would like to take this opportunity to ask again if you have changed your mind and would like to implement this feature at some point? To make all the problems mentioned here a thing of the past?

Additional context

No response

I would like to make a pull request for this feature

No

@shaharkazaz
Copy link
Collaborator

I'm not even sure what solution you are looking for/need TBH.

Because i dont want to inject in all my 1000 components the currentLang like

Why not create a new directive that does that for you? it's even easier now that Angular has HostDirectives

Regarding:

And... I asked a year or two ago about...

I also recall that you commented:

Sry but this is insane.... this makes the whole package useless...

As said many times before, this is an open-source project, if you want this feature you are welcome to open a PR 🙂
You can also try ngx-transloco-router, I'll add him to our 3rd party plugin section.

I'll consider pinning an issue to see if it gets enough attraction then maybe will finally open a PR for this instead of waiting for us.

@SvenBudak
Copy link
Author

Maybe it helps someone. I solvede it now in this way:

language.service.ts

import { Injectable } from '@angular/core';
import { Event, NavigationEnd, Router } from '@angular/router';
import { TranslocoService } from '@ngneat/transloco';
import { filter, take } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class LanguageService {
  constructor(
    private router: Router,
    private translocoService: TranslocoService
  ) {}

  public init() {
    this.router.events
      .pipe(
        filter(
          (e: NavigationEnd | Event): e is NavigationEnd =>
            e instanceof NavigationEnd
        ),
        take(1)
      )
      .subscribe((evt: NavigationEnd) => {
        if (evt.url === '/') {
          const sessionLang = this.getSessionLanguage();
          const preferredLang = this.getPreferredLanguage();
          const currentLang =
            sessionLang ||
            preferredLang ||
            this.translocoService.getActiveLang();
          this.router.navigate([currentLang]);
        }
      });
  }

  public storeSessionLanguage(lang: string) {
    sessionStorage.setItem('sessionLang', lang);
  }

  public getSessionLanguage(): string | null {
    return sessionStorage.getItem('sessionLang');
  }

  public storePreferredLanguage(lang: string) {
    localStorage.setItem('preferredLang', lang);
  }

  public getPreferredLanguage(): string | null {
    return localStorage.getItem('preferredLang');
  }

  public changeLanguage(lang: string) {
    this.translocoService.setActiveLang(lang);
    this.storePreferredLanguage(lang);
    this.updateUrlLang(lang);
  }

  updateUrlLang(lang: string) {
    const urlSegments = this.router.url.split('/');
    urlSegments[1] = lang;
    const newUrl = urlSegments.join('/');
    this.router.navigateByUrl(newUrl); // ensure the entire URL is considered
  }

  getLocalizedRoute(path: string | string[]): string[] {
    const currentLang = this.translocoService.getActiveLang();
    if (Array.isArray(path)) {
      return [`/${currentLang}`, ...path];
    } else {
      return [`/${currentLang}`, path];
    }
  }
}

language.guard.ts

import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, UrlTree } from '@angular/router';
import { TranslocoService } from '@ngneat/transloco';

import { LanguageService } from '../../../services/src';

export const LanguageGuard = (
  route: ActivatedRouteSnapshot
): boolean | Promise<boolean | UrlTree> => {
  const translocoService = inject(TranslocoService);
  const languageService = inject(LanguageService);

  const urlLang = route.params['lang'];
  const currentLang = translocoService.getActiveLang();

  if (urlLang && urlLang !== currentLang) {
    languageService.changeLanguage(urlLang);
  }

  return true; // allow navigation to continue
};

localize-route.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';
import { TranslocoService } from '@ngneat/transloco';

@Pipe({
  name: 'localizeRoute',
})
export class LocalizeRoutePipe implements PipeTransform {
  constructor(private translocoService: TranslocoService) {}

  transform(path: string | string[]): string[] {
    const currentLang = this.translocoService.getActiveLang();
    if (Array.isArray(path)) {
      return [`/${currentLang}`, ...path];
    } else {
      return [`/${currentLang}`, path];
    }
  }
}

app.router.ts

const routes: Routes = [
  {
    path: ':lang',
    canActivate: [LanguageGuard],
    ...

app.component.ts

constructor(private languageService: LanguageService) {
  this.languageService.init();
}

When I get a link to my website in another currentLang (for example, from a friend who speaks another language), that language is stored in the sessionStorage as sessionLang. When I leave the site and call it up again with domain.com, my preferredLang stored in localStorage is loaded again.

To keep the routerLinks working, I've written a pipe that prefixes every route with the currentLang: [routerLink]="'gallery' | localizeRoute"

This is all very cumbersome and the maintenance is not very good either, but at least it works. Maybe someone can use the code. Or even improve it and make a PR out of it.

@shaharkazaz shaharkazaz changed the title Feature(scope): Feature(transloco): Routes localization Jul 31, 2023
@tayambamwanza
Copy link

So with regard to this feature you are just waiting for PR contributions?

@SvenBudak
Copy link
Author

Now that SSR (Server-Side Rendering) has been brought to the forefront with Angular 17, this feature has become essential.

@shaharkazaz
Copy link
Collaborator

@tayambamwanza I have no objection to including this as a new package, as the label says, PRs are welcome.
I can't commit to a specific timeline since I'm currently in reserve duty and it's hard for me to even get to relevant bugs.

@tayambamwanza
Copy link

That's perfectly fine, I understand this is an open source library, what do you think of the example code posted earlier, would that suffice?

@shaharkazaz
Copy link
Collaborator

@tayambamwanza TBH I never needed this feature so I can't answer with a full heart.
Research must be done to understand all the common use cases and the best way to answer them. I'd start by seeing how ngx-translate-router is implemented to understand its pitfuls (see open issues, try to improve the API and usage)
BTW, did you try ngx-transloco-router? Where is the gap there?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants