langs-navigation
v0.0.13
Published
Please run next command in order to install the package: ``` npm i langs-navigation ```
Downloads
4
Readme
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 exceptde
.
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 exceptde
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:
- default meta tags;
- Facebook preview;
- Twitter card;
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
oranyValue
). - 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, iscookie.lang
property of current request;isLangDeChange
- event, will be fired on every lang change, but result will be true only if lang isde
isLangEnChange
- same as above but foren
lang.possibleLang
- possible next lang -browserLang
for browser andserverSideCookieLang
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
andcurrentFragmentString
(second adds '#' at start);currentQueryParams
andcurrentQueryParamsString
- (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 withtitleBuilder
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 stringgetFragmentString(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 asTranlateService
methods from thengx-tranlate
;getTextOrInstant(text: string)
- if input is text returns it, if it's translation key - returns.instant(key)
result of theTranlateService
methods from thengx-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>
...