@hazeljs/i18n 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -0
- package/README.md +292 -0
- package/dist/decorators/lang.decorator.d.ts +33 -0
- package/dist/decorators/lang.decorator.d.ts.map +1 -0
- package/dist/decorators/lang.decorator.js +53 -0
- package/dist/decorators/lang.decorator.test.d.ts +2 -0
- package/dist/decorators/lang.decorator.test.d.ts.map +1 -0
- package/dist/decorators/lang.decorator.test.js +61 -0
- package/dist/i18n.interceptor.d.ts +29 -0
- package/dist/i18n.interceptor.d.ts.map +1 -0
- package/dist/i18n.interceptor.js +52 -0
- package/dist/i18n.interceptor.test.d.ts +2 -0
- package/dist/i18n.interceptor.test.d.ts.map +1 -0
- package/dist/i18n.interceptor.test.js +126 -0
- package/dist/i18n.middleware.d.ts +59 -0
- package/dist/i18n.middleware.d.ts.map +1 -0
- package/dist/i18n.middleware.js +126 -0
- package/dist/i18n.middleware.test.d.ts +2 -0
- package/dist/i18n.middleware.test.d.ts.map +1 -0
- package/dist/i18n.middleware.test.js +239 -0
- package/dist/i18n.module.d.ts +73 -0
- package/dist/i18n.module.d.ts.map +1 -0
- package/dist/i18n.module.js +154 -0
- package/dist/i18n.module.test.d.ts +2 -0
- package/dist/i18n.module.test.d.ts.map +1 -0
- package/dist/i18n.module.test.js +162 -0
- package/dist/i18n.service.d.ts +112 -0
- package/dist/i18n.service.d.ts.map +1 -0
- package/dist/i18n.service.js +228 -0
- package/dist/i18n.service.test.d.ts +2 -0
- package/dist/i18n.service.test.d.ts.map +1 -0
- package/dist/i18n.service.test.js +297 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/translation.loader.d.ts +20 -0
- package/dist/translation.loader.d.ts.map +1 -0
- package/dist/translation.loader.js +51 -0
- package/dist/translation.loader.test.d.ts +2 -0
- package/dist/translation.loader.test.d.ts.map +1 -0
- package/dist/translation.loader.test.js +89 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +51 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { I18nService } from './i18n.service';
|
|
2
|
+
import { LocaleMiddleware } from './i18n.middleware';
|
|
3
|
+
import { I18nOptions } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* I18nModule — internationalization module for HazelJS.
|
|
6
|
+
*
|
|
7
|
+
* Register once in your root module using `I18nModule.forRoot()`. The module
|
|
8
|
+
* loads all `<locale>.json` files from the configured `translationsPath` at
|
|
9
|
+
* startup and makes `I18nService` and `LocaleMiddleware` available for
|
|
10
|
+
* injection across the entire application.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* \@HazelModule({
|
|
15
|
+
* imports: [
|
|
16
|
+
* I18nModule.forRoot({
|
|
17
|
+
* defaultLocale: 'en',
|
|
18
|
+
* fallbackLocale: 'en',
|
|
19
|
+
* translationsPath: './translations',
|
|
20
|
+
* detection: ['query', 'cookie', 'header'],
|
|
21
|
+
* }),
|
|
22
|
+
* ],
|
|
23
|
+
* controllers: [AppController],
|
|
24
|
+
* })
|
|
25
|
+
* export class AppModule {}
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class I18nModule {
|
|
29
|
+
/**
|
|
30
|
+
* Synchronous configuration. Options are resolved eagerly, and
|
|
31
|
+
* translations are loaded before the service instance is made available.
|
|
32
|
+
*/
|
|
33
|
+
static forRoot(options?: I18nOptions): {
|
|
34
|
+
module: typeof I18nModule;
|
|
35
|
+
providers: Array<{
|
|
36
|
+
provide: string | typeof I18nService | typeof LocaleMiddleware;
|
|
37
|
+
useFactory?: (...args: unknown[]) => unknown;
|
|
38
|
+
useValue?: unknown;
|
|
39
|
+
inject?: string[];
|
|
40
|
+
}>;
|
|
41
|
+
exports: Array<typeof I18nService | typeof LocaleMiddleware>;
|
|
42
|
+
global: boolean;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Asynchronous configuration — use when options must be resolved from the
|
|
46
|
+
* container (e.g. from ConfigService).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* I18nModule.forRootAsync({
|
|
51
|
+
* useFactory: (config: ConfigService) => ({
|
|
52
|
+
* defaultLocale: config.get('LOCALE', 'en'),
|
|
53
|
+
* translationsPath: config.get('TRANSLATIONS_PATH', './translations'),
|
|
54
|
+
* }),
|
|
55
|
+
* inject: [ConfigService],
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
static forRootAsync(options: {
|
|
60
|
+
useFactory: (...args: any[]) => I18nOptions;
|
|
61
|
+
inject?: unknown[];
|
|
62
|
+
}): {
|
|
63
|
+
module: typeof I18nModule;
|
|
64
|
+
providers: Array<{
|
|
65
|
+
provide: string | typeof I18nService | typeof LocaleMiddleware;
|
|
66
|
+
useFactory: (...args: unknown[]) => unknown;
|
|
67
|
+
inject?: unknown[];
|
|
68
|
+
}>;
|
|
69
|
+
exports: Array<typeof I18nService | typeof LocaleMiddleware>;
|
|
70
|
+
global: boolean;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=i18n.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.module.d.ts","sourceRoot":"","sources":["../src/i18n.module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAuB,MAAM,SAAS,CAAC;AAuB3D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAIa,UAAU;IACrB;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,WAAgB,GAAG;QACzC,MAAM,EAAE,OAAO,UAAU,CAAC;QAC1B,SAAS,EAAE,KAAK,CAAC;YACf,OAAO,EAAE,MAAM,GAAG,OAAO,WAAW,GAAG,OAAO,gBAAgB,CAAC;YAC/D,UAAU,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;YAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;YACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;SACnB,CAAC,CAAC;QACH,OAAO,EAAE,KAAK,CAAC,OAAO,WAAW,GAAG,OAAO,gBAAgB,CAAC,CAAC;QAC7D,MAAM,EAAE,OAAO,CAAC;KACjB;IAmCD;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE;QAE3B,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,WAAW,CAAC;QAC5C,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;KACpB,GAAG;QACF,MAAM,EAAE,OAAO,UAAU,CAAC;QAC1B,SAAS,EAAE,KAAK,CAAC;YACf,OAAO,EAAE,MAAM,GAAG,OAAO,WAAW,GAAG,OAAO,gBAAgB,CAAC;YAC/D,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;YAC5C,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;SACpB,CAAC,CAAC;QACH,OAAO,EAAE,KAAK,CAAC,OAAO,WAAW,GAAG,OAAO,gBAAgB,CAAC,CAAC;QAC7D,MAAM,EAAE,OAAO,CAAC;KACjB;CAoCF"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var I18nModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.I18nModule = void 0;
|
|
11
|
+
const core_1 = require("@hazeljs/core");
|
|
12
|
+
const i18n_service_1 = require("./i18n.service");
|
|
13
|
+
const translation_loader_1 = require("./translation.loader");
|
|
14
|
+
const i18n_middleware_1 = require("./i18n.middleware");
|
|
15
|
+
const path_1 = require("path");
|
|
16
|
+
const I18N_OPTIONS_TOKEN = 'I18N_OPTIONS';
|
|
17
|
+
/**
|
|
18
|
+
* Resolves user-supplied options and fills in all defaults.
|
|
19
|
+
*/
|
|
20
|
+
function resolveOptions(options) {
|
|
21
|
+
const defaultLocale = options.defaultLocale ?? 'en';
|
|
22
|
+
return {
|
|
23
|
+
defaultLocale,
|
|
24
|
+
fallbackLocale: options.fallbackLocale ?? defaultLocale,
|
|
25
|
+
translationsPath: options.translationsPath
|
|
26
|
+
? (0, path_1.resolve)(process.cwd(), options.translationsPath)
|
|
27
|
+
: (0, path_1.resolve)(process.cwd(), 'translations'),
|
|
28
|
+
detection: options.detection ?? ['query', 'cookie', 'header'],
|
|
29
|
+
queryParam: options.queryParam ?? 'lang',
|
|
30
|
+
cookieName: options.cookieName ?? 'locale',
|
|
31
|
+
isGlobal: options.isGlobal ?? true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* I18nModule — internationalization module for HazelJS.
|
|
36
|
+
*
|
|
37
|
+
* Register once in your root module using `I18nModule.forRoot()`. The module
|
|
38
|
+
* loads all `<locale>.json` files from the configured `translationsPath` at
|
|
39
|
+
* startup and makes `I18nService` and `LocaleMiddleware` available for
|
|
40
|
+
* injection across the entire application.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* \@HazelModule({
|
|
45
|
+
* imports: [
|
|
46
|
+
* I18nModule.forRoot({
|
|
47
|
+
* defaultLocale: 'en',
|
|
48
|
+
* fallbackLocale: 'en',
|
|
49
|
+
* translationsPath: './translations',
|
|
50
|
+
* detection: ['query', 'cookie', 'header'],
|
|
51
|
+
* }),
|
|
52
|
+
* ],
|
|
53
|
+
* controllers: [AppController],
|
|
54
|
+
* })
|
|
55
|
+
* export class AppModule {}
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
let I18nModule = I18nModule_1 = class I18nModule {
|
|
59
|
+
/**
|
|
60
|
+
* Synchronous configuration. Options are resolved eagerly, and
|
|
61
|
+
* translations are loaded before the service instance is made available.
|
|
62
|
+
*/
|
|
63
|
+
static forRoot(options = {}) {
|
|
64
|
+
const resolved = resolveOptions(options);
|
|
65
|
+
return {
|
|
66
|
+
module: I18nModule_1,
|
|
67
|
+
providers: [
|
|
68
|
+
{
|
|
69
|
+
provide: I18N_OPTIONS_TOKEN,
|
|
70
|
+
useValue: resolved,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
provide: i18n_service_1.I18nService,
|
|
74
|
+
useFactory: (...args) => {
|
|
75
|
+
const opts = args[0];
|
|
76
|
+
const store = translation_loader_1.TranslationLoader.load(opts.translationsPath);
|
|
77
|
+
const service = new i18n_service_1.I18nService();
|
|
78
|
+
service.initialize(store, opts);
|
|
79
|
+
return service;
|
|
80
|
+
},
|
|
81
|
+
inject: [I18N_OPTIONS_TOKEN],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
provide: i18n_middleware_1.LocaleMiddleware,
|
|
85
|
+
useFactory: (...args) => {
|
|
86
|
+
const opts = args[0];
|
|
87
|
+
return new i18n_middleware_1.LocaleMiddleware(opts);
|
|
88
|
+
},
|
|
89
|
+
inject: [I18N_OPTIONS_TOKEN],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
exports: [i18n_service_1.I18nService, i18n_middleware_1.LocaleMiddleware],
|
|
93
|
+
global: resolved.isGlobal,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Asynchronous configuration — use when options must be resolved from the
|
|
98
|
+
* container (e.g. from ConfigService).
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* I18nModule.forRootAsync({
|
|
103
|
+
* useFactory: (config: ConfigService) => ({
|
|
104
|
+
* defaultLocale: config.get('LOCALE', 'en'),
|
|
105
|
+
* translationsPath: config.get('TRANSLATIONS_PATH', './translations'),
|
|
106
|
+
* }),
|
|
107
|
+
* inject: [ConfigService],
|
|
108
|
+
* })
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
static forRootAsync(options) {
|
|
112
|
+
return {
|
|
113
|
+
module: I18nModule_1,
|
|
114
|
+
providers: [
|
|
115
|
+
{
|
|
116
|
+
provide: I18N_OPTIONS_TOKEN,
|
|
117
|
+
useFactory: (...args) => {
|
|
118
|
+
const userOptions = options.useFactory(...args);
|
|
119
|
+
return resolveOptions(userOptions);
|
|
120
|
+
},
|
|
121
|
+
inject: options.inject ?? [],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
provide: i18n_service_1.I18nService,
|
|
125
|
+
useFactory: (...args) => {
|
|
126
|
+
const opts = args[0];
|
|
127
|
+
const store = translation_loader_1.TranslationLoader.load(opts.translationsPath);
|
|
128
|
+
const service = new i18n_service_1.I18nService();
|
|
129
|
+
service.initialize(store, opts);
|
|
130
|
+
return service;
|
|
131
|
+
},
|
|
132
|
+
inject: [I18N_OPTIONS_TOKEN],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
provide: i18n_middleware_1.LocaleMiddleware,
|
|
136
|
+
useFactory: (...args) => {
|
|
137
|
+
const opts = args[0];
|
|
138
|
+
return new i18n_middleware_1.LocaleMiddleware(opts);
|
|
139
|
+
},
|
|
140
|
+
inject: [I18N_OPTIONS_TOKEN],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
exports: [i18n_service_1.I18nService, i18n_middleware_1.LocaleMiddleware],
|
|
144
|
+
global: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
exports.I18nModule = I18nModule;
|
|
149
|
+
exports.I18nModule = I18nModule = I18nModule_1 = __decorate([
|
|
150
|
+
(0, core_1.HazelModule)({
|
|
151
|
+
providers: [],
|
|
152
|
+
exports: [],
|
|
153
|
+
})
|
|
154
|
+
], I18nModule);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.module.test.d.ts","sourceRoot":"","sources":["../src/i18n.module.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
jest.mock('@hazeljs/core', () => ({
|
|
4
|
+
__esModule: true,
|
|
5
|
+
HazelModule: () => () => undefined,
|
|
6
|
+
Service: () => () => undefined,
|
|
7
|
+
}));
|
|
8
|
+
jest.mock('./translation.loader', () => ({
|
|
9
|
+
TranslationLoader: {
|
|
10
|
+
load: jest.fn().mockReturnValue(new Map()),
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
const i18n_module_1 = require("./i18n.module");
|
|
14
|
+
const i18n_service_1 = require("./i18n.service");
|
|
15
|
+
const i18n_middleware_1 = require("./i18n.middleware");
|
|
16
|
+
const translation_loader_1 = require("./translation.loader");
|
|
17
|
+
const mockLoad = translation_loader_1.TranslationLoader.load;
|
|
18
|
+
describe('I18nModule', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
mockLoad.mockReturnValue(new Map());
|
|
22
|
+
});
|
|
23
|
+
describe('forRoot()', () => {
|
|
24
|
+
it('returns module reference', () => {
|
|
25
|
+
const result = i18n_module_1.I18nModule.forRoot();
|
|
26
|
+
expect(result.module).toBe(i18n_module_1.I18nModule);
|
|
27
|
+
});
|
|
28
|
+
it('exports I18nService and LocaleMiddleware', () => {
|
|
29
|
+
const result = i18n_module_1.I18nModule.forRoot();
|
|
30
|
+
expect(result.exports).toContain(i18n_service_1.I18nService);
|
|
31
|
+
expect(result.exports).toContain(i18n_middleware_1.LocaleMiddleware);
|
|
32
|
+
});
|
|
33
|
+
it('sets global to true by default', () => {
|
|
34
|
+
const result = i18n_module_1.I18nModule.forRoot();
|
|
35
|
+
expect(result.global).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
it('sets global to false when isGlobal: false', () => {
|
|
38
|
+
const result = i18n_module_1.I18nModule.forRoot({ isGlobal: false });
|
|
39
|
+
expect(result.global).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
it('provides I18N_OPTIONS_TOKEN, I18nService, and LocaleMiddleware', () => {
|
|
42
|
+
const result = i18n_module_1.I18nModule.forRoot();
|
|
43
|
+
expect(result.providers).toHaveLength(3);
|
|
44
|
+
});
|
|
45
|
+
it('uses custom defaultLocale', () => {
|
|
46
|
+
const result = i18n_module_1.I18nModule.forRoot({ defaultLocale: 'fr' });
|
|
47
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
48
|
+
expect(optionsProvider?.useValue?.defaultLocale).toBe('fr');
|
|
49
|
+
});
|
|
50
|
+
it('sets fallbackLocale to defaultLocale when not specified', () => {
|
|
51
|
+
const result = i18n_module_1.I18nModule.forRoot({ defaultLocale: 'de' });
|
|
52
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
53
|
+
expect(optionsProvider?.useValue?.fallbackLocale).toBe('de');
|
|
54
|
+
});
|
|
55
|
+
it('sets custom fallbackLocale when specified', () => {
|
|
56
|
+
const result = i18n_module_1.I18nModule.forRoot({ defaultLocale: 'fr', fallbackLocale: 'en' });
|
|
57
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
58
|
+
expect(optionsProvider?.useValue?.fallbackLocale).toBe('en');
|
|
59
|
+
});
|
|
60
|
+
it('I18nService factory creates and initializes service', () => {
|
|
61
|
+
const store = new Map([['en', { hello: 'Hello' }]]);
|
|
62
|
+
mockLoad.mockReturnValue(store);
|
|
63
|
+
const result = i18n_module_1.I18nModule.forRoot({ translationsPath: './trans' });
|
|
64
|
+
const serviceProvider = result.providers.find((p) => p.provide === i18n_service_1.I18nService);
|
|
65
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
66
|
+
const opts = optionsProvider?.useValue;
|
|
67
|
+
const service = serviceProvider?.useFactory?.(opts);
|
|
68
|
+
expect(service).toBeInstanceOf(i18n_service_1.I18nService);
|
|
69
|
+
expect(mockLoad).toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
it('LocaleMiddleware factory creates middleware', () => {
|
|
72
|
+
const result = i18n_module_1.I18nModule.forRoot();
|
|
73
|
+
const mwProvider = result.providers.find((p) => p.provide === i18n_middleware_1.LocaleMiddleware);
|
|
74
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
75
|
+
const opts = optionsProvider?.useValue;
|
|
76
|
+
const mw = mwProvider?.useFactory?.(opts);
|
|
77
|
+
expect(mw).toBeInstanceOf(i18n_middleware_1.LocaleMiddleware);
|
|
78
|
+
});
|
|
79
|
+
it('uses default translationsPath "translations" when not specified', () => {
|
|
80
|
+
const result = i18n_module_1.I18nModule.forRoot();
|
|
81
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
82
|
+
const opts = optionsProvider?.useValue;
|
|
83
|
+
expect(typeof opts?.translationsPath).toBe('string');
|
|
84
|
+
expect((opts?.translationsPath).endsWith('translations')).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
it('applies default detection strategies', () => {
|
|
87
|
+
const result = i18n_module_1.I18nModule.forRoot();
|
|
88
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
89
|
+
const opts = optionsProvider?.useValue;
|
|
90
|
+
expect(opts?.detection).toEqual(['query', 'cookie', 'header']);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('forRootAsync()', () => {
|
|
94
|
+
it('returns module reference', () => {
|
|
95
|
+
const result = i18n_module_1.I18nModule.forRootAsync({ useFactory: () => ({}) });
|
|
96
|
+
expect(result.module).toBe(i18n_module_1.I18nModule);
|
|
97
|
+
});
|
|
98
|
+
it('exports I18nService and LocaleMiddleware', () => {
|
|
99
|
+
const result = i18n_module_1.I18nModule.forRootAsync({ useFactory: () => ({}) });
|
|
100
|
+
expect(result.exports).toContain(i18n_service_1.I18nService);
|
|
101
|
+
expect(result.exports).toContain(i18n_middleware_1.LocaleMiddleware);
|
|
102
|
+
});
|
|
103
|
+
it('sets global to true', () => {
|
|
104
|
+
const result = i18n_module_1.I18nModule.forRootAsync({ useFactory: () => ({}) });
|
|
105
|
+
expect(result.global).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
it('provides 3 providers', () => {
|
|
108
|
+
const result = i18n_module_1.I18nModule.forRootAsync({ useFactory: () => ({}) });
|
|
109
|
+
expect(result.providers).toHaveLength(3);
|
|
110
|
+
});
|
|
111
|
+
it('OPTIONS factory resolves and applies defaults', () => {
|
|
112
|
+
const result = i18n_module_1.I18nModule.forRootAsync({
|
|
113
|
+
useFactory: () => ({ defaultLocale: 'ja' }),
|
|
114
|
+
});
|
|
115
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
116
|
+
const resolved = optionsProvider?.useFactory?.();
|
|
117
|
+
expect(resolved.defaultLocale).toBe('ja');
|
|
118
|
+
});
|
|
119
|
+
it('passes inject args through to useFactory', () => {
|
|
120
|
+
const factory = jest.fn().mockReturnValue({ defaultLocale: 'ko' });
|
|
121
|
+
const result = i18n_module_1.I18nModule.forRootAsync({ useFactory: factory, inject: ['CONFIG'] });
|
|
122
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
123
|
+
optionsProvider?.useFactory?.('some-config-value');
|
|
124
|
+
expect(factory).toHaveBeenCalledWith('some-config-value');
|
|
125
|
+
});
|
|
126
|
+
it('I18nService factory creates and initializes service', () => {
|
|
127
|
+
const result = i18n_module_1.I18nModule.forRootAsync({ useFactory: () => ({}) });
|
|
128
|
+
const serviceProvider = result.providers.find((p) => p.provide === i18n_service_1.I18nService);
|
|
129
|
+
const resolvedOpts = {
|
|
130
|
+
defaultLocale: 'en',
|
|
131
|
+
fallbackLocale: 'en',
|
|
132
|
+
translationsPath: '/some/path',
|
|
133
|
+
detection: ['query'],
|
|
134
|
+
queryParam: 'lang',
|
|
135
|
+
cookieName: 'locale',
|
|
136
|
+
isGlobal: true,
|
|
137
|
+
};
|
|
138
|
+
const service = serviceProvider?.useFactory?.(resolvedOpts);
|
|
139
|
+
expect(service).toBeInstanceOf(i18n_service_1.I18nService);
|
|
140
|
+
});
|
|
141
|
+
it('LocaleMiddleware factory creates middleware', () => {
|
|
142
|
+
const result = i18n_module_1.I18nModule.forRootAsync({ useFactory: () => ({}) });
|
|
143
|
+
const mwProvider = result.providers.find((p) => p.provide === i18n_middleware_1.LocaleMiddleware);
|
|
144
|
+
const resolvedOpts = {
|
|
145
|
+
defaultLocale: 'en',
|
|
146
|
+
fallbackLocale: 'en',
|
|
147
|
+
translationsPath: '/some/path',
|
|
148
|
+
detection: ['query'],
|
|
149
|
+
queryParam: 'lang',
|
|
150
|
+
cookieName: 'locale',
|
|
151
|
+
isGlobal: true,
|
|
152
|
+
};
|
|
153
|
+
const mw = mwProvider?.useFactory?.(resolvedOpts);
|
|
154
|
+
expect(mw).toBeInstanceOf(i18n_middleware_1.LocaleMiddleware);
|
|
155
|
+
});
|
|
156
|
+
it('uses empty array for inject when not specified', () => {
|
|
157
|
+
const result = i18n_module_1.I18nModule.forRootAsync({ useFactory: () => ({}) });
|
|
158
|
+
const optionsProvider = result.providers.find((p) => p.provide === 'I18N_OPTIONS');
|
|
159
|
+
expect(optionsProvider?.inject).toEqual([]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { LocaleStore, TranslateOptions, ResolvedI18nOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The central translation and formatting service.
|
|
4
|
+
*
|
|
5
|
+
* Inject this service into any controller or provider to translate keys,
|
|
6
|
+
* apply interpolation and pluralization, and format numbers / dates / currency
|
|
7
|
+
* using the native Intl API — with zero external dependencies.
|
|
8
|
+
*/
|
|
9
|
+
export declare class I18nService {
|
|
10
|
+
/** Exposes Intl-based formatters as a nested namespace. */
|
|
11
|
+
readonly format: I18nFormatter;
|
|
12
|
+
private store;
|
|
13
|
+
private options;
|
|
14
|
+
constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Called by I18nModule after translations have been loaded from disk.
|
|
17
|
+
*/
|
|
18
|
+
initialize(store: LocaleStore, options: ResolvedI18nOptions): void;
|
|
19
|
+
/**
|
|
20
|
+
* Translate a dot-notation key for the given locale.
|
|
21
|
+
*
|
|
22
|
+
* @param key Dot-separated translation key, e.g. "errors.notFound".
|
|
23
|
+
* @param opts Optional count (plural), vars (interpolation) and locale override.
|
|
24
|
+
* @returns The translated string, or the key itself if not found.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* i18n.t('welcome', { vars: { name: 'Alice' } })
|
|
28
|
+
* // → "Welcome, Alice!" (from en.json: { "welcome": "Welcome, {name}!" })
|
|
29
|
+
*
|
|
30
|
+
* i18n.t('items', { count: 3, vars: { count: '3' } })
|
|
31
|
+
* // → "3 items" (from en.json: { "items": { "one": "1 item", "other": "{count} items" } })
|
|
32
|
+
*/
|
|
33
|
+
t(key: string, opts?: TranslateOptions): string;
|
|
34
|
+
/**
|
|
35
|
+
* Returns the current request-scoped locale, falling back to the default.
|
|
36
|
+
* When used with the LocaleMiddleware the locale is stored on the current
|
|
37
|
+
* async context; outside a request context the default locale is used.
|
|
38
|
+
*/
|
|
39
|
+
getCurrentLocale(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Returns all translation keys available for a given locale (flattened to
|
|
42
|
+
* dot-notation paths), useful for debugging and tooling.
|
|
43
|
+
*/
|
|
44
|
+
getKeys(locale?: string): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Returns all loaded locale codes.
|
|
47
|
+
*/
|
|
48
|
+
getLocales(): string[];
|
|
49
|
+
/**
|
|
50
|
+
* Checks whether a key exists for the given locale.
|
|
51
|
+
*/
|
|
52
|
+
has(key: string, locale?: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Traverses the translation map using a dot-notation key.
|
|
55
|
+
* Falls back to the fallbackLocale when the key is missing.
|
|
56
|
+
*/
|
|
57
|
+
private resolve;
|
|
58
|
+
private resolveInMap;
|
|
59
|
+
/**
|
|
60
|
+
* Given a translation value that may be a plain string or a plural object,
|
|
61
|
+
* returns the appropriate form.
|
|
62
|
+
*
|
|
63
|
+
* Uses the native Intl.PluralRules API, defaulting to "other" when
|
|
64
|
+
* the value is not a plural object.
|
|
65
|
+
*/
|
|
66
|
+
private selectPluralForm;
|
|
67
|
+
/**
|
|
68
|
+
* Replaces {placeholder} tokens in a string with values from vars.
|
|
69
|
+
*/
|
|
70
|
+
private interpolate;
|
|
71
|
+
private flattenKeys;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Exposes Intl-backed formatters, accessible as `i18nService.format.*`.
|
|
75
|
+
*/
|
|
76
|
+
export declare class I18nFormatter {
|
|
77
|
+
private readonly service;
|
|
78
|
+
constructor(service: I18nService);
|
|
79
|
+
/**
|
|
80
|
+
* Format a number using Intl.NumberFormat.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* i18n.format.number(1234567.89, 'de', { maximumFractionDigits: 2 })
|
|
84
|
+
* // → "1.234.567,89"
|
|
85
|
+
*/
|
|
86
|
+
number(value: number, locale?: string, opts?: Intl.NumberFormatOptions): string;
|
|
87
|
+
/**
|
|
88
|
+
* Format a date using Intl.DateTimeFormat.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* i18n.format.date(new Date(), 'fr', { dateStyle: 'long' })
|
|
92
|
+
* // → "4 mars 2026"
|
|
93
|
+
*/
|
|
94
|
+
date(value: Date | number, locale?: string, opts?: Intl.DateTimeFormatOptions): string;
|
|
95
|
+
/**
|
|
96
|
+
* Format a monetary value using Intl.NumberFormat with style 'currency'.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* i18n.format.currency(49.99, 'en', 'USD')
|
|
100
|
+
* // → "$49.99"
|
|
101
|
+
*/
|
|
102
|
+
currency(value: number, locale?: string, currency?: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Format a relative time using Intl.RelativeTimeFormat.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* i18n.format.relative(-3, 'day', 'en')
|
|
108
|
+
* // → "3 days ago"
|
|
109
|
+
*/
|
|
110
|
+
relative(value: number, unit: Intl.RelativeTimeFormatUnit, locale?: string, opts?: Intl.RelativeTimeFormatOptions): string;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=i18n.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.service.d.ts","sourceRoot":"","sources":["../src/i18n.service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EAGX,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAEjB;;;;;;GAMG;AACH,qBACa,WAAW;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAE/B,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,OAAO,CAAuB;;IAMtC;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAKlE;;;;;;;;;;;;;OAaG;IACH,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB,GAAG,MAAM;IASnD;;;;OAIG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE;IAOlC;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE;IAItB;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO;IAQ1C;;;OAGG;IACH,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,YAAY;IAiBpB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAyBxB;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,WAAW;CAYpB;AAED;;GAEG;AACH,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,WAAW;IAEjD;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,mBAAmB,GAAG,MAAM;IAK/E;;;;;;OAMG;IACH,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,qBAAqB,GAAG,MAAM;IAKtF;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,SAAQ,GAAG,MAAM;IAKlE;;;;;;OAMG;IACH,QAAQ,CACN,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,CAAC,sBAAsB,EACjC,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,IAAI,CAAC,yBAAyB,GACpC,MAAM;CAIV"}
|