This library is an extension for Transloco that provides support for displaying translations with markup.
ngx-transloco-markup offers an alternative to the Transloco directive and pipe: the <transloco>
component, that takes care of rendering your translations with markup.
By using this component, you no longer need to split your translations or use an [innerHtml]
-binding.
This allows for a much simpler syntax in your translation files and you no longer need to worry about potential markup-injection issues that could cause the layout of your application to break.
While this library ships with support for the most common markup use cases, you might wish to create your own customized markup rendering. Fortunately, thanks to the extensible architecture, you can do this quite easily.
- Installation
- Getting started
<transloco>
component API- Defining markup transpiler availability
- Contextual links
- Creating your own markup transpilers
- Further customization
Since this library is an extension for Transloco, first make sure your application has been configured to use transloco. If this is not the case follow the easy installation instructions from Transloco.
Next you'll need to install the ngx-transloco-markup
package from NPM:
npm install --save ngx-transloco-markup
If you are using the Angular CLI to build and test your application, then this all you need to do for the installation. For custom build setups: the library is published in the Angular Package Format, so it provides several distribution bundles that are compatible with most build tools.
Use the compatibility matrix below to determine which version of this module works with your project's Angular version.
Library version | Angular version | Transloco version |
---|---|---|
ngx-transloco-markup - 1.x.x |
>= 8.0.0 | @ngneat/transloco - 2.x.x |
ngx-transloco-markup - 2.x.x |
>= 10.0.0 | @ngneat/transloco - 2.x.x |
ngx-transloco-markup - 3.0.x |
>= 13.0.0 | @ngneat/transloco - 3.x.x |
ngx-transloco-markup - 3.1.x |
>= 13.0.0 | @ngneat/transloco - 3.x.x + 4.x.x |
ngx-transloco-markup - 4.x.x |
>= 14.0.0 | @ngneat/transloco - 4.x.x |
ngx-transloco-markup - 5.x.x |
>= 16.0.0 | @ngneat/transloco - 5.x.x |
ngx-transloco-markup - 5.2.x |
>= 16.0.0 | @ngneat/transloco - 5.x.x + 6.x.x |
ngx-transloco-markup - 6.x.x |
>= 17.0.0 | @jsverse/transloco - 7.x.x |
After having installed the ngx-transloco-markup
package you can now add support for translation markup rendering to your application.
A quick word about the architecture needed here.
This library depends on so called translation markup transpilers.
These small units are responsible for parsing the translation values and converting them into rendering functions.
For the <transloco>
component (provided by this library) you need to specify which transpilers will be used and at what level.
In this section we're just going to cover a simple setup that works for most applications.
Keep in mind, however, that you can completely customize the transpiler configuration.
That will be covered in later sections.
First let's add the default transpilers to the root module of your application (usually called AppModule
):
import { defaultTranslocoMarkupTranspilers } from 'ngx-transloco-markup';
@NgModule({
providers: [
defaultTranslocoMarkupTranspilers() // <-- Add this line to the providers array
]
})
export class AppModule { }
This registers the default transpilers and makes them globally available wherever you use the <transloco>
component.
When including the default transpilers, the following markup is supported:
- Bold text:
[b]...[/b]
- Italic text:
[i]...[/i]
- Links:
[link:parameterKey]...[/link]
, whereparameterKey
should be replaced with a key of the translation parameter that contains the URL or link object. Note that there is a better alternative to defining links in your translation values, by making use of contextual link tokens.
Next, you will need to add the TranslocoMarkupModule
to the module(s) in which you want to use the <transloco>
component:
import { TranslocoMarkupModule } from 'ngx-transloco-markup';
@NgModule({
imports: [
TranslocoMarkupModule // <-- Add this line to the imports array
]
})
export class ExampleModule { }
Alternatively you can import the TranslocoMarkupComponent
directly in standalone components:
import { Component } from '@angular/core';
import { TranslocoMarkupComponent } from 'ngx-transloco-markup';
@Component({
// ...
standalone: true,
imports: [
TranslocoMarkupComponent, // <-- Add this line to the imports array
],
})
export class MyComponent { }
Once the TranslocoMarkupModule
or TranslocoMarkupComponent
has been imported, you will be able to use the <transloco>
component in your templates.
As an example, suppose your (English) translation file contains the following entry:
{
"GREETING": "Hello [b]{{ name }}[/b], please visit my [link:website]website[/link]"
}
When the GREETING
entry is rendered by the <transloco>
component, it will display the text and renders the value of the name
parameter in a bold font.
Also, the website text will be rendered as a link that points to the value of the website
translation parameter.
The code snippet below shows an example of how this would be used in a component.
@Component({
selector: 'app-example',
template: `
<transloco
[key]="'GREETING'"
[params]="{ name: firstName + ' ' + lastName, website: 'https://www.example.com/' }"
></transloco>
`
})
export class ExampleComponent {
public firstName = 'John';
public lastName = 'Doe';
}
The <transloco>
component has the following input properties:
-
key
(string)Key that defines which translation value should be displayed. If no key is specified or the key is
undefined
, then thecontent
property will be used instead. When that property is also unspecified nothing will be displayed by the<transloco>
component. -
content
(string)Pre-translated text to display. Still applies the markup tags, but doesn't do any translation (except possibly for string interpolation expressions, e.g.
{{ SOME_TRANSLATION_KEY }}
). This property will be ignored if a translationkey
is specified. -
params
(HashMap)An object containing the translation parameters, which will be used to expand interpolation expressions (
{{ paramKey }}
). -
lang
(string)Language in which the text should be displayed. Usually you do not need to specify this, unless you want to override the default language. The default language is either the current language of the
TranslocoService
, or the language that was specified using theTRANSLOCO_LANG
injection token. -
scope
(string)Scope which is to be used. If this property is specified it will override the scope provide via the
TRANSLOCO_SCOPE
injection token. -
transpilers
(TranslationMarkupTranspiler | TranslationMarkupTranspiler[])Transpilers that will be available for rendering the translation value. These are merged with transpilers provided via dependency injection, unless the
mergeTranspilers
option is set tofalse
(see below). When the transpilers are merged, inline providers will take precedence over the provided transpilers. -
mergeTranspilers
(boolean)Specifies whether the inline transpilers are merged with the provided transpilers. When set to
false
, only the inline transpilers will be used, unless no inline transpilers have been specified. Defaults totrue
.
As mentioned before, one of the features of ngx-transloco-markup
is that you have full control over which transpilers will be available and at what level.
This allows you to limit the markup options to just what is necessary within a particular context.
The getting started section demonstrated how to make the default transpilers available everywhere in the application by providing them in the application root module. If you do not wish for a transpiler to be available everywhere, but only for a part of the application you can specify it at a different level:
-
inline - the narrowest scope at which a transpiler can be made available. Pass the transpiler instance to the
transpilers
input property of the<transloco>
component. This will make the transpiler available just for that particular use of the<transloco>
component. -
component - defined as part of the providers array of a component, e.g.:
@Component({ selector: 'app-fancy-something' template: 'A very [rainbow]colorful[/rainbow] text!', providers: [ provideTranslationMarkupTranspiler(RainbowTextTranspiler) ] }) export class FancyComponent { }
When a transpiler is defined in this way, all usages of the
<transloco>
component will support the transpiler within the context of theFancyComponent
context. This includes both the template of the component itself, but also for all child components that might be using the<transloco>
component. -
lazy-loaded module - defined in the providers array of a lazy loaded module. This will make the transpiler available within all components that are part of the lazy loaded module. Note that the transpiler does not necessarily need to be defined in the root of the lazy loaded module. If one if the transitively imported modules specifies a provider for the transpiler, then it will be available for the whole lazy loaded module.
-
root module - defined in the providers array of the root (application) module, or in one of the transitively imported modules. A markup transpiler provider that is defined in this way will be available everywhere in the application, meaning this is the widest scope at which a transpiler can be defined.
The component, lazy-loaded module and root module transpiler levels all make use of Angular's dependency injection system.
A tranpsiler defined at one of those levels needs to be specified using the provideTranslationMarkupTranspiler
function which generates the correct provider definition for the transpiler.
Note that the provided transpilers can be discarded for a particular usage of the <transloco>
component, by setting the mergeTranspilers
input property to false
.
Due to Angular's hierarchy of injectors you might run into the issue that specifying a transpiler at a certain level will override the set of transpilers defined at lower levels.
For example if you make a transpiler available in a lazy loaded module, then this will override all transpilers from the root module.
Often, this is not the intended effect.
Instead you probably would like to add a transpiler to the existing set of transpilers.
This is supported by ngx-transloco-markup by including inheritTranslationMarkupTranspilers()
in the providers list of a module or component:
import { inheritTranslationMarkupTranspilers } from 'ngx-transloco-markup';
import { CustomTranspiler } from './transpilers';
@NgModule({
providers: [
provideTranslationMarkupTranspiler(CustomTranspiler),
inheritTranslationMarkupTranspilers() // <-- make all transpilers from parent injector available
]
})
export class LazyLoadedModuleWithAdditionalTranspilers { }
Keep in mind that the order of transpiler providers is important.
If some of them can parse the same syntax, then the one that is provided first will win and therefore effectively overrides the others.
This means you usually would need to put inheritTranslationMarkupTranspilers()
after the other transpiler providers.
The default providers you get out-of-the-box with defaultTranslocoMarkupTranspilers()
, includes a link transpiler that supports the following syntax: [link:parameterKey]...[/link]
.
This is a generic syntax that can be used free of any context.
While that makes for an easy setup, it does add a bit of clutter to your translations.
That clutter can be reduced by introducing contextual markup tokens: customized syntax that can be used for within a specific context. It is not uncommon, for example, to refer to some entity by its name (or some other descriptive property) and be able to link to a detail view of that entity. Consider the scenario of a web shop, where adding an item to the basket would display the following message:
{
"PRODUCT_ADDED": "Added [link:productUrl]{{ productName }}[/link] to the basket."
}
In this case the entity is a product, for which both a name and URL are referenced in the translation. If instead a context specific transpiler was created for the product entity, the translation could be simplified to:
{
"PRODUCT_ADDED": "Added [product] to the basket."
}
Obviously ngx-transloco-markup
will not recognize the [product]
token by default, so you will need to create your own transpiler and make it available to the <transloco>
component.
You can find out how that do that in the creating your own markup transpilers section.
Since the need for contextual link transpilers is quite common, ngx-transloco-markup
provides a ContextualLinkTranspilerFactory
to simplify their creation.
This factory can be injected in your components and provider factory functions, allowing you to create two types of contextual link transpilers.
The first type that can be created is a ContextualLinkSubstitutionTranspiler
.
This type of transpiler simply substitutes a specific token (substring) in a translation value with a link.
Such a transpiler would be needed to support the [product]
token of the example translation shown above.
An instance of this type of transpiler can be created using the ContextualLinkTranspilerFactory.createSubstitutionTranspiler
function, which supports two call signatures:
-
createSubstitutionTranspiler(parameterKey: string)
This is the simplest form, which creates a transpiler that substitutes a token
[parameterKey]
with a link. The label that is displayed the link is determined by the value ofparameterKey.label
property within the translation parameters. Similarly, the link target is resolved via theparameterKey.link
property. -
createSubstitutionTranspiler(token: string, options: ContextualLinkSubstitutionTranspilerOptions)
With this call signature you are given more control. First, you need to specify what token will be converted to a link by the transpiler. That can be anything, e.g.
'<banana>'
,'$website'
or just'???'
(although I would not recommend the latter). Just be aware that the chosen token can collide with another transpiler that supports a similar grammar.In addition to the token you will need to specify how the label and link target are resolved, via the
options
object. This object has two properties,label
andlink
. Both allow you to define a resolving method, which can be one of the following:{ static: ... }
- A static value for the label or link.{ parameterKey: ... }
- A value that is obtained from the property with the specified key in the translation parameters.{ resolve: (translationParams) => ... }
- A resolver function to dynamically construct the label or link (using the translation parameters if necessary).
Another type of transpiler that can be created by the ContextualLinkTranspilerFactory
is the ContextualLinkBlockTranspiler
, which supports the following syntax: [linkToken]...[/linkToken]
.
In contrast to the substitution transpiler, the block transpiler will render the contents of the block as a link.
This is useful in case you need additional markup within the link or if the link text itself does not depend on an entity but is something that needs to be translated.
The factory also provides two signatures of the ContextualLinkTranspilerFactory.createBlockTranspiler
function for creating ContextualLinkBlockTranspiler
instances:
-
createBlockTranspiler(parameterKey: string)
Creates a link block transpiler that resolves the link target based on the specified key in the translation parameters object. The start and end token are equivalent to
[parameterKey]
and[/parameterKey]
. -
createBlockTranspiler(startToken: string, endToken: string, resolveLinkSpecification: ResolveLinkSpecification)
This alternative form gives you the freedom to choose the start and end token for the link block transpiler. Also, the method for resolving the link target can be specified in one of the following ways:
{ static: ... }
- A static value for the link.{ parameterKey: ... }
- A value that is obtained from the property with the specified key in the translation parameters.{ resolve: (translationParams) => ... }
- A resolver function to dynamically construct the link (using the translation parameters if necessary).
The whole architecture of ngx-transloco-markup
is based on the concept of transpilers: small units that are responsible for converting (part) of translation values into rendering functions.
For example the BoldTextTranspiler
is capable of recognizing blocks that start with [b]
and end with [/b]
.
Once it has recognized such a block, it transforms it to a rendering function that creates a <b>
HTML element and appends the content between the start and end tags to that element.
While the ngx-transloco-markup
library ships with a set of standard transpilers for the most common use cases, there are surely a lot of other different markup requirements which are not supported out-of-the-box.
Instead of trying to cover all these different requirements into a single library, you are offered the option to expand the set of transpilers if necessary.
Creating your own transpiler is as simple as instantiating an object that conforms to the TranslationMarkupTranspiler
interface (shown below) and make it available at the right place in the application for the <transloco>
component.
export interface TranslationMarkupTranspiler {
tokenize(
translation: string,
offset: number
): TokenizeResult | undefined;
transpile(
offset: number,
context: TranslationMarkupTranspilerContext
): TranspileResult | undefined;
}
As can be seen in the interface definition of the TranslationMarkupTranspiler
above, a transpiler needs to implement two functions: tokenize
and transpile
.
This is because the process is performed in two phases:
- A tokenization phase, where the translation value (a string) is converted into a sequence of tokens.
- The second phase is where the actual transpilation happens: the sequence of tokens is converted into one (or more) rendering functions.
Each of these two functions can either return a result model or undefined
, where the
latter is used to indicate that the transpiler is unable to parse the input at the specified position.
The <transloco>
component uses a sequence of transpilers to parse translation values.
If a transpiler cannot parse the input at specific position, then the next transpiler in the sequence will be asked to so, and so forth, until there is one that is able to parse the input.
Note that there will always be an implicit fallback transpiler present to capture literal text.
You don't need to specify this transpiler, as the <transloco>
component always appends this transpiler to end of the transpiler sequence.
Although the tokenization phase theoretically could be omitted, it simplifies the transpilation phase.
Mostly on conceptual level, but also performance wise.
This is why ngx-transloco-markup
converts translation values first into a sequence of tokens, before transpiling it into rendering functions.
The result model for the tokenize
function is as follows:
export interface TokenizeResult {
nextOffset: number;
token: unknown;
}
Apart from the token, the model also specifies the next offset where the tokenization process should continue.
Using the BoldTextTranspiler
as example, tokenizing the string 'trans[b]loco[/b]'
, will result in a token at offsets 5 and 12 and where the nextOffset
value is equal to 8 and 16 respectively.
For all other offsets, the tokenize
function will return undefined
.
One thing to note here is that tokens are typed as unknown
.
This is because the set of transpilers is not closed, meaning we cannot know upfront what type of tokens will be generated.
Once the translation value (a string) has been converted to a sequence of tokens, the transpilation phase is executed.
It is during this phase that the tranpile
function of the transpilers is called.
As input it receives the parse offset (within the token sequence) and a TranslationMarkupTranspilerContext
object.
This object has two properties and two functions:
tokens
- the token sequence that is to be transpiled.translation
- a dictionary object containing the translation values for the active language.transpile
- a function that can be used to recursively transpile the token sequence for transpilers that support nested content.transpileUntil
- another function used to recursive transpilation that continues parsing tokens until a certain token is encountered. Unlike thetranspile
function this function can return a sequence of markup renderers instead of just one (or none at all).
As mentioned before, tokens are represented with the unknown
type.
A transpiler therefore should recognize supported tokens using strict equality comparison (===
), typeof
-checks, instanceof
-checks or type guard functions.
Once a supported (sub)sequence of tokens is parsed at the specified offset a TranspileResult
object (interface is shown below) should be returned.
This object contains the offset of the next token where the transpilation process should continue.
export interface TranspileResult {
nextOffset: number;
renderer: TranslationMarkupRenderer;
}
export type TranslationMarkupRenderer<T extends Node = Node> = (translationParameters: HashMap) => T;
More importantly, a TranspileResult
object also includes a rendering function.
This function ultimately encodes the rendering logic that displays translations with markup.
Given a translation parameters object, the rendering function should be able to produce a DOM Node.
A completely transpiled translation value will result in one or more rendering functions.
Once the translation actually should be rendered, all of these functions are invoked with the translation parameters, resulting in one or more DOM Nodes.
These nodes are then added as children to the <transloco>
component, thereby making the rendered translation visible in the application.
To illustrate how to create your own custom transpiler, let's craft a transpiler to add some color to your translations.
It supports the following syntax [c:cssColorValue]...[/c]
, where cssColorValue
should be replaced by a valid CSS color, e.g. #123abc
, rgba(123, 45, 67, 0.5)
, orange
, etc.
First thing we need is a way to represent the tokens. For that we will be using the following:
class ColorStart {
constructor(
public readonly cssColorValue: string
) { }
}
const COLOR_END = new (class ColorEnd {})();
The start token of a color block will be an instance of the ColorStart
tag.
This token also stores the CSS color value that will be applied to its content.
Since the end token of a color block doesn't need to store any information, this can just be a constant value.
Here we chose create a singleton instance of the ColorEnd
class.
Although simple unique value, such as a string or Symbol
, would also be sufficient, the ColorEnd
class makes for a nice symmetry with the ColorStart
class.
With the token representations set up, we can now implement the tokenize
function:
@Injectable()
export class ColorTranspiler implements TranslationMarkupTranspiler {
public tokenize(translation: string, offset: number): TokenizeResult | undefined {
return (
recognizeColorStartToken(translation, offset) ||
recognizeColorEndToken(translation, offset)
);
}
// TODO: transpile function
}
function recognizeColorStartToken(translation: string, offset: number): TokenizeResult | undefined {
const COLOR_START_TOKEN = '[c:';
if (!translation.startsWith(COLOR_START_TOKEN, offset)) {
return undefined;
}
const end = translation.indexOf(']', offset + COLOR_START_TOKEN.length);
if (end < 0) {
return undefined;
}
const cssColorValue = translation.substring(offset + COLOR_START_TOKEN.length, end);
return {
nextOffset: end + 1,
token: new ColorStart(cssColorValue)
};
}
function recognizeColorEndToken(translation: string, offset: number): TokenizeResult | undefined {
const COLOR_END_TOKEN = '[/c]';
if (!translation.startsWith(COLOR_END_TOKEN, offset)) {
return undefined;
}
return {
nextOffset: offset + COLOR_END_TOKEN.length,
token: COLOR_END
};
}
The tokenize
function makes use of two utility functions: recognizeColorStartToken
and recognizeColorEndToken
.
Since there is nothing special to mention about their implementation (they should be self-explanatory), let's proceed with the implementation of the transpile
function.
First, we need to decide how the parse the token sequence.
Given a sequence of tokens and a specific position within that sequence, the token at that position must be an instance of ColorStart
, so that will be our first check.
This token will also give us the CSS color value needed later to construct rendering function.
Once verified that the (sub)sequence starts with a ColorStart
token, next we need to (recursively) transpile the contents up until the COLOR_END
token.
You could implement that process yourself, however, the context
object provides a transpileUntil
function that already does exactly what we need.
Invoking this function will yield an array of TranslationMarkupTranspiler
functions and the next unparsed offset (which will be the COLOR_END
token).
A possible implementation for the transpile
function is shown below.
@Injectable()
export class ColorTranspiler implements TranslationMarkupTranspiler {
public tokenize(translation: string, offset: number): TokenizeResult | undefined {
// ...
}
public transpile(
start: number,
context: TranslationMarkupTranspilerContext
): TranspileResult | undefined {
const nextToken = context.tokens[start];
if (!(nextToken instanceof ColorStart)) {
return undefined;
}
const { nextOffset, renderers } = context.transpileUntil(
start + 1,
(token) => token === COLOR_END
);
return {
nextOffset: Math.min(nextOffset + 1, context.tokens.length),
renderer: this.createRenderer(nextToken.cssColorValue, renderers)
};
}
}
Next, there is only one thing left to do: we need to implement the createRenderer
utility function.
It is the responsibility of this function to create the renderer that combines all child renderers to render the contents with the desired color.
Our desired rendering function should create some element (in this case a <span>
element), set an inline color style and attach the rendered child nodes to the element.
To simplify the implementation, we can make use of the TranslationMarkupRendererFactory
, which can be obtained via dependency injection.
The implementation therefore would look something like the following:
@Injectable()
export class ColorTranspiler implements TranslationMarkupTranspiler {
constructor(
private readonly rendererFactory: TranslationMarkupRendererFactory
) { }
public tokenize(translation: string, offset: number): TokenizeResult | undefined {
// ...
}
public transpile(
start: number,
context: TranslationMarkupTranspilerContext
): TranspileResult | undefined {
// ...
}
private createRenderer(
cssColorValue: string,
childRenderers: TranslationMarkupRenderer[]
): TranslationMarkupRenderer {
const spanRenderer = this.rendererFactory.createElementRenderer('span', childRenderers);
function renderColorMarkup(translationParameters: HashMap): HTMLSpanElement {
const spanElement = spanRenderer(translationParameters);
spanElement.style.color = cssColorValue;
return spanElement;
}
return renderColorMarkup;
}
}
A complete implementation of the colored text transpiler can be found in the demo application: demo/app/features/custom-transpilers/colored-text-transpiler.ts
One of Transloco's features is the support for interpolation expressions.
Such expressions allow you to insert parameter values or other translations using the {{ expression }}
syntax.
The syntax and expression types can be customized.
One example is the message format plugin to support translations that use the ICU syntax for expressing pluralization and gender.
Transloco itself also uses a transpiler for this purpose: the TranslocoTranspiler
.
Plugins can override the default transloco Transpiler to support a different (customized) syntax and evaluation scheme for interpolation expressions.
Since ngx-transloco-markup
is meant to support all Transloco features, it also uses the TranslocoTranspiler
to expand interpolation expressions.
This means that it also supports custom implementations, like the message format plugin.
Due to the different transpiler architecture used by ngx-transloco-markup
, you might need to help it
recognize interpolation expressions when a custom syntax is used.
For example, expressions supported by the message format plugin only require single opening and closing curly-braces ({ expression }
).
Also, they might contain nested curly braces, e.g.: {myCount, plural, =0 {no results} one {1 result} other {# results}}
.
Custom interpolation expression can be recognized by ngx-transloco-markup
if you provide a custom
implementation of the InterpolationExpressionMatcher
interface:
export interface InterpolationExpressionMatcher {
matchExpression(source: string, offset: number): number | undefined;
}
The interface has just one function matchExpression
that returns the length of the interpolation expression (in number of characters) for a given translation value and position within that value.
When no expression was found at the specified position, then undefined
must be returned.
An example implementation for the message format plugin could be the following:
export class MessageFormatInterpolationExpressionMatcher implements InterpolationExpressionMatcher {
public matchExpression(source: string, startOffset: number): number | undefined {
let offset = startOffset;
let level = 0;
do {
const character = source.charAt(offset);
if (character === '{') {
level++;
}
if (character === '}') {
level--;
}
offset++;
} while (level > 0 && offset < source.length)
const expressionLength = offset - startOffset;
return level === 0 && expressionLength > 1 ? expressionLength : undefined
}
}
To make the custom InterpolationExpressionMatcher
implementation available to ngx-transloco-markup
register it as provider in root module of your application using the TRANSLATION_INTERPOLATION_EXPRESSION_MATCHER
injection token:
import { TRANSLATION_INTERPOLATION_EXPRESSION_MATCHER } from 'ngx-transloco-markup';
@NgModule({
providers: [
{
provide: TRANSLATION_INTERPOLATION_EXPRESSION_MATCHER,
useClass: MessageFormatInterpolationExpressionMatcher
}
]
})
export class AppModule {}
ngx-transloco-markup supports two type of link models out-of-the-box: string values and values that conform to the ExternalLink
model.
Both are treated as links that target a location outside of the application.
Many applications, however, also need links that target a specific part within the application.
If your application uses the Angular router, then this is usually achieved by means of a router link directive.
Since that directive cannot be used within translation values another method is necessary.
To still be able to support router links (or other special link types) in translation values, you can specify additional link renderers.
A link renderer should implement or extend the LinkRenderer
(abstract) class:
export abstract class LinkRenderer<T> {
public abstract supports(link: unknown): link is T;
public abstract render(link: T, targetElement: HTMLAnchorElement): void;
}
Link renderers are used by the link transpilers to determine how a specific link should be applied to an
HTMLAnchorElement
.
This setup allows for different types of link models that can have their own specific rendering method.
A custom LinkRenderer
can be registered by providing it at the right module (usually the application's root module), using the provideLinkRenderer
function:
import { provideLinkRenderer } from 'ngx-transloco-markup';
@NgModule({
providers: [
provideLinkRenderer(MyCustomLinkRenderer)
]
})
export class AppModule { }
Since the need for router links is quite common a special renderer for such links is available.
This renderer is provided in a separate NPM package: ngx-transloco-markup-router-link
.
Note that the renderer is not part of the ngx-transloco-markup
core package, as it depends on the @angular/router
package.