Skip to content

Commit d3c5262

Browse files
committed
feat: 🎸 functional transpiler
1 parent 21084c2 commit d3c5262

13 files changed

+400
-166
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ charset = utf-8
66
indent_style = space
77
indent_size = 2
88
insert_final_newline = true
9-
trim_trailing_whitespace = true
9+
trim_trailing_whitespace = false
1010

1111
[*.md]
1212
max_line_length = off

docs/docs/additional-functionality.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ You can point to specific keys in other keys from the same translation file. For
1616

1717
So the result of `service.translate('fromList')` will be: "from home english".
1818

19-
When using this feature inside a [scope](https://ngneat.github.io/transloco/docs/scope-configuration), make sure you prefix the key reference with the scope name:
19+
When using this feature inside a [scope](../scope-configuration)), make sure you prefix the key reference with the scope name:
2020

2121
```json title="admin/en.json"
2222
{

docs/docs/hack.mdx

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,6 @@ export const customInterceptor = {
4646
};
4747
```
4848

49-
## The Transpiler
50-
The transpiler is responsible for resolving the given value. For example, the default transpiler transpiles `Hello {{ key }}` and replaces the dynamic variable `key` based on the given params, or the translation object.
51-
52-
```ts
53-
import { TranslocoTranspiler } from '@ngneat/transloco';
54-
55-
export class CustomTranspiler implements TranslocoTranspiler {
56-
transpile(value: any, params, translation: Translation) {
57-
return ...;
58-
}
59-
}
60-
61-
export const customTranspiler = {
62-
provide: TRANSLOCO_TRANSPILER,
63-
useClass: CustomTranspiler
64-
}
65-
```
66-
6749
## Missing Handler
6850

6951
This handler is responsible for handling missing keys. The default handler calls `console.warn()` with the key when config.isProdMode is set to `false`, and returns an empty string to use as a replacement for the missing key's value.

docs/docs/loading-template.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: Loading Template
44

55
Transloco provides you with a way to define a loading template, that will be used while the translation file is loading.
66

7-
Similarly to the previous examples, set the `TRANSLOCO_LOADING_TEMPLATE` provider either in lazy module providers, component providers, in the template, or even in the `app.module` itself (affecting the entire app).
7+
Similarly to the previous examples, set the `TRANSLOCO_LOADING_TEMPLATE` provider either in lazy module providers, component providers, in the template, or even in the `AppModule` itself (affecting the entire app).
88

99
For example:
1010

docs/docs/plugins/message-format.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ npm i messageformat @ngneat/transloco-messageformat
1515
```
1616

1717
## Usage
18-
The `MessageFormatTranspiler` is compatible with the DefaultTranspiler and therefore you can switch without worry that it will break your current translations.
18+
The `MessageFormatTranspiler` is compatible with the `DefaultTranspiler` and therefore you can switch without worry that it will break your current translations.
1919

2020
It then enables support for the following within youIt enables support for the following within your i18n translation files:
2121

@@ -26,7 +26,7 @@ It then enables support for the following within youIt enables support for the f
2626
}
2727
```
2828

29-
To enable this plugin, add the following to the imports array in your `app.module.ts`:
29+
To enable this plugin, add the following import in your `TranslocoRootModule`:
3030

3131
```ts title="transloco-root.module.ts"
3232
import { TranslocoMessageFormatModule } from '@ngneat/transloco-messageformat';

docs/docs/translation-in-template.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Let's say we need to use the `dashboard` scope all over the template. Given this
4747
```json title="en.json"
4848
{
4949
"foo": "Foo",
50-
"bar": "Bar"
50+
"bar": "Bar",
5151
"dashboard": {
5252
"title": "Dashboard Title",
5353
"desc": "Dashboard Desc"

docs/docs/transpiler.mdx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
title: The Transpiler
3+
---
4+
5+
The transpiler is responsible for resolving the given value.
6+
For example, when given `Hello {{ key }}` the default transpiler will replace the dynamic variable `key` based on the given params, or the translation object.
7+
8+
## Functional Transpiler
9+
10+
In addition to the default transpiler Transloco also exposes the `FunctionalTranspiler` which allows you to implement function calls into your translation values.
11+
This way you can leverage Angular's DI power and make you translations even more flexible.
12+
13+
The `FunctionalTranspiler` is compatible with the `DefaultTranspiler` and therefore you can switch without worry that it will break your current translations.
14+
15+
To enable this transpiler, add the following provider in your `TranslocoRootModule`:
16+
17+
```ts title="transloco-root.module.ts"
18+
import { Injector } from '@angular/core'
19+
import { FunctionalTranspiler, TRANSLOCO_TRANSPILER } from '@ngneat/transloco';
20+
21+
@NgModule({
22+
...
23+
providers: [{
24+
provide: TRANSLOCO_TRANSPILER,
25+
useClass: FunctionalTranspiler,
26+
deps: [Injector]
27+
}]
28+
})
29+
export class TranslocoRootModule {}
30+
```
31+
32+
### Usage
33+
34+
In order to use a function in the translation we need to provide it to the transpiler.
35+
We do so by creating a new class that implements the `TranslocoTranspilerFunction` interface.
36+
For example, let's say we want to display different texts in case the user has a specific feature exposed or not:
37+
38+
```ts title="has-feature-flag.ts"
39+
import { FFService } from './feature-flag.service';
40+
import { TranslocoTranspilerFunction } from '@ngneat/transloco';
41+
42+
class FeatureFlagResolver implements TranslocoTranspilerFunction {
43+
constructor(private featureFlagService: FFService) {}
44+
45+
transpile(...args: string[]): any {
46+
const [flagName, trueValue, falseValue] = args;
47+
48+
return this.featureFlagService.hasFF(flagName) ? trueValue : falseValue;
49+
}
50+
}
51+
```
52+
As you can see the `transpile` function can accept any number of arguments, you are the one who defines which arguments will be passed. In my case I'm passing 3:
53+
* The feature flag's name.
54+
* The value I want to present in case the user has the flag.
55+
* The value I want to present in case the user doesn't have the flag.
56+
57+
Now we will add this transpiler function to the `TranslocoRootModule` providers:
58+
59+
```ts title="transloco-root.module.ts"
60+
import { Injector } from '@angular/core'
61+
import { FunctionalTranspiler, TRANSLOCO_TRANSPILER } from '@ngneat/transloco';
62+
import { FeatureFlagResolver } from './has-feature-flag';
63+
64+
@NgModule({
65+
...
66+
providers: [{
67+
provide: TRANSLOCO_TRANSPILER,
68+
useClass: FunctionalTranspiler,
69+
deps: [Injector]
70+
},
71+
{
72+
provide: 'hasFeatureFlag', // ====> The function name used in the translation
73+
useClass: FeatureFlagResolver
74+
}],
75+
})
76+
export class TranslocoRootModule {}
77+
```
78+
79+
The functional syntax is very similar to calling a regular function, here is an example:
80+
81+
```json title="en.json"
82+
{
83+
"title": "[[ hasFeatureFlag(newDashboards, has flag, missing flag) ]]",
84+
}
85+
```
86+
87+
In this case we are checking if the use has `newDashboard` flag, in case he does, we want to display
88+
`'has flag'` and otherwise we will display `'missing flag'`.
89+
90+
### Usage Notes
91+
92+
If the function returns a string that includes the interpolation syntax (`{{value}}`), the transpiler will replace it with the `params` or [other keys references](../additional-functionality#reference-other-keys) just like the default.
93+
94+
If you function param needs to include a comma you need to escape it:
95+
```json title="en.json"
96+
{
97+
"title": "[[ someFunc(Hello {{user}}\\, welcome ,...) ]]",
98+
}
99+
```
100+
`'Hello {{user}}, welcome'` will be the first param passed.
101+
102+
## Custom Transpiler
103+
104+
You can also provide a custom transpiler by creating a class that implements the `TranslocoTranspiler` interface.
105+
106+
```ts
107+
import { TranslocoTranspiler } from '@ngneat/transloco';
108+
109+
export class CustomTranspiler implements TranslocoTranspiler {
110+
transpile(value: any, params, translation: Translation) {
111+
return ...;
112+
}
113+
}
114+
115+
export const customTranspiler = {
116+
provide: TRANSLOCO_TRANSPILER,
117+
useClass: CustomTranspiler
118+
}
119+
```

docs/docusaurus.config.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ module.exports = {
4141
},
4242
{
4343
href: 'https://stackblitz.com/edit/ngneat-transloco',
44-
label: 'Playground ↗',
45-
position: 'left'
44+
label: 'Playground',
45+
position: 'left',
46+
className: 'header-playground-link'
4647
},
4748
{
4849
href: 'https://gitter.im/ngneat-transloco/lobby',

docs/sidebars.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ module.exports = {
4242
type: 'doc',
4343
id: 'loading-template'
4444
},
45+
{
46+
type: 'doc',
47+
id: 'transpiler'
48+
},
4549
{
4650
type: 'doc',
4751
id: 'hack'

docs/src/css/custom.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
padding: 0 var(--ifm-pre-padding);
2525
}
2626

27+
[data-theme='dark'] .docusaurus-highlight-code-line {
28+
background-color: rgb(46, 47, 75);
29+
}
30+
2731
code {
2832
background-color: rgba(255, 229, 100, 0.2);
2933
color: #1a1a1a;
@@ -192,6 +196,22 @@ header iframe {
192196
no-repeat;
193197
}
194198

199+
.header-playground-link {
200+
display: flex;
201+
}
202+
203+
.header-playground-link:after {
204+
background: url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='10' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M29.9984 2.92009C29.9543 1.81641 29.0238 0.957482 27.9201 1.00163L9.93446 1.72105C8.83077 1.7652 7.97185 2.6957 8.01599 3.79939C8.06014 4.90308 8.99064 5.76201 10.0943 5.71786L18.047 5.37359L20.1625 7.57369L2.5304 27.6435C1.7812 28.4551 1.83181 29.7204 2.64345 30.4696C3.45509 31.2188 4.72041 31.1682 5.46962 30.3566L23.3959 10.9365L26.4426 14.1051L26.721 21.0656C26.7652 22.1693 27.6957 23.0282 28.7994 22.9841C29.9031 22.9399 30.762 22.0094 30.7178 20.9057L29.9984 2.92009Z' fill='black'/%3E%3C/svg%3E")
205+
no-repeat center;
206+
content: '';
207+
width: 24px;
208+
height: 24px;
209+
}
210+
[data-theme='dark'] .header-playground-link:after {
211+
background: url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='10' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M29.9984 2.92009C29.9543 1.81641 29.0238 0.957482 27.9201 1.00163L9.93446 1.72105C8.83077 1.7652 7.97185 2.6957 8.01599 3.79939C8.06014 4.90308 8.99064 5.76201 10.0943 5.71786L18.047 5.37359L20.1625 7.57369L2.5304 27.6435C1.7812 28.4551 1.83181 29.7204 2.64345 30.4696C3.45509 31.2188 4.72041 31.1682 5.46962 30.3566L23.3959 10.9365L26.4426 14.1051L26.721 21.0656C26.7652 22.1693 27.6957 23.0282 28.7994 22.9841C29.9031 22.9399 30.762 22.0094 30.7178 20.9057L29.9984 2.92009Z' fill='white'/%3E%3C/svg%3E")
212+
no-repeat center;
213+
}
214+
195215
[data-theme='dark'] .header-gitter-link:before {
196216
background: url("data:image/svg+xml;charset=utf-8,%3Csvg version='1.1' fill='white' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 18 25' class='logo-gitter-sign' data-v-44ebcb1a=''%3E%3Crect x='15' y='5' width='2' height='10'%3E%3C/rect%3E%3Crect x='10' y='5' width='2' height='20'%3E%3C/rect%3E%3Crect x='5' y='5' width='2' height='20'%3E%3C/rect%3E%3Crect width='2' height='15'%3E%3C/rect%3E%3C/svg%3E")
197217
no-repeat;

0 commit comments

Comments
 (0)