Skip to content

Commit

Permalink
feat(messageformat): 🎸 add cache support
Browse files Browse the repository at this point in the history
Closes: #358
  • Loading branch information
shaharkazaz committed Sep 10, 2021
1 parent 0385eb2 commit 773bc5c
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 116 deletions.
4 changes: 4 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ We have removed deprecated code and upgraded dependencies, please take a look at
- Upgraded `messageformat` from v2.3.0 to `@messageformat/core` v3.0.0, see [changelog](https://github.com/messageformat/messageformat/blob/master/packages/core/CHANGELOG.md#300-2021-05-13) for more information.
- `TranslocoMessageFormatModule` removed `init` method, use `forRoot` instead.

### Features

- Messageformat compiled messages are now cached by default see [#358](https://github.com/ngneat/transloco/issues/358) & [messageformat caching](https://ngneat.github.io/transloco/docs/plugins/message-format#caching). Thank goes to [k3nsei](https://github.com/k3nsei).

## Transloco Persist Lang

- `@angular/core` peer dependency is now `>=v12`
Expand Down
47 changes: 29 additions & 18 deletions docs/docs/plugins/message-format.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,34 +48,45 @@ By default, messageformat initializes _all_ locales. You could also provide the
```ts title="transloco-root.module.ts"
@NgModule({
imports: [
TranslocoMessageFormatModule.forRoot(
{
TranslocoMessageFormatModule.forRoot({
locales: 'en-GB'
}
)
})
],
...
})
export class TranslocoRootModule {}
```

The value for `locales` is either a string or an array of strings. The first locale is used as the default locale by messageformat. More info [here](https://messageformat.github.io/messageformat/MessageFormat).
The value for `locales` is either a string or an array of strings. The first locale is used as the default locale by messageformat. More info [here](https://messageformat.github.io/messageformat/api/core.messageformat._constructor_/).

## Advanced configuration
MessageFormat instances provide some methods to influence its behaviour, among them `addFormatters`, `setBiDiSupport`, and `setStrictNumberSign`. Learn about their meaning [here](https://messageformat.github.io/messageformat/MessageFormat)
MessageFormat instances provides some options to influence its behaviour, among them `customFormatters`, `biDiSupport`, and `strictNumberSign`. Learn about their meaning [here](https://messageformat.github.io/messageformat/api/core.messageformatoptions/)

This is how you would enable bi-directional support and add a custom formatter, for example:
```ts title="transloco-root.module.ts"
@NgModule({
imports: [
TranslocoMessageFormatModule.forRoot(
{
biDiSupport: true,
customFormatters: { upcase: v => v.toUpperCase() }
}
)
],
...
})
export class TranslocoRootModule {}
@NgModule({
imports: [
TranslocoMessageFormatModule.forRoot({
biDiSupport: true,
customFormatters: { upcase: v => v.toUpperCase() }
})
],
...
})
export class TranslocoRootModule {}
```

## Caching (from v3)
By default the messageformat compile output is cached to reduce computing times, you can disable caching by passing the `enableCache` option:

```ts title="transloco-root.module.ts"
@NgModule({
imports: [
TranslocoMessageFormatModule.forRoot({
enableCache: false,
})
],
...
})
export class TranslocoRootModule {}
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export type MFLocale = ConstructorParameters<typeof MessageFormat>[0];

export interface MessageformatConfig extends MessageFormatOptions<'string'> {
locales?: MFLocale;
enableCache?: boolean;
}
43 changes: 43 additions & 0 deletions libs/transloco-messageformat/src/lib/messageformat.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { MFLocale } from './messageformat.config';
import MessageFormat, {
MessageFormatOptions,
MessageFunction,
} from '@messageformat/core';

export type MFFactory = (
locales: MFLocale,
messageConfig: MessageFormatOptions<'string'>
) => MessageFormat;

export function defaultFactory(
locales: MFLocale,
messageConfig: MessageFormatOptions<'string'>
): MessageFormat {
return new MessageFormat<'string'>(locales, messageConfig);
}

export function cachedFactory(
locales: MFLocale,
messageConfig: MessageFormatOptions<'string'>
): MessageFormat {
const mf = defaultFactory(locales, messageConfig);
const original = mf.compile;
const cache = new Map<string, MessageFunction<'string'>>();
const localeKey = `__${locales?.toString() || MessageFormat.defaultLocale}__`;

mf.compile = function (messages: string): MessageFunction<'string'> {
const cacheKey = `${localeKey}${messages}`;
const cachedMsg = cache.get(cacheKey);

if (cachedMsg) {
return cachedMsg;
}

const msg = original.call(this, messages);
cache.set(cacheKey, msg);

return msg;
};

return mf;
}
104 changes: 58 additions & 46 deletions libs/transloco-messageformat/src/lib/messageformat.transpiler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,65 @@
import { MessageFormatTranspiler } from './messageformat.transpiler';
import { flatten, translocoConfig } from '@ngneat/transloco';
import { CustomFormatter } from '@messageformat/core';
import { MessageformatConfig } from './messageformat.config';

describe('MessageFormatTranspiler', () => {
const config = {};
describe('Cache enabled', () => {
const config = {};
assertParser(config);
});

describe('Cache disabled', () => {
const config = { enableCache: false };
assertParser(config);
});

it('should work with locales', () => {
const config = { locales: 'en-GB' };
const parser = new MessageFormatTranspiler(config);
const message =
'{count, plural, =0{No} one{A} other{Several}} {count, plural, one{word} other{words}}';

const result = parser.transpile(message, { count: 1 }, {});
expect(result).toBe('A word');
});

it('should use passed-in formatters', () => {
const formatters: { [key: string]: CustomFormatter } = {
prop: <T = Record<string, string>>(v: T, lc: any, p: string | null) =>
v[p as keyof T],
upcase: (v) => (v as string).toUpperCase(),
};
const messages = {
answer: 'Answer: {obj, prop, a}',
describe: 'This is {upper, upcase}.',
};

const parser = new MessageFormatTranspiler({
customFormatters: formatters,
});
const upper = parser.transpile(messages.describe, { upper: 'big' }, {});
expect(upper).toEqual('This is BIG.');

expect(
parser.transpile(messages.answer, { obj: { q: 3, a: 42 } }, {})
).toBe('Answer: 42');
});

it('should switch locale in runtime', () => {
const config = { locales: 'en' };
const parser = new MessageFormatTranspiler(config);
const polishKey =
'{count, plural, =0 {none} one {# thing} few {# things} many {# things} other {# things}}';
const params = { count: 2 };

expect(() => parser.transpile(polishKey, params, {})).toThrowError();
parser.setLocale('pl');
expect(parser.transpile(polishKey, params, {})).toBe('2 things');
});
});

function assertParser(config: MessageformatConfig) {
const parser = new MessageFormatTranspiler(config);
const parserWithCustomInterpolation = new MessageFormatTranspiler(
config,
Expand Down Expand Up @@ -154,48 +210,4 @@ describe('MessageFormatTranspiler', () => {
},
});
});

it('should work with locales', () => {
const config = { locales: 'en-GB' };
const parser = new MessageFormatTranspiler(config);
const message =
'{count, plural, =0{No} one{A} other{Several}} {count, plural, one{word} other{words}}';

const result = parser.transpile(message, { count: 1 }, {});
expect(result).toBe('A word');
});

it('should use passed-in formatters', () => {
const formatters: { [key: string]: CustomFormatter } = {
prop: <T = Record<string, string>>(v: T, lc: any, p: string | null) =>
v[p as keyof T],
upcase: (v) => (v as string).toUpperCase(),
};
const messages = {
answer: 'Answer: {obj, prop, a}',
describe: 'This is {upper, upcase}.',
};

const parser = new MessageFormatTranspiler({
customFormatters: formatters,
});
const upper = parser.transpile(messages.describe, { upper: 'big' }, {});
expect(upper).toEqual('This is BIG.');

expect(
parser.transpile(messages.answer, { obj: { q: 3, a: 42 } }, {})
).toBe('Answer: 42');
});

it('should switch locale in runtime', () => {
const config = { locales: 'en' };
const parser = new MessageFormatTranspiler(config);
const polishKey =
'{count, plural, =0 {none} one {# thing} few {# things} many {# things} other {# things}}';
const params = { count: 2 };

expect(() => parser.transpile(polishKey, params, {})).toThrowError();
parser.setLocale('pl');
expect(parser.transpile(polishKey, params, {})).toBe('2 things');
});
});
}
24 changes: 14 additions & 10 deletions libs/transloco-messageformat/src/lib/messageformat.transpiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,17 @@ import {
MFLocale,
TRANSLOCO_MESSAGE_FORMAT_CONFIG,
} from './messageformat.config';

function mfFactory(
locales: MFLocale,
messageConfig: MessageFormatOptions<'string'>
): MessageFormat {
return new MessageFormat<'string'>(locales, messageConfig);
}
import {
cachedFactory,
defaultFactory,
MFFactory,
} from './messageformat.factory';

@Injectable()
export class MessageFormatTranspiler extends DefaultTranspiler {
private messageFormat: MessageFormat;
private readonly messageConfig: MessageFormatOptions<'string'>;
private readonly mfFactory: MFFactory;

constructor(
@Optional()
Expand All @@ -36,9 +35,14 @@ export class MessageFormatTranspiler extends DefaultTranspiler {
@Optional() @Inject(TRANSLOCO_CONFIG) userConfig?: TranslocoConfig
) {
super(userConfig);
const { locales, ...messageConfig } = { locales: null, ...config };
const {
locales,
enableCache = true,
...messageConfig
} = { locales: null, ...config };
this.messageConfig = messageConfig;
this.messageFormat = mfFactory(locales, messageConfig);
this.mfFactory = enableCache ? cachedFactory : defaultFactory;
this.messageFormat = this.mfFactory(locales, messageConfig);
}

transpile(value: any, params: HashMap = {}, translation: Translation): any {
Expand Down Expand Up @@ -70,6 +74,6 @@ export class MessageFormatTranspiler extends DefaultTranspiler {
}

setLocale(locale: MFLocale) {
this.messageFormat = mfFactory(locale, this.messageConfig);
this.messageFormat = this.mfFactory(locale, this.messageConfig);
}
}
2 changes: 1 addition & 1 deletion libs/transloco-messageformat/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"module": "ES2015",
"types": ["jasmine", "node"]
},
"files": ["src/test-setup.ts"],
Expand Down
41 changes: 0 additions & 41 deletions libs/transloco/src/lib/tests/config-provider.spec.ts

This file was deleted.

0 comments on commit 773bc5c

Please sign in to comment.