Skip to content

Commit cf07bfc

Browse files
authored
feat: 🎸 Add interpolation to configuration (#330)
* feat: 🎸 Add RexExTranspiler * refactor: 💡 Removed RegExTranspiler in favor of configuration * refactor: 💡 Removed RegExp as transpilation option * refactor: 💡 Moved wrapParam to testDefaultBehaviour scope * fix: 🐛 MessageFormatTranspiler should use TranslocoConfig * refactor: 💡 removed unused const in spec
1 parent 20112f5 commit cf07bfc

File tree

13 files changed

+136
-49
lines changed

13 files changed

+136
-49
lines changed

cypress/integration/transpilers.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@ export function testTranspilersContent(lang = 'english') {
33
cy.get(`[data-cy=regular]`).should('contain', `Home - ${lang}`);
44
cy.get(`[data-cy=with-params]`).should('contain', `Replaces standard 🦄 - ${lang}`);
55
cy.get(`[data-cy=with-translation-reuse]`).should('contain', `a.b.c from list - ${lang}`);
6-
cy.get(`[data-cy=with-message-format]`).should('contain', `The boy won his race - ${lang}`);
7-
cy.get(`[data-cy=with-message-format-dynamic]`).should('contain', `The girl won her race - ${lang}`);
6+
cy.get(`[data-cy=with-message-format]`).should('contain', `The boy named Henkie won his race - ${lang}`);
7+
cy.get(`[data-cy=with-message-format-dynamic]`).should('contain', `The girl named Kim won her race - ${lang}`);
88
cy.get(`[data-cy=with-message-format-params]`).should(
99
'contain',
10-
`Can replace transloco params and also give parse messageformat: The person won their race - ${lang}`
10+
`Can replace transloco params and also give parse messageformat: The person named Joko won their race - ${lang}`
1111
);
1212

1313
// Directive
1414
cy.get(`[data-cy=d-regular]`).should('contain', `Home - ${lang}`);
1515
cy.get(`[data-cy=d-with-params]`).should('contain', `Replaces standard 🦄 - ${lang}`);
1616
cy.get(`[data-cy=d-with-translation-reuse]`).should('contain', `a.b.c from list - ${lang}`);
17-
cy.get(`[data-cy=d-with-message-format]`).should('contain', `The boy won his race - ${lang}`);
18-
cy.get(`[data-cy=d-with-message-format-dynamic]`).should('contain', `The girl won her race - ${lang}`);
17+
cy.get(`[data-cy=d-with-message-format]`).should('contain', `The boy named Pete won his race - ${lang}`);
18+
cy.get(`[data-cy=d-with-message-format-dynamic]`).should('contain', `The girl named Maxime won her race - ${lang}`);
1919
cy.get(`[data-cy=d-with-message-format-params]`).should(
2020
'contain',
21-
`Can replace transloco params and also give parse messageformat: The person won their race - ${lang}`
21+
`Can replace transloco params and also give parse messageformat: The person named Ono won their race - ${lang}`
2222
);
2323

2424
// Dynamic params
@@ -31,10 +31,10 @@ export function testTranspilersContent(lang = 'english') {
3131
cy.get(`[data-cy=p-regular]`).should('contain', `Home - ${lang}`);
3232
cy.get(`[data-cy=p-with-params]`).should('contain', `Replaces standard 🦄 - ${lang}`);
3333
cy.get(`[data-cy=p-with-translation-reuse]`).should('contain', `a.b.c from list - ${lang}`);
34-
cy.get(`[data-cy=p-with-message-format]`).should('contain', `The boy won his race - ${lang}`);
35-
cy.get(`[data-cy=p-with-message-format-dynamic]`).should('contain', `The girl won her race - ${lang}`);
34+
cy.get(`[data-cy=p-with-message-format]`).should('contain', `The boy named Stef won his race - ${lang}`);
35+
cy.get(`[data-cy=p-with-message-format-dynamic]`).should('contain', `The girl named Donna won her race - ${lang}`);
3636
cy.get(`[data-cy=p-with-message-format-params]`).should(
3737
'contain',
38-
`Can replace transloco params and also give parse messageformat: The person won their race - ${lang}`
38+
`Can replace transloco params and also give parse messageformat: The person named Mary won their race - ${lang}`
3939
);
4040
}

docs/docs/config-options.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,12 @@ translocoConfig({
103103
}
104104
})
105105
```
106+
107+
### `interpolation`
108+
The start and end markings for parameters: (defaults to `['{{', '}}']`)
109+
```ts
110+
translocoConfig({
111+
// This will enable you to specify parameters as such: `"Hello <<<value>>>"`
112+
interpolation: ['<<<', '>>>']
113+
})
114+
```

docs/docs/transpiler.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ description: The Transpiler | Transloco Angular i18n
66
The transpiler is responsible for resolving the given value.
77
For example, when given `Hello {{ key }}` the default transpiler will replace the dynamic variable `key` based on the given params, or in some cases, within the [translation object itself](./additional-functionality#reference-other-keys).
88

9+
## DefaultTranspiler
10+
The default transpiler can be configured with custom interpolation start and end markings to match message parameters.
11+
12+
To configure the `DefaultTranspiler` interpolation markings you must provide a Transloco config with the [interpolation](./config-options#interpolation) property set.
13+
914
## Functional Transpiler
1015

1116
In addition to the default transpiler, Transloco also exposes the `FunctionalTranspiler` which allows you to implement function calls into your translation values. This way you can leverage Angular's DI power and make your translations even more flexible.

projects/ngneat/transloco-messageformat/src/lib/messageformat.transpiler.spec.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { MessageFormatTranspiler } from './messageformat.transpiler';
2-
import { flatten } from '@ngneat/transloco';
2+
import { flatten, translocoConfig } from '@ngneat/transloco';
33

44
describe('MessageFormatTranspiler', () => {
55
const config = {};
66
const parser = new MessageFormatTranspiler(config);
7+
const parserWithCustomInterpolation = new MessageFormatTranspiler(
8+
config,
9+
translocoConfig({ interpolation: ['<<<', '>>>'] })
10+
);
711

812
it('should translate simple SELECT messageformat string from params when first param given', () => {
913
const parsed = parser.transpile(
@@ -32,7 +36,7 @@ describe('MessageFormatTranspiler', () => {
3236
expect(parsed).toEqual('The person won their race');
3337
});
3438

35-
it('should translate simple parmas and SELECT messageformat string from params when no param given', () => {
39+
it('should translate simple params and SELECT messageformat string from params when no param given', () => {
3640
const parsed = parser.transpile(
3741
'The {{value}} { gender, select, male {boy won his} female {girl won her} other {person won their}} race',
3842
{ value: 'smart', gender: '' },
@@ -41,6 +45,24 @@ describe('MessageFormatTranspiler', () => {
4145
expect(parsed).toEqual('The smart person won their race');
4246
});
4347

48+
it('should translate simple param and interpolate params inside messageformat string', () => {
49+
const parsedMale = parser.transpile(
50+
'The {{ value }} { gender, select, male {boy named {{ name }} won his} female {girl named {{ name }} won her} other {person named {{ name }} won their}} race',
51+
{ value: 'smart', gender: 'male', name: 'Henkie' },
52+
{}
53+
);
54+
expect(parsedMale).toEqual('The smart boy named Henkie won his race');
55+
});
56+
57+
it('should translate simple param and interpolate params inside messageformat string using custom interpolation markers', () => {
58+
const parsedMale = parserWithCustomInterpolation.transpile(
59+
'The <<< value >>> { gender, select, male {boy named <<< name >>> won his} female {girl named <<< name >>> won her} other {person named <<< name >>> won their}} race',
60+
{ value: 'smart', gender: 'male', name: 'Henkie' },
61+
{}
62+
);
63+
expect(parsedMale).toEqual('The smart boy named Henkie won his race');
64+
});
65+
4466
it('should translate simple string from params', () => {
4567
const parsed = parser.transpile('Hello {{ value }}', { value: 'World' }, {});
4668
expect(parsed).toEqual('Hello World');

projects/ngneat/transloco-messageformat/src/lib/messageformat.transpiler.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Injectable, Inject, Optional } from '@angular/core';
2-
import { DefaultTranspiler, HashMap, Translation, isObject, setValue, getValue } from '@ngneat/transloco';
1+
import { Inject, Injectable, Optional } from '@angular/core';
2+
import { DefaultTranspiler, getValue, HashMap, isObject, setValue, Translation } from '@ngneat/transloco';
33

44
import * as MessageFormat from 'messageformat';
5-
import { MessageformatConfig, TRANSLOCO_MESSAGE_FORMAT_CONFIG, MFLocale } from './messageformat.config';
5+
import { MessageformatConfig, MFLocale, TRANSLOCO_MESSAGE_FORMAT_CONFIG } from './messageformat.config';
6+
import { TRANSLOCO_CONFIG, TranslocoConfig } from '../../../transloco/src/lib/transloco.config';
67

78
function mfFactory(locales?: MFLocale, messageConfig?: MessageFormat.Options): MessageFormat {
89
//@ts-ignore
@@ -14,8 +15,11 @@ export class MessageFormatTranspiler extends DefaultTranspiler {
1415
private messageFormat: MessageFormat;
1516
private messageConfig: MessageFormat.Options;
1617

17-
constructor(@Optional() @Inject(TRANSLOCO_MESSAGE_FORMAT_CONFIG) config: MessageformatConfig) {
18-
super();
18+
constructor(
19+
@Optional() @Inject(TRANSLOCO_MESSAGE_FORMAT_CONFIG) config: MessageformatConfig,
20+
@Optional() @Inject(TRANSLOCO_CONFIG) userConfig?: TranslocoConfig
21+
) {
22+
super(userConfig);
1923
const { locales, ...messageConfig } = config || { locales: undefined };
2024
this.messageConfig = messageConfig;
2125
this.messageFormat = mfFactory(locales, messageConfig);

projects/ngneat/transloco/src/lib/tests/transloco.transpiler.spec.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { DefaultTranspiler, FunctionalTranspiler, getFunctionArgs, TranslocoTranspiler } from '../transloco.transpiler';
22
import { flatten } from '../helpers';
33
import { transpilerFunctions } from './transloco.mocks';
4+
import { defaultConfig, translocoConfig } from '@ngneat/transloco';
45

56
describe('TranslocoTranspiler', () => {
67
describe('DefaultTranspiler', () => {
78
testDefaultBehaviour(new DefaultTranspiler());
89
});
910

11+
describe('DefaultTranspiler with custom interpolation', () => {
12+
testDefaultBehaviour(new DefaultTranspiler(translocoConfig({ interpolation: ['<<', '>>'] })), ['<<', '>>']);
13+
});
14+
1015
describe('FunctionalTranspiler', () => {
1116
const injectorMock = { get: key => transpilerFunctions[key] };
1217
const parser = new FunctionalTranspiler(injectorMock);
@@ -63,19 +68,26 @@ describe('TranslocoTranspiler', () => {
6368
});
6469
});
6570

66-
function testDefaultBehaviour(parser: TranslocoTranspiler) {
71+
function testDefaultBehaviour(
72+
parser: TranslocoTranspiler,
73+
interpolationMarkings: [string, string] = defaultConfig.interpolation
74+
) {
6775
it('should translate simple string from params', () => {
68-
const parsed = parser.transpile('Hello {{ value }}', { value: 'World' }, {});
76+
const parsed = parser.transpile(`Hello ${wrapParam('value')}`, { value: 'World' }, {});
6977
expect(parsed).toEqual('Hello World');
7078
});
7179

7280
it('should translate simple string with multiple params', () => {
73-
const parsed = parser.transpile('Hello {{ from }} {{ name }}', { name: 'Transloco', from: 'from' }, {});
81+
const parsed = parser.transpile(
82+
`Hello ${wrapParam('from')} ${wrapParam('name')}`,
83+
{ name: 'Transloco', from: 'from' },
84+
{}
85+
);
7486
expect(parsed).toEqual('Hello from Transloco');
7587
});
7688

7789
it('should translate simple string with a key from lang', () => {
78-
const parsed = parser.transpile('Hello {{ world }}', {}, flatten({ world: 'World' }));
90+
const parsed = parser.transpile(`Hello ${wrapParam('world')}`, {}, flatten({ world: 'World' }));
7991
expect(parsed).toEqual('Hello World');
8092
});
8193

@@ -86,30 +98,38 @@ describe('TranslocoTranspiler', () => {
8698
lang: 'lang',
8799
nes: { ted: 'supporting nested values!' }
88100
});
89-
const parsed = parser.transpile('Hello {{ withKeys }} {{ from }} {{ lang }} {{nes.ted}}', {}, lang);
101+
const parsed = parser.transpile(
102+
`Hello ${wrapParam('withKeys')} ${wrapParam('from')} ${wrapParam('lang')} ${wrapParam('nes.ted')}`,
103+
{},
104+
lang
105+
);
90106
expect(parsed).toEqual('Hello with keys from lang supporting nested values!');
91107
});
92108

93109
it('should translate simple string with from lang with nested params', () => {
94110
const lang = flatten({
95-
dear: 'dear {{name}}',
96-
hello: 'Hello {{dear}}'
111+
dear: `dear ${wrapParam('name')}`,
112+
hello: `Hello ${wrapParam('dear')}`
97113
});
98-
const parsed = parser.transpile('{{ hello }}', { name: 'world' }, lang);
114+
const parsed = parser.transpile(`${wrapParam('hello')}`, { name: 'world' }, lang);
99115
expect(parsed).toEqual('Hello dear world');
100116
});
101117

102118
it('should translate simple string with params and from lang', () => {
103-
const parsed = parser.transpile('Hello {{ from }} {{ name }}', { name: 'Transloco' }, flatten({ from: 'from' }));
119+
const parsed = parser.transpile(
120+
`Hello ${wrapParam('from')} ${wrapParam('name')}`,
121+
{ name: 'Transloco' },
122+
flatten({ from: 'from' })
123+
);
104124
expect(parsed).toEqual('Hello from Transloco');
105125
});
106126

107127
it('should translate simple string with params and from lang with params', () => {
108128
const lang = flatten({
109-
hello: 'Hello {{name}}'
129+
hello: `Hello ${wrapParam('name')}`
110130
});
111131
const parsed = parser.transpile(
112-
'{{ hello }}, good {{ timeOfDay }}',
132+
`${wrapParam('hello')}, good ${wrapParam('timeOfDay')}`,
113133
{ name: 'world', timeOfDay: 'morning' },
114134
lang
115135
);
@@ -126,16 +146,16 @@ describe('TranslocoTranspiler', () => {
126146
const translation = {
127147
a: 'Hello',
128148
j: {
129-
r: 'Hey {{value}}'
149+
r: `Hey ${wrapParam('value')}`
130150
},
131151
b: {
132-
flat: 'Flat {{ dynamic }}',
152+
flat: `Flat ${wrapParam('dynamic')}`,
133153
c: {
134154
otherKey: 'otherKey',
135-
d: 'Hello {{value}}'
155+
d: `Hello ${wrapParam('value')}`
136156
},
137157
g: {
138-
h: 'Name {{ name }}'
158+
h: `Name ${wrapParam('name')}`
139159
}
140160
}
141161
};
@@ -192,5 +212,9 @@ describe('TranslocoTranspiler', () => {
192212
});
193213
});
194214
});
215+
216+
function wrapParam(param: string) {
217+
return `${interpolationMarkings[0]} ${param} ${interpolationMarkings[1]}`;
218+
}
195219
}
196220
});

projects/ngneat/transloco/src/lib/transloco.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type TranslocoConfig = {
1818
useFallbackTranslation?: boolean;
1919
allowEmpty?: boolean;
2020
};
21+
interpolation?: [string, string];
2122
};
2223

2324
export const TRANSLOCO_CONFIG = new InjectionToken('TRANSLOCO_CONFIG', {
@@ -40,7 +41,8 @@ export const defaultConfig: TranslocoConfig = {
4041
},
4142
flatten: {
4243
aot: false
43-
}
44+
},
45+
interpolation: ['{{', '}}']
4446
};
4547

4648
/**

projects/ngneat/transloco/src/lib/transloco.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { TRANSLOCO_CONFIG } from './transloco.config';
1111
export const defaultProviders = [
1212
{
1313
provide: TRANSLOCO_TRANSPILER,
14-
useClass: DefaultTranspiler
14+
useClass: DefaultTranspiler,
15+
deps: [TRANSLOCO_CONFIG]
1516
},
1617
{
1718
provide: TRANSLOCO_MISSING_HANDLER,

projects/ngneat/transloco/src/lib/transloco.transpiler.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
import { Injectable, InjectionToken, Injector } from '@angular/core';
1+
import { Inject, Injectable, InjectionToken, Injector, Optional } from '@angular/core';
22
import { HashMap, Translation } from './types';
3-
import { getValue, isString, isObject, setValue, isDefined } from './helpers';
3+
import { getValue, isDefined, isObject, isString, setValue } from './helpers';
4+
import { defaultConfig, TRANSLOCO_CONFIG, TranslocoConfig } from './transloco.config';
45

56
export const TRANSLOCO_TRANSPILER = new InjectionToken('TRANSLOCO_TRANSPILER');
67

78
export interface TranslocoTranspiler {
89
transpile(value: any, params: HashMap, translation: HashMap): any;
10+
911
onLangChanged?(lang: string): void;
1012
}
1113

1214
export class DefaultTranspiler implements TranslocoTranspiler {
15+
protected interpolationMatcher: RegExp;
16+
17+
constructor(@Optional() @Inject(TRANSLOCO_CONFIG) userConfig?: TranslocoConfig) {
18+
this.interpolationMatcher = resolveMatcher(userConfig);
19+
}
20+
1321
transpile(value: any, params: HashMap = {}, translation: Translation): any {
1422
if (isString(value)) {
15-
return value.replace(/{{(.*?)}}/g, (_, match) => {
23+
return value.replace(this.interpolationMatcher, (_, match) => {
1624
match = match.trim();
1725
if (isDefined(params[match])) {
1826
return params[match];
@@ -72,6 +80,12 @@ export class DefaultTranspiler implements TranslocoTranspiler {
7280
}
7381
}
7482

83+
function resolveMatcher(userConfig?: TranslocoConfig): RegExp {
84+
const [start, end] = userConfig && userConfig.interpolation ? userConfig.interpolation : defaultConfig.interpolation;
85+
86+
return new RegExp(`${start}(.*?)${end}`, 'g');
87+
}
88+
7589
export interface TranslocoTranspilerFunction {
7690
transpile(...args: string[]): any;
7791
}

src/app/app.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { TranslocoMessageFormatModule } from '@ngneat/transloco-messageformat';
5454
missingHandler: {
5555
useFallbackTranslation: false
5656
}
57+
// interpolation: ['<<<', '>>>']
5758
})
5859
}
5960
],

0 commit comments

Comments
 (0)