0.0.13 • Published 2 years ago

langs-navigation v0.0.13

Weekly downloads
-
License
-
Repository
github
Last release
2 years ago

lang-navigation

Instalation

Please run next command in order to install the package:

npm i langs-navigation

Then, import NavigationModule using NavigationModule.forRoot({...}) static method:

NavigationModule.forRoot({ })

Overview

This library can be used if ngx-translate need to be used together with lang prefix in url. (f.e /en, /de/start).

The package allows to simplify navigation and to add:

  • autosetup langs/default lang/current lang for ngx-translate;
  • head title tag with default or custom value and automatically change it on page/lang change event;
  • meta tags for SEO optimization, and for Facebook preview and Twitter cards;
  • default or custom canonical links and automatically change it on page/lang change event;
  • default or custom html lang attribute and automatically change it on page/lang change event;
  • default redirects for known pages. F. e. if you have route .../en/start, but user typed in URL .../start - it will be redirected to the correct route;
  • a possibility to work with Angular Universal;
  • autocorrect lang cookie for the document to be able to return correct lang from server using Angular Universal;
  • navigate relatively through the app using navigate directive or service;
  • quick access to the set for current URL consts, such as lang, path, query string, fragment and build URL string for href attribute of the A tag using directive;
  • a lot of other things described below;

Sounds strange, but langs now is limited with next for values ONLY: de, en, nl and pl.

The package provides Lang as type and it's required to pass only language abbreviation for some of known above.

Input

Can be setuped next configuration input Partial<NavigationConfiguration>:

interface NavigationConfiguration {
  // all langs that can be used, need to specify here
  // it will setup langs for ngx-tranlate
  availableLangs: Lang[];

  // canonical links settings
  canonicalLinks: CanonicalLinks;

  // custom titles for the paths
  pathTitlesKeys: PathTitlesKeys;

  // custom lang attribute of the html tag for path
  htmlLangs: HtmlLangs;

  // will setup initLang for ngx-tranlate as well
  initLang: Lang;

  // main page of the webapp
  startPath: string;

  // error/not found path of the webapp
  notFoundPath: string;

  // this will be used for default head title
  appName: string;

  // the function to build the title
  titleBuilder: TitleBuilder;

  // set to true to enable logs
  isLoggingEnabled: boolean;

  // meta description will add a set of meta tags 
  twitterMeta: MetaDescription;
  facebookMeta: MetaDescription;
  defaultMeta: MetaDescription;
  
  // skip initialization
  skipInit: boolean;
}

If some propery is undefined then instead will be used default value:

const defaultMeta: MetaDescription = {
  builder: () => ({}),
  fallsbackLang: en,
  notTranlateableNames: []
};

const defaultConfiguration: NavigationConfiguration = {
  availableLangs: allLangs,
  canonicalLinks: {},
  appName: 'App',
  pathTitlesKeys: {},
  htmlLangs: {},
  initLang: en,
  notFoundPath: 'not-found',
  startPath: '',
  titleBuilder: ({ appName, currentPathTitle, currentLang }) => `${appName} | ${currentPathTitle} | ${currentLang.toUpperCase()}`,
  isLoggingEnabled: false,
  skipInit: false,

  facebookMeta: defaultMeta,
  twitterMeta: defaultMeta,
  defaultMeta
};

After all, webapp will receive access to the next services:

    NavigationService,
    CanonicalLinksService,
    MetaService,
    UrlService,
    LangsService,
    TranslateWithFallbackLangPipe,
    TranslationHelpService
    NavigateDirective,
    TranslateWithFallbackLangPipe,
    TranslateDirective,

Lets discuss them deeply.

NavigationService

The main service.

When NavigationModule is added, the init() method of the NavigationService will be executed. It will do next:

  • add langs, defaultLang and current lang for the ngx-translate;
  • set correct lang cookie;
  • sets default redirect, f.e if user typed /start, he/she will be redirect to /lang/start ;
  • add change lang subscriptions, to keep correct head actions (title, html lang, canonical link, meta tags);
  • set correct html tag attribute lang and change it on lang change event;

To set custom HTML lang attribute can be used htmlLangs langs input propery;

F. e., if you wish to inform search engine that some page will be always available in one language, whatever url lang is, then can be used next code in app.module.ts:

...
import { anyValue, langs, NavigationConfiguration } from 'langs-navigation';
const { de, en } = langs;
...

@NgModule({
  declarations: [
    ...
  ],
  imports: [
  ...
  NavigationModule.forRoot({
    ...
    htmlLangs: {
      'imprint': de,
      'privacy': {
        [anyValue]: en,
        [de]: de
      }
    },
    ...
  });
...
export class AppModule {}

Code above do next:

  • for page /lang/imprint need always set in HTML lang attribute value de whatever actual lang is now;
  • for page lang/privacy need to set page lang en for every actual languages except de.

The main feature of this service is the relative() method.

It allows to simplify navigation to the correct route. As the input it can be used NavigateInput interface:

interface VisitInput {
  nextLang?: Lang;
  isRedirect?: boolean;
  fragment?: string;
  queryParams?: Record<string, string>;
}

interface NavigateInput extends VisitInput {
  path?: string,
}

For example, if current path is /en/start or /de/start and need to be visited next prices page:

this.navigation.relative({path: 'prices'});

If need to open concrete lang:

this.navigation.relative({path: 'prices', nextLang: 'de' });

There is also an option isRedirect - means need to use same query params, fragment and current route.

It's need to use when it's required to pass query string, for example, and we have no idea the lang to use.

Also service has visit property and gives the possibility to visit start or not found pages.

...
import { NavigationService } from 'langs-navigation'
...
constructor(private navigation: NavigationService){ }
...
goToStartPage(){
  this.navigation.visit.start();
}
..

NavigationService is running entrypoints of the others services.

In order to skip entrypoint, and also for child components can be used .ForChild() static method

CanonicalLinksService

Purpose of the service is limited with it name.

It provides an access to the whole lifecycle of the canonical link - create => destroy => create.

Each time when page or lang are changed - previous canonical link is destroyed and new one is created.

Canonical link value will be the same as current URL, except path/lang match will be found inside input property.

F.e., if it's required to to have canonical link:

  • with de lang for path /imprint (.../de/canonical), whatever lang will be opened;
  • with en lang for path /privacy for every lang except de and keep it's own canonical link;

Then, can be used next code:

...
import { anyValue, langs, NavigationConfiguration } from 'langs-navigation';
const { de, en } = langs;
...

@NgModule({
  declarations: [
    ...
  ],
  imports: [
  ...
  NavigationModule.forRoot({
    ...
     canonicalLinks: {
      'imprint': { withLang: de },
      'privacy': { withLang: en, exceptLangs: de }
    },
    ...
  });
...
export class AppModule {}

Sometimes, can be need to specify special canonical link inside Angular Component of the webapp.

For this purpose cab be used method appendLangLink({ exceptLangs, path, withLang, subscribeLangChange }).

F. e. if user will open some not exist page and he/she is not redirected to not found path, then it is wise to add next code:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { CanonicalLinksService } from 'langs-navigation';

@Component({
  selector: 'app-not-found',
  templateUrl: './not-found.component.html',
  styleUrls: ['./not-found.component.scss']
})
export class NotFoundComponent implements OnInit, OnDestroy {
  constructor(
    private canonicalLinksService: CanonicalLinksService
  ) { }

  ngOnInit(): void {
    this.canonicalLinksService.appendLangLink({ path: 'not-found' });
  }

  ngOnDestroy(): void {
    this.canonicalLinksService.destroy();
  }
}

Code above will:

  • append canonical link with value /lang/not-found every time when NotFoundComponent is rendered on the page;
  • remove it when the component is destroyed;

MetaService

Responsible only for meta tags.

The main flow of this service is simple as a pie:

  • add meta tags for the opened page or changed lang according with the provided input settings;
  • remove meta tags that were created previously when page/lang changed;

There are three type of meta tags, that can be created by this service:

The difference between them is in the prefix used by service while creating tags.

  • no prefix <meta name=".." content="...">;
  • <meta name="twitter:.." content="...">;
  • <meta name="og:.." content="..."> - facebook;

To create meta tags need to provide the appropiate property values as the input.

The service expects to see 3 properties: facebookMeta, twitterMeta and defaultMeta.

The result of meta is a builder that gives an access to the current lang, title and path.

Builder should return key-value pairs with:

  • path from left side (or special consts: defaultValue or anyValue).
  • key-value pair from the right side with meta-tag-name:meta-tag-content-translation-key info.

Check next example:

...
import { markAsText, defaultValue, anyValue, langs, NavigationConfiguration } from 'langs-navigation';
const { de, en } = langs;
...

@NgModule({
  declarations: [
    ...
  ],
  imports: [
  ...
  NavigationModule.forRoot({
    ...
   facebookMeta: {
    builder: ({ lang, title, path }) => ({
      [defaultValue]: {
        // meta tag name: meta tag content
        'image': 'https://some-app.some-domain-prefix/assets/img/greeting.svg',
        'type': 'article',
        'url': `https://some-app.some-domain-prefix/${lang}/${path}`,
        'title': markAsText('I am a title'),
        'description': markAsText(`A brief description of the content, usually between 2 and 4 sentences.
                        This will displayed below the title of the post on Facebook. | ${title}`),

      },
      [anyValue]: {
        'image': `https://some-app.some-domain-prefix/assets/img/greeting.svg`,
        'type': 'article',
        'url': `https://some-app.some-domain-prefix/${lang}/${path}`,
        'title': markAsText(title),
        'description': '-description-translation-key',

      },
    }),
  },

  defaultMeta: {
    builder:
      ({ title, path, lang }) => ({
        // this is the case without lang, need to be specified text itself for main page, default lang
        [defaultValue]: { // main page without any lang
          'keywords': 'Some keywords',
          'description': 'Some text',
          'robots': 'index, follow'
        },
        [anyValue]: {
          'keywords': `meta.${path}.keywords`,
          'description': `meta.${path}.description`,
          'robots': 'index, follow'
        },
        '': { // main page /en /de /pl /nl
          'keywords': 'main-page-keywords-translation-key',
          'description': 'main-page-description-translation-key',
          'robots': 'index, follow'
        },
        // no need to index these pages
        'not-found': {
          'googlebot': 'no-index',
          'robots': 'no-index'
        },
        'privacy': {
          'googlebot': 'no-index',
          'robots': 'no-index'
        }
      }),
    ...
  });
...
export class AppModule {}

The code above will create tags in head section like next:

<meta name="keywords" content=...">
<meta name="description" content="...">
<meta name="robots" content="index, follow">

<meta name="og:image" content="...">
<meta name="og:type" content="article">
<meta name="og:url" content="...">
<meta name="og:title" content="...">
<meta name="og:description" content="...">

For the /privacy and /not-found paths default tags will be next:

<meta name="robots" content="no-index">
<meta name="googlebot" content="no-index">

<meta name="og:image" content="...">
<meta name="og:type" content="article">
<meta name="og:url" content="...">
<meta name="og:title" content="...">
<meta name="og:description" content="...">

The algorithm of parsing meta tags input is next:

  • if current path is provided - will be used it's meta tags configuration;
  • if current path is not provided will be used it's meta tags configuration under anyValue data;
  • if translations is not loaded at the moment of meta tag creation - will be used defaultValue data;
  • if translation is loaded for every tag content value the service will try to retrive translation;
  • if it's known that the content is text then can be used markAsText help method and service will skip searchig for translation;
  • append meta tags;

LangsService

Simplifies work with langs as entity.

Service gives access to the next properties:

  • langs;
  • initLang;
  • defaultLang;
  • browserLang;
  • onLangChange - event on lang is changed, result is current lang;
  • serverSideCookieLang - returns value for server side rendering process, is cookie.lang property of current request;
  • isLangDeChange - event, will be fired on every lang change, but result will be true only if lang is de
  • isLangEnChange - same as above but for en lang.
  • possibleLang - possible next lang - browserLang for browser and serverSideCookieLang for server;
  • isLang(possibleLang: string) - returns true if input string can be casted to Lang type.

UrlService

Gives nicer access to the current URL.

Service has next properties:

  • currentUrlPath;
  • currentUrlLang;
  • currentFragment and currentFragmentString (second adds '#' at start);
  • currentQueryParams and currentQueryParamsString - (first is Record<string, string> representaion of the query params and second if final query string);
  • currentUrlPathNice - transforms path to capitalized version with spaces. F.e. /en/start-page => "Start Page";
  • title - by default is the combination of ${appName} | ${currentPathTitle} | ${currentLang.toUpperCase() can be re-defined with titleBuilder input property;

Also, service has next public methods:

  • getRelativeUrlByPath({ path, fragment, nextLang, queryParams, isRedirect }: NavigateInput) - builds final relative URL by input params;
  • getQueryParamsString(queryParams?: Record<string, string>): string; builds query string
  • getFragmentString(fragment?: string): string

TranslationHelpService

Organizes work with ngx-translate.

Service provides next methods:

  • isTranslationLoaded(lang: Lang): boolean check is tranlation loaded for specified lang;
  • getTranslationDirectly(key: string, lang: Lang): string | undefined returns translation without subscription to change lang event, directly.
  • getTranslation(lang: Lang), getParsedResult(translations: any, key: any, interpolateParams?: Record<string, unknown>): any and ' onTranslationChange()' - are the same as TranlateService methods from the ngx-tranlate;
  • getTextOrInstant(text: string) - if input is text returns it, if it's translation key - returns .instant(key) result of the TranlateService methods from the ngx-tranlate;

TranslateWithFallbackLangPipe

Tries to translate text with current lang, - if it's not found - with fallbackLang;

As the input it accepts key: string, fallbackLang?: Lang.

The process of translation can be skipped, need to use previously described markAsText help method.

F. e., need to use en translation if current one is not available:

<span> {{'welcome-text' | navTranslateWithFallbackLang : 'en' | async}}</span>

NavigateDirective

Navigates by tag attributes and adds href attribute to the A tag.

Example usage:

<a navigate="test1"
  [isRedirect]="true"
  nextLang="de">
  Open test 1
</a>
 
<a navigate="test1"
  [queryParams]="{test2: 'true'}">
  Open test1 with params
</a>

<a navigate="test1" 
  fragment="frag" 
  [queryParams]="{test2: 'true'}">
  Open test 1 with fragment and params
</a>

<a navigate="test1"
  nextLang="en"
  fragment="frag" 
  [queryParams]="{test2: 'true'}">
  Open test 1 with nextLang
</a>

The code above will be transformed to next in browser, for de lang as current:

<a navigate="test1" 
  nextlang="de"
  href="/de/test1">
  Open test 1
</a>

<a navigate="test1" 
  href="/de/test1?test2=true">
  Open test 1 with params
</a>

<a navigate="test1" 
  fragment="frag"
  href="/de/test1?test2=true#frag">
  Open test 1 with fragment
</a>

<a navigate="test1" 
  nextlang="en" 
  fragment="frag" 
  href="/en/test1?test2=true#frag">
  Open test 1 with nextLang
</a>

But for the en lang result will be a lot different:

<a navigate="test1" 
  nextlang="de" 
  href="/de/test1"> 
  Open test 1 
</a>

<a navigate="test1" 
  href="/en/test1?test2=true">
  Open test1 with params 
</a>

<a navigate="test1" 
  fragment="frag"
  href="/en/test1?test2=true#frag"> 
  Open test 1 with fragment and params 
</a>

<a navigate="test1" 
  nextlang="en" 
  fragment="frag" 
  href="/en/test1?test2=true#frag"> 
  Open test 1 with nextLang
</a>

So from code examples above is obvious that if lang is nore specified in nextLang attribute then will be used current one.

BUT visible href will not be used when user will click on link!

The main cool thing is that the on link click, default href event will be prevented.

Instead, will be runned relative method from the NavigationService to keep single page app from the refreshing.

Above, is the reason why the name of attributes, that can be as input for directive, are matched with relative method input params.

TranslateDirective

Do the same as ngx-tranlate directive, but handles link clicks.

It's possible to use the same syntax as for NavigationDirective for links inside the text translations.

To do so, just need to use TranslateDirective instead of default translate pipe.

Example usage:

  // inside some json file with translations
  ...
  "process-text": "<p>... can be found <a navigate=\"page-path\" fragment=\"process\">here</a></p>....",
  ...

The link above will be catched and used by NavigationService as like it is default NavigationDirective usage.

Need only to translate it like this:

//some html template
...
<div nav-translate="process-text"></div>
...
0.0.10

2 years ago

0.0.11

2 years ago

0.0.12

2 years ago

0.0.13

2 years ago

0.0.9

2 years ago

0.0.8

2 years ago

0.0.5

2 years ago

0.0.4

2 years ago

0.0.7

2 years ago

0.0.6

2 years ago

0.0.3

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago