Git Product home page Git Product logo

ngx-translate-router's Introduction

ngx-translate-router

An implementation of routes localization for Angular.

  • ngx-translate-router npm version
  • ngx-translate-router-http-loader npm version
  • ngx-translate-router-scully-plugin npm version (Documentation here)

Fork of localize-router.

Based on and extension of ngx-translate.

Version to choose :

angular version translate-router http-loader type remarks
6 - 7 1.0.2 1.0.1 legacy
7 1.7.3 1.1.0 legacy
8 2.2.3 1.1.0 legacy
8 - 12 3.1.9 1.1.2 active
13 4.0.1 2.0.0 active
14 5.1.1 2.0.0 active need rxjs 7 or higher
15 6.0.0 2.0.0 active minimum angular 15.0.3
15.1 6.1.0 2.0.0 active minimum angular 15.1.0
16 7.0.0 2.0.0 active minimum angular 16
17 7.1.0 2.0.0 active optional standalone API
18 7.2.1 2.0.0 active

Demo project can be found under sub folder src.

This documentation is for version 1.x.x which requires Angular 6+. If you are migrating from the older version follow migration guide to upgrade to latest version.

Table of contents:

Installation

npm install --save @gilsdav/ngx-translate-router

Usage

In order to use @gilsdav/ngx-translate-router you must initialize it with following information:

  • Available languages/locales
  • Prefix for route segment translations
  • Routes to be translated

Initialize "module"

Module mode

import {LocalizeRouterModule} from '@gilsdav/ngx-translate-router'; Module can be initialized either using static file or manually by passing necessary values.

Be careful to import this module after the standard RouterModule and the TranslateModule. This should be done for the main router as well as for lazy loaded ones.

imports: [
  TranslateModule.forRoot(),
  RouterModule.forRoot(routes),
  LocalizeRouterModule.forRoot(routes) // <--
]

Standalone mode

Standalone mode is the new Angular API that allow you to manage your application without ng-modules.

This library provide an additional "RouterConfigurationFeature" called withLocalizeRouter you can provide to provideRouter Angular built-in function. Parameters for this function are exactly the same as for LocalizeRouterModule.forRoot().

Here is an example to configure it within an SSR app:

providers: [
  provideHttpClient(withFetch()),
  importProvidersFrom(TranslateModule.forRoot()),
  provideRouter(
    routes,
    withDisabledInitialNavigation(),
    withLocalizeRouter(routes, { // <--
      parser: {
        provide: LocalizeParser,
        useFactory: (createTranslateRouteLoader),
        deps: [TranslateService, Location, LocalizeRouterSettings]
      },
      initialNavigation: true
    })
  ),
  provideClientHydration()
]

You are also able to import LocalizeRouterPipe into your standalone components.

Http loader

In order to use Http loader for config files, you must include @gilsdav/ngx-translate-router-http-loader package and use its LocalizeRouterHttpLoader.

import {BrowserModule} from "@angular/platform-browser";
import {NgModule} from '@angular/core';
import {Location} from '@angular/common';
import {HttpClientModule, HttpClient} from '@angular/common/http';
import {TranslateModule} from '@ngx-translate/core';
import {LocalizeRouterModule} from '@gilsdav/ngx-translate-router';
import {LocalizeRouterHttpLoader} from '@gilsdav/ngx-translate-router-http-loader';
import {RouterModule} from '@angular/router';

import {routes} from './app.routes';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,
    TranslateModule.forRoot(),
    RouterModule.forRoot(routes),
    LocalizeRouterModule.forRoot(routes, {
      parser: {
        provide: LocalizeParser,
        useFactory: (translate, location, settings, http) =>
            new LocalizeRouterHttpLoader(translate, location, settings, http),
        deps: [TranslateService, Location, LocalizeRouterSettings, HttpClient]
      }
    })
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

More details are available on localize-router-http-loader.

If you are using child modules or routes you need to initialize them with forChild command:

@NgModule({
  imports: [
    TranslateModule,
    RouterModule.forChild(routes),
    LocalizeRouterModule.forChild(routes)
  ],
  declarations: [ChildComponent]
})
export class ChildModule { }

Manual initialization

With manual initialization you need to provide information directly:

LocalizeRouterModule.forRoot(routes, {
    parser: {
        provide: LocalizeParser,
        useFactory: (translate, location, settings) =>
            new ManualParserLoader(translate, location, settings, ['en','de',...], 'YOUR_PREFIX'),
        deps: [TranslateService, Location, LocalizeRouterSettings]
    }
})

Initialization config

Apart from providing routes which are mandatory, and parser loader you can provide additional configuration for more granular setting of @gilsdav/ngx-translate-router. More information at LocalizeRouterConfig.

Server side

In order to use @gilsdav/ngx-translate-router in Angular universal application (SSR) you need to:

  1. Initialize the module

  2. In case you opted for initializing with Http loader, you need to take care of static file location. @gilsdav/ngx-translate-router-http-loader by default will try loading the config file from assets/locales.json. This is a relative path which won't work with SSR. You could use one of the following approaches,

    1. Creating a factory function to override the default location with an absolute URL
      export function localizeLoaderFactory(translate: TranslateService, location: Location, settings: LocalizeRouterSettings, http: HttpClient) {
        return new LocalizeRouterHttpLoader(translate, location, settings, http, 'http://example.com/assets/locales.json');
      }
       
      LocalizeRouterModule.forRoot(routes, {
        parser: {
          provide: LocalizeParser,
          useFactory: localizeLoaderFactory,
          deps: [TranslateService, Location, LocalizeRouterSettings, HttpClient]
        }
      })
    2. Using an HTTP interceptor in your server.module to convert relative paths to absolute ons, ex:
      intercept(request: HttpRequest<any>, next: HttpHandler) {
        if (request.url.startsWith('assets') && isPlatformServer(this.platformId)) {
          const req = this.injector.get(REQUEST);
          const url = req.protocol + '://' + req.get('host') + '/' + request.url;
          request = request.clone({
            url: url
          });
        }
        return next.handle(request);
      }
  3. Let node server knows about the new routes:

    let fs = require('fs');
    let data: any = JSON.parse(fs.readFileSync(`src/assets/locales.json`, 'utf8'));
     
    app.get('/', ngApp);
    data.locales.forEach(route => {
      app.get(`/${route}`, ngApp);
      app.get(`/${route}/*`, ngApp);
    });
  4. In case you want to use cacheMechanism = CacheMechanism.Cookie you will need to handle the cookie in your node server. Something like,

    app.use(cookieParser());
    
    app.get('/', (req, res) => {
      const defaultLang = 'de';
      const lang = req.acceptsLanguages('de', 'en');
      const cookieLang = req.cookies.LOCALIZE_DEFAULT_LANGUAGE; // This is the default name of cookie
    
      const definedLang = cookieLang || lang || defaultLang;
    
      res.redirect(301, `/${definedLang}/`);
    });

Gotchas

  • In case you are using domino in your project, you will face the following error

    ERROR TypeError: Cannot read property 'indexOf' of undefined
    at TranslateService.getBrowserLang

    to overcome this, use the following:

    // language is readonly so normally you can't assign a value to it.
    // The following comments remove the compile time error and the IDE warning
    // @ts-ignore
    // noinspection JSAnnotator
    window.navigator.language = 'en';

Deal with initialNavigation

When you add Universal into your app you will have initialNavigation set to "enabled". This is to avoid the flickering of the lazy-load.

Unfortunatly it doesn't help with this library and can cause issues. So you need to set it to "disabled" and add the ngx-translate-router option initialNavigation: true to have this desired behavior.

imports: [
  RouterModule.forRoot(routes, { initialNavigation: 'disabled' }),
  LocalizeRouterModule.forRoot(routes, {
    ...
    initialNavigation: true
  })
]

How it works

@gilsdav/ngx-translate-router intercepts Router initialization and translates each path and redirectTo path of Routes. The translation process is done with ngx-translate. In order to separate router translations from normal application translations we use prefix. Default value for prefix is ROUTES.. Finally, in order to avoid accidentally translating a URL segment that should not be translated, you can optionally use escapePrefix so the prefix gets stripped and the segment doesn't get translated. Default escapePrefix is unset.

'home' -> 'ROUTES.home'

Example to escape the translation of the segment with escapePrefix: '!'

'!segment' -> 'segment'
{ path: '!home/first' ... } -> '/fr/home/premier'

Upon every route change @gilsdav/ngx-translate-router kicks in to check if there was a change to language. Translated routes are prepended with two letter language code:

http://yourpath/home -> http://yourpath/en/home

If no language is provided in the url path, application uses:

  • cached language in LocalStorage/SessionStorage/Cookie (browser only) or
  • current language of the browser (browser only) or
  • first locale in the config

Make sure you therefore place most common language (e.g. 'en') as a first string in the array of locales.

Note that ngx-translate-router does not redirect routes like my/route to translated ones e.g. en/my/route. All routes are prepended by currently selected language so route without language is unknown to Router.

Excluding routes

Sometimes you might have a need to have certain routes excluded from the localization process e.g. login page, registration page etc. This is possible by setting flag skipRouteLocalization on route's data object.

In case you want to redirect to an url when skipRouteLocalization is activated, you can also provide config option localizeRedirectTo to skip route localization but localize redirect to. Otherwise, route and redirectTo will not be translated.

let routes = [
  // this route gets localized
  { path: 'home', component: HomeComponent },
  // this route will not be localized
  { path: 'login', component: LoginComponent, data: { skipRouteLocalization: true } }
    // this route will not be localized, but redirect to will do
  { path: 'logout', redirectTo: 'login', data: { skipRouteLocalization: { localizeRedirectTo: true } } }
];

Note that this flag should only be set on root routes. By excluding root route, all its sub routes are automatically excluded. Setting this flag on sub route has no effect as parent route would already have or have not language prefix.

ngx-translate integration

LocalizeRouter depends on ngx-translate core service and automatically initializes it with selected locales. Following code is run on LocalizeParser init:

this.translate.setDefaultLang(cachedLanguage || languageOfBrowser || firstLanguageFromConfig);
// ...
this.translate.use(languageFromUrl || cachedLanguage || languageOfBrowser || firstLanguageFromConfig);

Both languageOfBrowser and languageFromUrl are cross-checked with locales from config.

Path discrimination

Do you use same path to load multiple lazy-loaded modules and you have wrong component tree ? discriminantPathKey will help ngx-translate-router to generate good component tree.

  {
    path: '',
    loadChildren: () => import('app/home/home.module').then(m => m.HomeModule),
    data: {
        discriminantPathKey: 'HOMEPATH'
    }
  },
  {
    path: '',
    loadChildren: () => import('app/information/information.module').then(m => m.InformationModule),
    data: {
        discriminantPathKey: 'INFOPATH'
    }
  }

WildCard Path

Favored way

The favored way to use WildCard ( '**' path ) is to use the redirectTo. It will let the user to translate the "not found" page message.

{
  path: '404',
  component: NotFoundComponent
},
{
  path: '**',
  redirectTo: '/404'
}
Alternative

If you need to keep the wrong url you will face to a limitation: You can not translate current page. This limitation is because we can not determine the language from a wrong url.

{
  path: '**',
  component: NotFoundComponent
}

Matcher params translation

Configure routes

In case you want to translate some params of matcher, localizeMatcher provides you the way to do it through a function per each param. Make sure that the key is the same as the one used in the navigate path (example: if the function returns "map", it must be contained in the not localized path: [routerLink]="['/matcher', 'aaa', 'map'] | localize") otherwise you will not be able to use routerLinkActiveOptions.

Example:

{
  path: 'matcher',
  children: [
    {
      matcher: detailMatcher,
      loadChildren: () => import('./matcher/matcher-detail/matcher-detail.module').then(mod => mod.MatcherDetailModule)
    },
    {
      matcher: baseMatcher,
      loadChildren: () => import('./matcher/matcher.module').then(mod => mod.MatcherModule),
      data: {
        localizeMatcher: {
          params: {
            mapPage: shouldTranslateMap
          }
        }
      }
    }
  ]
}

...

export function shouldTranslateMap(param: string): string {
  if (isNaN(+param)) {
    return 'map';
  }
  return null;
}

The output of the function should be falsy if the param must not be translated or should return the key (without prefix) you want to use when translating if you want to translate the param.

Notice that any function that you use in localizeMatcher must be exported to be compatible with AOT.

Small changes to your matcher

We work with UrlSegment to split URL into "params" in basic UrlMatchResult but there is not enough information to apply the translations.

You must use the LocalizedMatcherUrlSegment type to more strongly associate a segment with a parameter. It contains only the localizedParamName attribute in addition to basic UrlSegment. Set this attribute before adding the segment into consumed and posParams.

const result: UrlMatchResult = {
  consumed: [],
  posParams: { }
};

...

(segment as LocalizedMatcherUrlSegment).localizedParamName = name;
result.consumed.push(segment);
result.posParams[name] = segment;
Matcher params translated without localizeMatcher issue

If the URL is accidentally translated from a language to another which creates an inconsistent state you have to enable escapePrefix mechanism. (example: escapePrefix: '!')

RedirectTo with function

Starting in Angular 18 introduced redirectTo as a function in addition to a string. If you use this feature, you can use the redirectTo function to translate the path. However, the library translates the entire router at the start of the application statically, which means that the translate function has no yet the navigation context of the user in that moment as it should be evaluated dynamically once the user navigates to such route. If you want to use this feature, you can use the LocalizeRouterService to translate the path injecting the service as in this example.

{
  path: 'conditionalRedirectTo', redirectTo: ({ queryParams }) => {
    const localizeRouterService = inject(LocalizeRouterService);
    if (queryParams['redirect']) {
      return localizeRouterService.translateRoute('/test') as string;
    }
    return localizeRouterService.translateRoute('/home') as string;
  }
}

Pipe

LocalizeRouterPipe is used to translate routerLink directive's content. Pipe can be appended to partial strings in the routerLink's definition or to entire array element:

<a [routerLink]="['user', userId, 'profile'] | localize">{{'USER_PROFILE' | translate}}</a>
<a [routerLink]="['about' | localize]">{{'ABOUT' | translate}}</a>

Root routes work the same way with addition that in case of root links, link is prepended by language. Example for german language and link to 'about':

'/about' | localize -> '/de/über'

Service

Routes can be manually translated using LocalizeRouterService. This is important if you want to use router.navigate for dynamical routes.

class MyComponent {
    constructor(private localize: LocalizeRouterService) { }

    myMethod() {
        let translatedPath: any = this.localize.translateRoute('about/me');
       
        // do something with translated path
        // e.g. this.router.navigate([translatedPath]);
    }
}

AOT

In order to use Ahead-Of-Time compilation any custom loaders must be exported as functions. This is the implementation currently in the solution:

export function localizeLoaderFactory(translate: TranslateService, location: Location, http: Http) {
  return new StaticParserLoader(translate, location, http);
}

API

LocalizeRouterModule

Methods:

  • forRoot(routes: Routes, config: LocalizeRouterConfig = {}): ModuleWithProviders: Main initializer for @gilsdav/ngx-translate-router. Can provide custom configuration for more granular settings.
  • forChild(routes: Routes): ModuleWithProviders: Child module initializer for providing child routes.

LocalizeRouterConfig

Properties

  • parser: Provider for loading of LocalizeParser. Default value is StaticParserLoader.
  • useCachedLang: boolean. Flag whether default language should be cached. Default value is true.
  • alwaysSetPrefix: boolean. Flag whether language should always prefix the url. Default value is true.
    When value is false, prefix will not be used for for default language (this includes the situation when there is only one language).
  • cacheMechanism: CacheMechanism.LocalStorage || CacheMechanism.SessionStorage || CacheMechanism.Cookie. Default value is CacheMechanism.LocalStorage.
  • cacheName: string. Name of cookie/local store. Default value is LOCALIZE_DEFAULT_LANGUAGE.
  • defaultLangFunction: (languages: string[], cachedLang?: string, browserLang?: string) => string. Override method for custom logic for picking default language, when no language is provided via url. Default value is undefined.
  • cookieFormat: string. Format of cookie to store. Default value is '{{value}};{{expires}}'. (Extended format e.g : '{{value}};{{expires}};path=/')
    • {{value}} will be replaced by the value to save (CACHE_NAME=language). Must be present into format.
    • {{expires}} will be replaced by expires=currentDate+30days. Optional if you want session cookie.
      • you can configure the number of expiration days by using this synthax: {{expires:365}}. It will result as expires=currentDate+365days.
    • results to : LOCALIZE_DEFAULT_LANGUAGE=en;expires=Wed, 11 Sep 2019 21:19:23 GMT.

LocalizeRouterService

Properties:

  • routerEvents: An EventEmitter to listen to language change event
localizeService.routerEvents.subscribe((language: string) => {
    // do something with language
});
  • parser: Used instance of LocalizeParser
let selectedLanguage = localizeService.parser.currentLang;

Methods:

  • translateRoute(path: string | any[]): string | any[]: Translates given path. If path starts with backslash then path is prepended with currently set language.
localizeService.translateRoute('/'); // -> e.g. '/en'
localizeService.translateRoute('/about'); // -> '/de/ueber-uns' (e.g. for German language)
localizeService.translateRoute('about'); // -> 'ueber-uns' (e.g. for German language)
  • changeLanguage(lang: string, extras?: NavigationExtras, useNavigateMethod?: boolean): Translates current url to given language and changes the application's language. extras will be passed down to Angular Router navigation methods. userNavigateMethod tells localize-router to use navigate rather than navigateByUrl method.
    For german language and route defined as :lang/users/:user_name/profile
yoursite.com/en/users/John%20Doe/profile -> yoursite.com/de/benutzer/John%20Doe/profil

Hooks:

For now there is only one hook which is only interesting if you are using initialNavigation flag (and more specifically, if you are making an AppInitializer that uses Angular's Router or ngx-translate-router).

  • hooks.initialized: an observable with event sent when initialNavigation is executed.

Usage example:

export const appInitializerFactory = (injector: Injector) => {
  return () => {
    const localize = injector.get(LocalizeRouterService);
    return firstValueFrom(
      localize.hooks.initialized
        .pipe(
          tap(() => {
            const router = injector.get(Router);
            router.events.pipe(
              filter(url => url instanceof NavigationEnd),
              first()
            ).subscribe((route: NavigationEnd) => {
              console.log(router.url, route.url);
              router.navigate(['/fr/accueil']);
            });
          })
        )
      )
  }
};

LocalizeParser

Properties:

  • locales: Array of used language codes
  • currentLang: Currently selected language
  • routes: Active translated routes
  • urlPrefix: Language prefix for current language. Empty string if alwaysSetPrefix=false and currentLang is same as default language.

Methods:

  • translateRoutes(language: string): Observable<any>: Translates all the routes and sets language and current language across the application.
  • translateRoute(path: string): string: Translates single path
  • getLocationLang(url?: string): string: Extracts language from current url if matching defined locales

License

Licensed under MIT

Thanks

Thanks to all our contributors

As well as to all the contributors of the initial project Made with contributors-img.

ngx-translate-router's People

Contributors

ahasall avatar dgilsonafelio avatar giacomo avatar gilsdav avatar likle avatar osamafelfel avatar ruizmarc avatar vrady avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ngx-translate-router's Issues

formatQueryParams when param is array

I have an issue when a query param is array, for example id=1&id=2, the new query params after language change becomes id=1,2.
I think the issue is releated to formatQueryParams in localize-router.parser.ts.
Perhaps something like this could resolve this?

public formatQueryParams(params: Params): string {
return new HttpParams({ fromObject: params }).toString();
}

Lazy Routes children dont translate

Hello we've go a problem, lazy routes children are not translated and dont event get params from activatedRoute. Try to add translations for
src/app/test2/test-routing.module.ts for path sarah :D

i investigate it and at file localize-router.parser you have method _translateRouteTree and there is condition
if (route.loadChildren && (<any>route)._loadedConfig) { this._translateRouteTree((<any>route)._loadedConfig.routes); }

and this _loadedConfig is missing at angular8

Route mutation problem when using NGRX

When using the ManualParserLoader in combination with ivy rendering engine, the following Error occurs on switching between languages:

TypeError: Cannot assign to read only property 'path' of object '[object Object]'
TypeError: Cannot assign to read only property 'path' of object '[object Object]'
at ManualParserLoader.push.../../node_modules/@gilsdav/ngx-translate-router/ivy_ngcc/fesm5/gilsdav-ngx-translate-router.js.LocalizeParser._translateProperty

Four letter language code

Can I use four letter language code instead two?

http://yourpath/en-US/home
http://yourpath/pt-BR/home
http://yourpath/pt-PT/home

[email protected] Fail running tests

Running my testsuite i got following error:

 TypeError: Cannot read property 'alwaysSetPrefix' of undefined
      at <Jasmine>
      at DummyLocalizeParser.get urlPrefix [as urlPrefix] (http://localhost:9876/_karma_webpack_/node_modules/@gilsdav/ngx-translate-router/__ivy_ngcc__/fesm2015/gilsdav-ngx-translate-router.js:257:1)
      at DummyLocalizeParser.addPrefixToUrl (http://localhost:9876/_karma_webpack_/node_modules/@gilsdav/ngx-translate-router/__ivy_ngcc__/fesm2015/gilsdav-ngx-translate-router.js:270:20)
      at LocalizeRouterService.translateRoute (http://localhost:9876/_karma_webpack_/node_modules/@gilsdav/ngx-translate-router/__ivy_ngcc__/fesm2015/gilsdav-ngx-translate-router.js:641:1)

Default language issue

Hi there, first of all thanks for forking Greentube/localize-router and getting it to work on Angular 8+

We've been using your package while upgrading our whole system for a couple of months now and we've encountered an issue regarding setting the default language.

We would like to have some routes translated to 'nl-BE' and all other routes need to stay at 'nl-NL' so the 'nl-BE' file doesn't have all translations, we wanted those to be handled by the default 'nl-NL' language.

However, because the package sets the default language in the translateService and uses that default language to translate the routes as well we can't use this stragegy.

It would be nice to have some kind of 'preferredLanguage' (don't really know a better word for it at the moment) or an option where we can set the cultureLang inside the localizeRouterSettings so we could do something like this:

/**
 * Factory for localize router parser
 *
 * @param translate
 * @param location
 * @param settings
 *
 * @returns the localize router loader
 *
 * @author Roy Freij <[email protected]>
 * @version 1.0.0
 */
export function localizeLoaderFactory(
    translate: TranslateService,
    location: Location,
    settings: LocalizeRouterSettings,
): LocalizeRouterStaticLoader {
    return new LocalizeRouterStaticLoader(translate, location, {
        ...settings,
        alwaysSetPrefix: false,
        useCachedLang: false,
        defaultLangFunction: () => environment.langCode
        cultureLang: () => {
            if (translate.getBrowserCultureLang() === 'nl-BE') {
                return 'nl-BE';
            }
        },
    });
}

If this already is possible please let me know.

Cookies issue in Angular 7, SSR and domino

I'm using your package with Angular 7 in SSR mode. I was facing an issue related to cookies and I thought it's related to the package itself until I got a feedback that the package works with SSR ( feedback was given on #31 ).

After some investigation I found the root cause is domino package. Domino is mocking the document object during SSR and when you try accessing document.cookie the result will be an exception as follow:

ERROR Error: NotYetImplemented
at Document.exports.nyi (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:2496:9)
at ManualParserLoader.LocalizeParser._cacheWithCookies (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:217918:64)
at ManualParserLoader.get [as _cachedLang] (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:217845:29)
at ManualParserLoader.LocalizeParser.init (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:217537:85)

So basically the error is being thrown from ngx-translate-router/src/lib/localize-router.parser.ts

  private _cacheWithCookies(value?: string): string {
    if (typeof document === 'undefined' || typeof document.cookie === 'undefined') {
      return;
    }

As far as I understand, you were depending on the fact that document won't be available during SSR. I believe something like the following condition will do the same trick and will behave nicely with domino.

if (this.platformService.isBrowser === false || typeof document === 'undefined' || typeof document.cookie === 'undefined')

I understand that this is not an issue in your package but a lot of SSR projects are using domino and the case will be repetitive. Is it possible to do that change?

SSR Angular Universal sometimes getting HTTP 404 errors

When I use Angular Universal and send concurrent requests, I receive multiple 404 errors. Translations are loaded via HTTP and I use initialNavigation via this package only. The function translateText tries to translate the route, but _translationObject is sometimes not defined. As far as I can see, there are no significant differences between my code and what is described in the documentation.

LocalizeRouterModule + BrowserTransferStateModule + TransferHttpCacheModule = ERROR: Cannot instantiate cyclic dependency. Injection token HTTP_INTERCEPTORS.

Hi.
Here is one bug from the original repository, which I met in your fork.
Greentube/localize-router#99

Can you fix it?

Since I don't use any interceptors in my APP, so the problem comes from the mentioned modules:
LocalizeRouterModule
BrowserTransferStateModule
TransferHttpCacheModule

The app was working before I added these 2 modules:
BrowserTransferStateModule
TransferHttpCacheModule

After these modules were added - I've started to receive the mentioned error:
Cannot instantiate cyclic dependency. Injection token HTTP_INTERCEPTORS.

[REQUEST] make COOKIE_EXPIRY configurable / customizable

Some pages require a longer living cookie expiry time.

Since we made the cookie customizable we can also add a possibility to change the expiry time.

At the moment.

const COOKIE_EXPIRY = 30; // 1 month

@gilsdav What do you think about? Could i start to work on it?

Not transition on change language

url: /ru/home -> tranisiton to pl -> pl/home/home

core.js:1624 ERROR Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'pl/home/home'

var /** @type {?} */ url = _this.traverseRouteSnapshot(rootSnapshot_1); returned pl/home/home :(

Help, please 💃

RoutesEvents should be triggered

Using Angular 8, subscribing to routerEvents doesn't work as expected.

      // should be triggered on every language change
      this.localize.routerEvents.subscribe((language: string) => {
        // actually never called
        console.log(language);
      });

caused by:

  /**
   * Event handler to react on route change
   */
  private _routeChanged(): (eventPair: [NavigationStart, NavigationStart]) => void {
    return ([previousEvent, currentEvent]: [NavigationStart, NavigationStart]) => {
      const previousLang = this.parser.currentLang; // this.parser.getLocationLang(previousEvent.url) || this.parser.defaultLang;
      const currentLang = this.parser.getLocationLang(currentEvent.url) || this.parser.defaultLang;
      if (currentLang !== previousLang) {
        this.parser.translateRoutes(currentLang).subscribe(() => {
          this.router.resetConfig(this.parser.routes);
          // Init new navigation with same url to take new congif in consideration
          this.router.navigateByUrl(currentEvent.url, { replaceUrl: true });
          // Fire route change event
          this.routerEvents.next(currentLang);
        });
      }
    };
  }

the condition on every call currentLang !== previousLang is false

I'm working on a solution, but it requires some more tests.

redirectTo: "/" does not work

The property redirectTo is producing an invalid url when the desired result is the root (including language).
When / is provided the result is lang/ which is not a valid url.

Imagine a route tree where the accepted routes are:

/en/element/:id
/en

With the following config /en/element is not accepted and redirected to root:

const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    redirectTo: '/'
  },
  {
    path: ':id',
    component: ElementComponent
  }
];

To fix this trailing slash .replace(/\/$/, '') needs to be added to the following function:

   /**
   * Translate route and return observable
   */
  translateRoute(path: string): string {
    const queryParts = path.split('?');
    if (queryParts.length > 2) {
      throw Error('There should be only one query parameter block in the URL');
    }
    const pathSegments = queryParts[0].split('/');

    /** collect observables  */
    return pathSegments
      .map((part: string) => part.length ? this.translateText(part) : part)
      .join('/').replace(/\/$/, '') +
      (queryParts.length > 1 ? `?${queryParts[1]}` : '');
  }

Chrome, Safari and Firefox remove the trailing slash by default on a brand new navigation.

Can't navigate to sibling routes

I'm trying to integrate the package, it works for almost all routes except when I try to navigate to a sibling route. The url change, but I'm not redirected to the wanted view. I can't figure what I'm doing wrong. Here is my configuration :

view-login.routing.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { LocalizeRouterModule } from '@gilsdav/ngx-translate-router';
import { TranslateModule } from '@ngx-translate/core';

import { LOGIN_METADATA } from '../../common/routing/route-metadata';
import { RoutingKeys } from '../../common/routing/routing-keys';
import { ViewLoginComponent } from './view-login.component';

const routes: Routes = [
  {
    path: '',
    component: ViewLoginComponent,
    children: [
      {
        path: RoutingKeys.loginAuth,
        loadChildren: 'app/views/view-login/view-authenticate/view-authenticate.module#ViewAuthenticateModule',
        data: { metadata: LOGIN_METADATA },
      },
      {
        path: RoutingKeys.loginLocal,
        loadChildren: 'app/views/view-login/view-login-local/view-login-local.module#ViewLoginLocalModule',
        data: {
          hiddenBottomBar: true,
          metadata: LOGIN_METADATA,
        },
      },
      {
        path: RoutingKeys.loginForgottenPassword,
        loadChildren:
          'app/views/view-login/view-forgotten-password/view-forgotten-password.module#ViewForgottenPasswordModule',
        data: {
          hiddenBottomBar: true,
        },
      },
    ],
  },
];

@NgModule({
  imports: [
    RouterModule.forChild(routes),
    LocalizeRouterModule.forChild(routes),
    TranslateModule.forChild(),
  ],
  exports: [
    RouterModule,
    LocalizeRouterModule
  ]
})
export class ViewLoginRouting {}

view-login.component.html:

<div class="glob-grid-simpleCol"><router-outlet></router-outlet></div>

view-authenticate.routing.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { LocalizeRouterModule } from '@gilsdav/ngx-translate-router';
import { TranslateModule } from '@ngx-translate/core';

import { Chapter1, Chapter2, ScreenName } from '../../../shared/at-internet/at-internet.enum';
import { ViewAuthenticateComponent } from './view-authenticate.component';

const routes: Routes = [
  {
    path: '',
    component: ViewAuthenticateComponent,
    data: {
      chapter1: Chapter1.LOGIN,
      chapter2: Chapter2.LOGIN,
      screenName: ScreenName.SIGN_UP_OR_LOG_IN,
    },
  },
];

@NgModule({
  imports: [
    RouterModule.forChild(routes),
    LocalizeRouterModule.forChild(routes),
    TranslateModule.forChild(),
  ],
  exports: [
    RouterModule,
    LocalizeRouterModule
  ]
})
export class ViewAuthenticateRouting {}

view-authenticate.component.html:

<div class="view-authenticate">
  <img alt="icon-mascotte" [src]="ILLUSTRATION_MASCOTTE" />
  <span class="glob-font-title-3 view-authenticate-title">
    {{ 'tooltip_bananas_modification_new_users_title' | translate }}
  </span>

  <span class="view-authenticate-subtitle">{{ 'connection_homepage_body' | translate }}</span>

  <atom-facebook-login-button></atom-facebook-login-button>

  <atom-apple-sign-in></atom-apple-sign-in>

  <div class="view-authenticate-link-container">
    <a class="view-authenticate-link" [routerLink]="'/' + routingKeys.register | localize">
      {{ 'connection_homepage_account_creation' | translate }}</a
    >
    <a class="view-authenticate-link" [routerLink]="['/', routingKeys.login, routingKeys.loginLocal] | localize" [state]="item"> {{ 'login_connection_action' | translate }}</a>
  </div>

  <div class="view-authenticate-cgu">
    {{ 'onboarding_cgu' | translate }}
    <a class="view-authenticate-cgu-link" [href]="'official_cgu' | translate" target="_blank">
      {{ 'link_cgu' | translate }}
    </a>
    {{ 'onboarding_cgu_1' | translate }}
    <a class="view-authenticate-cgu-link" [href]="'official_privacy_policy' | translate" target="_blank">
      {{ 'link_privacy' | translate }} </a
    >.
  </div>
</div>

view-login-local.routing.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { LocalizeRouterModule } from '@gilsdav/ngx-translate-router';
import { TranslateModule } from '@ngx-translate/core';

import { Chapter1, Chapter2, ScreenName } from '../../../shared/at-internet/at-internet.enum';
import { ViewLoginLocalComponent } from './view-login-local.component';

const routes: Routes = [
  {
    path: '',
    component: ViewLoginLocalComponent,
    data: {
      chapter1: Chapter1.LOGIN,
      chapter2: Chapter2.LOGIN,
      screenName: ScreenName.LOGIN,
    },
  },
];

@NgModule({
  imports: [
    RouterModule.forChild(routes),
    LocalizeRouterModule.forChild(routes),
    TranslateModule.forChild(),
  ],
  exports: [
    RouterModule,
    LocalizeRouterModule
  ]
})
export class ViewLoginLocalRouting {}

So when I'm on the view-authenticate and click on the view-login-local link, I can see the url change, but I'm not redirected to the view-login-local, however, if I reload the page, I reach the wanted view.
Hope I was enough clear and you'll can help me.
Regards

ERROR Error: Arguments array must have arguments

Hi. I tried to use this package. Angular 8.2.7

I have issue when i trying to inject LocalizeRouterService in any component or service:

From Injectable service:
MainBrowserComponent_Host.ngfactory.js? [sm]:1 ERROR Error: Arguments array must have arguments. at injectArgs (core.js:809) at core.js:16345 at _callFactory (core.js:30485) at _createProviderInstance (core.js:30428) at resolveNgModuleDep (core.js:30387) at NgModuleRef_.get (core.js:31577) at resolveDep (core.js:32142) at createClass (core.js:31995) at createDirectiveInstance (core.js:31806) at createViewNodes (core.js:44209)

From component:
compiler.js:2175 Uncaught Error: Can't resolve all parameters for LangComponent: ([object Object], ?). at syntaxError (compiler.js:2175) at CompileMetadataResolver._getDependenciesMetadata (compiler.js:20399) at CompileMetadataResolver._getTypeMetadata (compiler.js:20294) at CompileMetadataResolver.getNonNormalizedDirectiveMetadata (compiler.js:19923) at CompileMetadataResolver.loadDirectiveMetadata (compiler.js:19786) at compiler.js:25829 at Array.forEach (<anonymous>) at compiler.js:25828 at Array.forEach (<anonymous>) at JitCompiler._loadModules (compiler.js:25825)

I founded issue: https://stackoverflow.com/questions/52767225/error-arguments-array-must-have-arguments-appmodule
But it doesn't relative for my code..

Update peer dependecy

Trying to ng update @gilsdav/ngx-translate-router from 2.0.2 to 2.0.3

Package "@gilsdav/ngx-translate-router-http-loader" has an incompatible peer dependency to "@gilsdav/ngx-translate-router" (requires "^1.0.2", would install "2.0.3").

[email protected] has incorrect peer dependencies

@gilsdav got some issues using Angular 9 with your package.

warning " > @gilsdav/[email protected]" has incorrect peer dependency "@ngx-translate/core@^11.0.1".
warning " > @gilsdav/[email protected]" has incorrect peer dependency "@angular/common@^8.0.0".
warning " > @gilsdav/[email protected]" has incorrect peer dependency "@angular/core@^8.0.0".
warning " > @gilsdav/[email protected]" has incorrect peer dependency "@angular/router@^8.0.0".
warning " > @gilsdav/[email protected]" has incorrect peer dependency "@ngx-translate/core@^11.0.1".

I would use "@ngx-translate/core": "^12.1.1",

and the ngx-translate-router should be updated as well to "angular 9".

Thanks.

localize pipe is not working?

Hi, am using it wrong, or it is not working?

  <a [routerLink]="'/manual' | localize">
    Manual page
  </a>

This do append a language code, something like: exampe.com/ru/manual
But it does not translate the '/manual' string itself.

If I use it like this:

  <a [routerLink]="'/manual' | translate | localize">
    Manual page
  </a>

then it translates the '/manual' to appropriate string:
example.com/ru/руководство

But in this case the link is broken (the page is not opening), likely because it is defined like this:

  {
    path: 'manual',
    loadChildren: () =>
      import('./pages/manual/manual.module').then((m) => m.ManualModule),
  },

angular 9 ivy errors

hello, i tried to use this in angular 9 (~9.0.0-next.8) with IVY enabled and we've got a problem ;d

app build okey (you need to add @Injectable to localize-router.service), and when in console you will see

ERROR TypeError: Cannot read property 'state' of undefined at LocalizeRouterPipe.transform (localize-router.pipe.ts:50) at ɵɵpipeBind1 (core.js:35382) at AppComponent_Template (template.html:5) at executeTemplate (core.js:12584) at refreshView (core.js:12443) at refreshComponent (core.js:13731) at refreshChildComponents (core.js:12178) at refreshView (core.js:12502) at renderComponentOrTemplate (core.js:12556) at tickRootContext (core.js:13895)

and the code for this is:
// if view is already destroyed, ignore firing change detection if ((<any>this._ref)._view.state & VIEW_DESTROYED_STATE) { return this.value; }

probably the same problem exist with angular 8 ivy enabled.

ERROR TypeError: Cannot read property 'indexOf' of undefined

I'm trying to use this package with Angular 7 project. Everything is working fine when running on browser mode (none SSR). When trying to run it with SSR, I face the below error

ERROR TypeError: Cannot read property 'indexOf' of undefined
at TranslateService.getBrowserLang (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:219761:25)
at LocalizeUniversalLoader.LocalizeParser._getBrowserLang (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:217620:55)
at LocalizeUniversalLoader.LocalizeParser.init (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:217369:32)
at SafeSubscriber._next (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:134620:23)
at SafeSubscriber.__tryOrUnsub (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:74744:16)
at SafeSubscriber.next (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:74682:22)
at Subscriber._next (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:74625:26)
at Subscriber.next (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:74602:18)
at MapSubscriber._next (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:77433:26)
at MapSubscriber.Subscriber.next (/Users/osamafelfel/work/idea_projects/letsfund-management-localize/dist/server.js:74602:18)

I believe this is related to not having a language when running on server. I'm thinking of a workaround by setting the language from node side but I believe there should be a better solution.

I'm using the below versions,
"@gilsdav/ngx-translate-router": "^1.0.2",
"@gilsdav/ngx-translate-router-http-loader": "^1.0.0",

and here is my Parser loader

export class LocalizeUniversalLoader extends LocalizeParser {

  private httpClient: HttpClient;
  private injector: Injector;
  private platformId: Object;

  constructor(translate: TranslateService, location: Location, settings: LocalizeRouterSettings,
              injector: Injector, platformId: Object, httpClient: HttpClient) {

    super(translate, location, settings);
    this.injector = injector;
    this.platformId = platformId;
    this.httpClient = httpClient;
  }
  /**
   * Gets config from the server
   */
  public load(routesForTranslation: Routes): Promise<any> {
    let url = '';
    if (isPlatformServer(this.platformId)) {
      const request = this.injector.get(REQUEST);
      url = request.protocol + '://' + request.get('host');
    } else if (isPlatformBrowser(this.platformId)) {
      const location = window.location;
      url = location.protocol + '//' + location.host;
    }

    console.log(url);

    return new Promise((resolve: any) => {
      this.httpClient.get<{ locales: Array<string>, prefix: string}>(url + '/assets/locales/locales.json').subscribe(
        (data: any) => {
          this.locales = data.locales;
          this.prefix = data.prefix;
          this.init(routesForTranslation).then(resolve);
        }
      );
    });
  }
}

P.S: I tried a demo project with the same configuration and it doesn't have this issue and I can't understand the reason. https://github.com/SrgSteak/angular7-universal-translate

Routing breaks when using discriminantPathKey and skipRouteLocalization

I've been trying to debug an issue for hours and I've finally found out what the issue is.

The GilsdavReuseStrategy has a getKey() method, that seems to return undefined in all circumstances when skipRouteLocalization is not used. I'm using regular path keys, instead of a matcher, causing it to always return undefined for my routes. Because undefined === undefined is obviously true, it thinks it should reuse a component thus it won't load the new component.

By manually returning false in the shouldReuseRoute method I can get the routing to work correctly, but this has the side effect that it'll keep reloading all components. I'm not sure how to write logic properly so that it works without localization.

Compilation warning: require function is used in a way in which dependencies cannot be statically extracted

Hi there,

Since we migrate from localize-router to your (best) library we start to get this warning:

WARNING in node_modules/@angular/compiler/src/util.js 10:24-31
Critical dependency: require function is used in a way in which dependencies cannot be statically extracted

We checked and the error is on the line projects/ngx-translate-router/src/lib/localized-router.ts:8. The import of isPromise is wrong.

Should be replace for an alternative.

More info:
https://stackoverflow.com/questions/51319209/critical-dependency-require-function-is-used-in-a-way-in-which-dependencies-can

Alternatives:
https://stackoverflow.com/questions/27746304/how-do-i-tell-if-an-object-is-a-promise/38339199#38339199

[REQUEST]: Extend LocalizeRouterConfig properties to save Cookie only with global Path

Feature-Request:
@gilsdav As you already know I'm working on a page that uses your package to be able to translate routes.

Since we have recurring users we save the selected language through the cookie.
Using this package i see a strange behaivour. The Cookie is set to each single route.

Why not using LocalStorage?

because ssr cant access to it.

As shown in this image the multiple cookies:
grafik

The problem when a user opens different pages in more tabs and we fallback to the cookie language - he should see different languages through the tabs..

He should see all tabs in the prefered cookie language - and the package should save only one cookie for a domain.

Caused by:

document.cookie = `${name}=${encodeURIComponent(value)};expires=${d.toUTCString()}`;

Solution:

  • Adding a property LocalizeRouterConfig to enable this behaivour if needed.

Resulting:
grafik

HTML href from component.ts

My environment:
`
Angular CLI: 8.3.25
Node: 12.16.1
OS: win32 x64
Angular: 8.2.14
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package Version

@angular-devkit/architect 0.803.25
@angular-devkit/build-angular 0.803.25
@angular-devkit/build-optimizer 0.803.25
@angular-devkit/build-webpack 0.803.25
@angular-devkit/core 8.3.25
@angular-devkit/schematics 8.3.25
@angular/cli 8.3.25
@ngtools/webpack 8.3.25
@schematics/angular 8.3.25
@schematics/update 0.803.25
rxjs 6.5.4
typescript 3.5.3
webpack 4.39.2

`

I have this markup in my *.component.ts:

let url = this.localizeRouter.translateRoute('/url.a/url.b/' + row.id);
return '<a data-action="link" href="${url}" class="cell-action">${data}</a>';

In my HTML href route is translated href="en/test1/test2/123" but when I try to navigate there in my console I get:
core.js:6014 ERROR Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'en/English/test1/test2/123' Error: Cannot match any routes. URL Segment: 'en/English/test1/test2/123' at ApplyRedirects.noMatchError (router.js:4295) at CatchSubscriber.selector (router.js:4259) at CatchSubscriber.error (catchError.js:29) at MapSubscriber._error (Subscriber.js:75) at MapSubscriber.error (Subscriber.js:55) at MapSubscriber._error (Subscriber.js:75) at MapSubscriber.error (Subscriber.js:55) at MapSubscriber._error (Subscriber.js:75) at MapSubscriber.error (Subscriber.js:55) at ThrowIfEmptySubscriber._error (Subscriber.js:75) at resolvePromise (zone-evergreen.js:797) at resolvePromise (zone-evergreen.js:754) at zone-evergreen.js:858 at ZoneDelegate.invokeTask (zone-evergreen.js:391) at Object.onInvokeTask (core.js:39680) at ZoneDelegate.invokeTask (zone-evergreen.js:390) at Zone.runTask (zone-evergreen.js:168) at drainMicroTaskQueue (zone-evergreen.js:559) at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:469) at invokeTask (zone-evergreen.js:1603)

I don't know why there is an extra English word.
Any ideas?

Thanks.

Instructions unclear on translating routes with parameters

Hi,

I'm currently not sure how I translate certain routes that contain parameters. I have the following setup:

// app.module.ts

const appRoutes: Routes = [{
	path: "",
	component: MainComponent,
	children: [{
		path: "",
		component: SidebarComponent,
		children: [{
			path: "",
			component: CitiesListComponent
		}, {
			path: ":cityId/locations",
			component: LocationsListComponent
		}]
	}]
}];

@NgModule({
	declarations: [
		AppComponent
	],
	imports: [
		// ...
		TranslateModule.forRoot({
			loader: {
				provide: TranslateLoader,
				useFactory: (createTranslateLoader),
				deps: [HttpClient]
			},
			compiler: {
				provide: TranslateCompiler,
				useClass: TranslateMessageFormatCompiler
			}
		}),
		LocalizeRouterModule.forRoot(appRoutes, {
			parser: {
				provide: LocalizeParser,
				useFactory: (translate, location, settings) => {
					return new ManualParserLoader(translate, location, settings, ["nl", "en"]);
				},
				deps: [TranslateService, Location, LocalizeRouterSettings]
			}
		}),
		RouterModule.forRoot(appRoutes),
		// ...
	],
	providers: [],
	bootstrap: [AppComponent]
})
export class AppModule {
	constructor() {
		
	}
}

I'm trying to get the translated route, but the :cityId parameter obviously should not be translated. I might plan to support slugs in the future, so might need some explanation on how to translate it using data that I manually want to provide, however, I currently try to navigate using this:

const translatedPath: any = this.localize.translateRoute([city.id, "locations"]);
this.router.navigate(translatedPath);

This prompts me with an error:

core.js:5871 ERROR Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: '157/locations'
Error: Cannot match any routes. URL Segment: '157/locations'
    at ApplyRedirects.noMatchError (router.js:4334)
    at CatchSubscriber.selector (router.js:4298)
    at CatchSubscriber.error (catchError.js:29)
    at MapSubscriber._error (Subscriber.js:75)
    at MapSubscriber.error (Subscriber.js:55)
    at MapSubscriber._error (Subscriber.js:75)
    at MapSubscriber.error (Subscriber.js:55)
    at MapSubscriber._error (Subscriber.js:75)
    at MapSubscriber.error (Subscriber.js:55)
    at ThrowIfEmptySubscriber._error (Subscriber.js:75)
    at resolvePromise (zone-evergreen.js:793)
    at resolvePromise (zone-evergreen.js:752)
    at zone-evergreen.js:854
    at ZoneDelegate.invokeTask (zone-evergreen.js:400)
    at Object.onInvokeTask (core.js:41249)
    at ZoneDelegate.invokeTask (zone-evergreen.js:399)
    at Zone.runTask (zone-evergreen.js:168)
    at drainMicroTaskQueue (zone-evergreen.js:570)
    at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:485)
    at invokeTask (zone-evergreen.js:1596)

What am I doing wrong here?

Open discussion about new use cases

Summary

  • Go back to previous language after switching language
  • Allow localize redirect of a localize-skipped route
  • Skip the localization of a segment of the route
  • Matchers need to be localized when switching language
  • Skip translation but add language on routes

Intro

This is a quite special issue as it aims to open a discussion based on our use cases and our experience with route localization to help make your library an even more powerful solution.

A year and a half ago we started using localize-router. However, after some time using it we found a few use cases that were not covered by the library. Our first intention was to open a pull request to contribute to the library, but at that moment it seemed that the localize-router was far from being maintained as there were a few important and necessary unattained pull-requests. For this reason, we finally incorporated the project into ours and develop solutions for the different use cases we had that were not covered.

After upgrading to Angular 8 with the new lazy loaded routes we found that the absorbed library was not longer working properly neither in client side nor SSR, and furthermore it was not compatible with Ivy.

While we were looking for solutions, we end up finding your forked library. We think that you have done a very good job and it seems that you are actively maintaining it. For this reason, we thought that instead of fixing our own evolution of localize-router, it would be much better to share with you our different use cases so we can help to make and even more powerful route localization library and even to share or contribute with the approaches we took to solve them.

Use cases

Allow localize redirect of a localize-skipped route

We found that we needed to redirect some routes to other localized routes. Redirect was working properly, but we had some routes which we didn’t want to localize and we activated the option skipRouteLocalization. However, activating this, also skipped the localization of the route which we were redirecting to.

Our solution was to add a new option in router data butLocalizeRedirectTo to localize the redirectTo if needed. Having this capability is a must have for us.

Matchers need to be localized when switching language (and they may have localized parameters)

In some cases, we need to localize a certain part of a matcher, but localize router was not able to translate those matchers when switching languages.

Our solution was to add some extra info about the matcher in data, which included the parameters and whether or not or how a parameter should be dealt with.

In the following example, the matcher has 5 params and the mapPage if it is not a number, we need to translate it based on the key we are providing in the config. Having this capability is a must have for us.

data: {
  localizeRouter: {
    matcher: {
      params: ['country', 'state', 'city', 'street', 'mapPage'],
      config: {
        mapPage: {
          condition: 'NOT_NUMBER',
          key: 'MAP'
        }
      }
    }
  }
}

Skip the localization of a segment of the route

In some cases, we want to avoid the localization of a route segment, so for example, we avoid to accidentally localize a route parameter.

Our solution was to create a new prefix that we were including at the start of those segments we didn’t want to translate. When when found the IGNORE_ prefix, instead of translating the key we striped the prefix and left the rest. It is not a requirement, but it would be very nice to have.

Go back to previous language after switching language

When the user switches the language and press back in the browser, the route localizer crashes.

We didn’t find a good solution to this problem and it is not a really a must have for us, but it would be nice to find a solution.

Illustrative matchers example

We have prepared an illustrative example about the matcher thing in the following repo:
https://github.com/Nekronik/angular-vrwvgb

How it works

There are 2 matchers that lazy load modules

All the posible combinations are displayed

The combinations are grouped by which module should load

What is happening

Reuse strategy is reusing the loaded module even when the other one should be loaded (reload and it works to prove it is not working)

When the language is changed the route state is lost and erroneous
/fr/matcher/aaa/bbb/ccc/12345678 -> /en/matcher/matcher

The map parameter should be translated (it is not possible to test since url state is lost)


We hope this discussion helps to bring your library forward and make it even more useful to more people to become the router localization reference. Count on us! 😃

pre and post language change events

Hello Gilsdav,

We currently face the problem where we need to change url params depending on the selected language. It seems that the router service "changeLanguage" function overwrites our changes because it always triggers last. Is there a way to know when the router navigate from the service is done? Something like a PostLanguageChange Event?

Best regards,
Robin

Error while translating on the home page (path empty)

Hi Gilsdav,

I'm getting an error "Error: Cannot match any routes. URL Segment" when I try to translate the main route in my app.

Route config :

const routes: Routes = [
    { path: '', loadChildren: '../home/home.module#HomeModule', pathMatch: 'full' },
    { path: 'product-evaluation', loadChildren: '../product-evaluation/product-evaluation.module#ProductEvaluationModule' },
    { path: '**', redirectTo: '/' }
];

When I'm on the route "product-evaluation," I have no problem to translate and navigating to the translated URL with the function "changeLanguage()".

Angular version :

Angular CLI: 7.3.6
Node: 10.15.3
OS: darwin x64
Angular: 7.2.9
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.13.6
@angular-devkit/build-angular     0.13.6
@angular-devkit/build-optimizer   0.13.6
@angular-devkit/build-webpack     0.13.6
@angular-devkit/core              7.3.6
@angular-devkit/schematics        7.3.6
@angular/cli                      7.3.6
@ngtools/webpack                  7.3.6
@schematics/angular               7.3.6
@schematics/update                0.13.6
rxjs                              6.3.3
typescript                        3.2.4
webpack                           4.29.0

It seems that the route does'nt know the new config with the selected language when trying to translate.

I don't know if there's a possible fix or if I must change my config.

Thank you.

Standardize library, releases and processes

I think that several files and processes should be added to make this library stronger and standard.

  • Changelog
  • Automated Relase flow
  • GitHub releases
  • GitHub templates to open issues and make PR
  • GitHub actions to build, test and lint the library
  • How to contribute section
  • Add package manager lockfile #27
  • Git commit convention and linter
  • Create https://stackblitz.com to report issues
  • ...

I can do some of these actions because I did it in other libraries.

Preloaded routes are not translated

Hi,
I've updated version from 3.0.0 to 3.1.2 and some routes are not translated.
After some investigation, I've discovered that preloaded routes with PreloadingStrategy are not translated.
When a lazy module with routes is loaded, the routes are translated in

return localize.initChildRoutes([].concat(...getResult));

But when a lazy module with routes is preloaded not calls the initChildRoutes method. The entering point when a module is preloaded in the router library is https://github.com/angular/angular/blob/1f0c1f3ff2170d6a333897f5d9a89e3857a4e5f8/packages/router/src/router_preloader.ts#L132

    return this.preloadingStrategy.preload(route, () => {
      const loaded$ = this.loader.load(ngModule.injector, route);
      return loaded$.pipe(mergeMap((config: LoadedRouterConfig) => {
        route._loadedConfig = config;
        return this.processRoutes(config.module, config.routes);
      }));
    });
  }

Removing preloadingStrategy: PreloadAllModules in router configuration works as expected.

Minimal reproduction

https://stackblitz.com/github/jonnyprof/ngx-translate-router-preload

Steps to reproduce:
0- Go to home or login page
1- Click to login -> forgot password. It returns a 404
2- If you reload the page it works fine

If you comment (or remove) preloadingStrategy from src/app/app-routing.module.ts it works as expected.

Explanation:

The routes from PasswordModule haven't been translated and compares 'RESET' with 'reset' in defaultUrlMatcher method. As there is no matcher it returns a 404 route.

Versions:
@angular/*: 9.1.12
@gilsdav/ngx-translate-router": 3.1.2

Could add Country iso code (Alpha-3 or Alpha-2) in url?

Could I Add iso country code (Alpha-3 or Alpha-2) to url prefix for initiate country definition /en-US/home or /en_US/home instead of /en/home, when I initiate user language locale?

Example like this:
this.localize.changeLanguage('en-US'); or this.localize.changeLanguage('en', 'United States');
Expected results:
Alpha-3: /en-USA/home or /en_USA/home
Alpha-2: /en-US/home or /en_US/home

GilsdavReuseStrategy resolves wrong routes

We have quite big application with some lazy routing like:

  {
    path: '',
    loadChildren: () => import('app/home/home.module').then(m => m.HomeModule)
  },
  {
    path: '',
    loadChildren: () => import('app/information/information.module').then(m => m.InformationModule)
  }

And in the lazy modules we have a different layout/component, such as:

export const homeRoutes: Routes = [
  {
    path: '',
    component: HomeComponent,
    children: [
      {
        path: 'SECTION1',
        component: Section1Component
      },
      {
        path: 'SECTION1',
        component: Section2Component
      }
    ]
  }
];
...
const informationRoutes: Routes = [
  {
    path: '',
    component: InformationComponent,
    children: [
      {
        path: 'ABOUT_US',
        component: AboutUsComponent
      },
      {
        path: 'FAQS',
        component: FaqsComponent
      },
      {
        path: 'HOW_IT_WORKS',
        component: HowItWorksComponent
      }
    ]
  }
];

The problem is that when we navigate from an empty path to another empty path takes the wrong route (and component). For example, takes InformationComponent with section1Component from home, and the layout is broken.

I've found that if change GilsdavReuseStrategy.shouldReuseRoute for the DefaultRouteReuseStrategy (return future.routeConfig === curr.routeConfig;) works correctly.

I couldn't figure out what is the purpose of GilsdavReuseStrategy, so I'm not sure how to fix it.

Thanks and congratulations for your great job.

Can't seem to translate anything!

Hello!

So, I followed the instructions but I couldn't get the routing translations to work...

I installed the packages as mentioned and followed the instructions as best as I could.
This is currently my code:

app.routes.ts

export const appRoutes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    component: HomeComponent
  },
  {
    path: 'home',
    component: HomeComponent
  },
  {
    path: 'cart',
    component: ShoppingCartComponent
    //outlet: 'shopping'
  },
  { path: '**', component: PageNotFoundComponent }
];

app.module.ts

import { appRoutes } from './app.routes';

// Normal Translations
export function createTranslateLoader(http: HttpClient) {
  return new TranslateHttpLoader(http);
}

// Router Translations
export function HttpLoaderFactory(
  translate: TranslateService,
  location: Location,
  settings: LocalizeRouterSettings,
  http: HttpClient
) {
  return new LocalizeRouterHttpLoader(
    translate,
    location,
    { ...settings, alwaysSetPrefix: true },
    http,
    '../assets/i18n/locales.json'
  );
}

...

@NgModule({
  imports: [
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: createTranslateLoader,
        deps: [HttpClient]
      }
    }),
    RouterModule.forRoot(appRoutes),
    LocalizeRouterModule.forRoot(appRoutes, {
      parser: {
        provide: LocalizeParser,
        useFactory: HttpLoaderFactory,
        deps: [TranslateService, Location, LocalizeRouterSettings, HttpClient]
      }
    })
  ]
})

assets/i18n/locales.json

{
  "locales": ["pt", "en"]
}

assets/i18n/pt.json

{
  "CART": {
    "CART": "cesto",
    "BACK": "voltar",
    "QTY": "Quantidade",
    "TOTAL": "Total",
    "EMPTY": "O seu cesto encontra-se vazio!"
  },
  "ROUTES.home": "inicio",
  "ROUTES.cart": "cesto",
  "ROUTES.shopping": "compras"
}

app.component.ts

import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  constructor(private translate: TranslateService) {
    translate.addLangs(['pt', 'en']);
    translate.setDefaultLang('pt');

    const browserLang = translate.getBrowserLang();
    translate.use(browserLang.match(/pt/) ? browserLang : 'en');
  }
}

Common translations using ngx-translation work just fine, I get the cart strings translated. But my routes don't change.
Also the prefix is always pt, even when I change my browser language to English and logging the browserLang variable prints 'en'.

Hope you guys could help me out.

Thanks in advance!
Regards

Navigation out of ** path is not working

I have a weird behavior which I can't understand. I have the following routes

const routes: Routes =
  [
    {
      path: '',
      component: ShellComponent,
      children:
        [
          {
            path: '',
            component: HomeComponent,
            data: {key: 1}
          },
          {
            path: 'features',
            redirectTo: 'features/website/customize',
          },
          {
            path: 'features/website',
            redirectTo: 'features/website/customize',
          },
          {
            path: 'features/dashboard',
            redirectTo: 'features/dashboard/insights',
          },
          {
            path: 'features/website/:feature',
            component: FeaturesComponent,
          },
          {
            path: 'features/dashboard/:feature',
            component: FeaturesComponent,
          },
          {
            path: 'pricing',
            component: PricingComponent,
          },
        ]
    },
    {
      path: '**',
      component: NotFoundComponent,
      data: {key: 6}
    }
  ];

Whenever I land on NotFoundComponent, all links stop working. The URL in the address bar is being changed but the actual navigation is not happening. I'm not sure how this would be related to this package but when I revert it everything works normally.

How do you suggest carrying on the investigation on this? I'm suspecting the ** along with the double empty path at the beginning but I couldn't find the actual root cause.

AoT converts cacheMechanism namespace constant to null

In my app I changed the default cache mechanism from local storage to cookies.

I did that in my code with the constant as described in the README:

LocalizeRouterModule.forRoot(ROUTES, {
  cacheMechanism: CacheMechanism.Cookie,
  cookieFormat: `{{value}}; {{expires}}; Domain=${environment.cookieUrl}`,
  parser: {
    provide: LocalizeParser,
    useFactory: localizeRouterParserFactory,
    deps: [TranslateService, Location, LocalizeRouterSettings]
  }
})

However, in AoT builds, the constant is turned into null, as seen in the minified output:

['ɵmpd'](512, v.Location, v.Location, [
v.LocationStrategy,
v.PlatformLocation
]),
i['ɵmpd'](256, o.p, void 0, [
]),
i['ɵmpd'](256, o.a, void 0, [
]),
i['ɵmpd'](256, o.b, null, [
]),
i['ɵmpd'](256, o.c, void 0, [
]),
i['ɵmpd'](256, o.e, void 0, [
]),
i['ɵmpd'](256, o.d, '{{value}}; {{expires}}; Domain=.example.com', [
]),

And with a debugging breakpoint in get_cachedLang(), I can see that this.settings has the following:

Object { useCachedLang: true, alwaysSetPrefix: true, cacheMechanism: null, cacheName: "LOCALIZE_DEFAULT_LANGUAGE", defaultLangFunction: undefined, cookieFormat: "{{value}}; {{expires}}; Domain=.example.com" }

By changing the value to a string (also provides IntelliSense in VS Code), the problem is fixed.

LocalizeRouterModule.forRoot(ROUTES, {
  cacheMechanism: 'Cookie',
  cookieFormat: `{{value}}; {{expires}}; Domain=${environment.cookieUrl}`,
  parser: {
    provide: LocalizeParser,
    useFactory: localizeRouterParserFactory,
    deps: [TranslateService, Location, LocalizeRouterSettings]
  }
})

I wonder if this is either a bug in Angular's AoT compilation, or that the use of a TypeScript namespace to define the constant is the issue? Either way, maybe the README should suggest to use a string instead?

ngx-translate-router: 2.2.2
Angular: 8.2.14
TypeScript: 3.5.3

Thanks for this amazing library, David!

Parameters instructions unclear

Routes definition:

export const routes: Routes = [
  {
    path: 'invite/!:intTag/!:userTag',
    canActivate: [InterviewGuard, LangGuard],
    component: InviteComponent,
  },
  {
    path: 'setup',
    canActivate: [AfterInviteGuard, LangGuard],
    component: BeforeComponent,
  },
  {
    path: 'interview',
    canActivate: [AfterInviteGuard, LangGuard],
    component: BaseComponent,
  },
  {
    path: 'error',
    component: ErrorComponent,
    canActivate: [LangGuard],
  },

  { path: '', redirectTo: '/error', pathMatch: 'full' },
];

Language file:

{
"ROUTES": {
    "error": "error",
    "interview": "interview",
    "invite": "invite",
    "invite/:intTag/:userTag": "invite/:intTag/:userTag",
    ":intTag": ":intTag",
    ":userTag": ":userTag",
    "setup": "setup"
  },
}

Code I try to run: console.log(localizeService.translateRoute('/invite/aa/bb'));
Response: /en/invite/`

If I try to go to http://localhost:4202/en/invite/aa/bb, it says Cannot match any routes. URL Segment: 'en/invite/aa/bb'

Routes such as /setup, /error works without a proble, the problem is caused by these 2 dynamic parts of route.

Any solution?

Issues with lazy loaded routes

Hi,

I've some issues when using this library with lazy loaded routes.

I have 2 routes : /lazy-one-level and /lazy-two-levels/child and two languages fr and en (default). Everything in en works fine. But in fr it's not working well :
/fr/lazy-one-level (the route is in fact translated) can be loaded if I come after the application have been initialized (from Home) but it's not found when the application initializes it.

/fr/lazy-two-levels/child (the route is in fact translated) it's never found.

Reproduction : https://stackblitz.com/edit/angular-9-localize-router-lazy

Thanks for helping !

ERR! code E404

Cannot install

npm i @gilsdav/ngx-translate-router
npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/@gilsdav%2fngx-translate-router - Not found
npm ERR! 404
npm ERR! 404 '@gilsdav/ngx-translate-router@^3.0.1' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.

npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\user\AppData\Roaming\npm-cache_logs\2020-08-06T11_15_47_296Z-debug.log

The value of routerLink does not change in the AppComponent (Ionic)

Hello,

Thank you for your library and the work you do.

I use ionic with angular, I noticed that in the side menu which is loaded in the app.component.html does not change the value of the routerLink.

<ion-app>
   <ion-split-pane contentId="main-content">
      <ion-menu contentId="main-content" type="overlay">
         <ion-header>
            <ion-toolbar>
               <ion-title>Menu</ion-title>
            </ion-toolbar>
         </ion-header>
         <ion-content>
            <ion-list lines="none">

               <ion-menu-toggle auto-hide="false" *ngFor="let page of appPages">
                  <ion-item [routerLinkActive]="'active'"
                  [routerLink]="page.url | localize" <!--the value does not change-->
                  [routerDirection]="'root'">
                  <ion-icon slot="start" [name]="page.icon"></ion-icon>
                  <ion-label>
                     {{page.title | translate}} <!--returns the right value-->
                     {{page.url | localize}} <!--returns the right value-->
                  </ion-label>
                  </ion-item>
               </ion-menu-toggle>

            </ion-list>
         </ion-content>
      </ion-menu>
      <ion-router-outlet id="main-content"></ion-router-outlet>
   </ion-split-pane>
</ion-app>

how to deal with old links?

In my app, I want to keep old links works so the users open /home it's should redirect him to en/home
how to do that?

[Questions] Angular 7 & SSR

@gilsdav I have several questions regarding the package and it would be great if you have sometime to answer them.

  1. As per the README, we should use v1.0.2 with Angular 6 & 7. It seems that version 1 is failing behind in terms of features/fixes. For example, fix for issue #22 has been applied for v2 only. Another example would be cookieFormat which is only available for v2.
    • Are you planning to continue the work on v1?
    • Can we make v2 compatible with Angular 7? I tried it with Angular 7 and it's working fine except for an exception which doesn't affect the functionality.
  2. Are you planning to make the package compatible with SSR? For example, the cookies solution must handle the SSR case. Currently the cookies are not being used during server rendering.
  3. Finally, why did you Fork localize-router on the first place? Both packages provide almost same functionalities.

Add package manager lock file

Must be added the yarn.lock or package-lock.json file to prevent errors developing.

What package manager you use?

We use yarn and for us is faster than npm.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.