@iamalond/nestjs-i18next 1.3.0 → 1.3.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/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<a href='https://img.shields.io/npm/v/@iamalond/nestjs-i18next'><img src="https://img.shields.io/npm/v/@iamalond/nestjs-i18next" alt="NPM Version" /></a>
|
|
11
11
|
<a href='https://img.shields.io/npm/l/@iamalond/nestjs-i18next'><img src="https://img.shields.io/npm/l/@iamalond/nestjs-i18next" alt="NPM License" /></a>
|
|
12
12
|
<a href='https://img.shields.io/npm/dm/@iamalond/nestjs-i18next'><img src="https://img.shields.io/npm/dm/@iamalond/nestjs-i18next" alt="NPM Downloads" /></a>
|
|
13
|
-
<a href='https://img.shields.io/github/last-commit/
|
|
13
|
+
<a href='https://img.shields.io/github/last-commit/iamAlond/nestjs-i18next'><img src="https://img.shields.io/github/last-commit/iamAlond/nestjs-i18next" alt="Last commit" /></a>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
## About the Project
|
|
@@ -30,16 +30,20 @@ The project was inspired by the popular `nestjs-i18n` module, but with a major e
|
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
33
|
+
**Node.js 18.0.0 or newer is required.**
|
|
34
|
+
|
|
33
35
|
```bash
|
|
34
|
-
npm install nestjs-i18next
|
|
35
|
-
yarn add nestjs-i18next
|
|
36
|
-
pnpm add nestjs-i18next
|
|
36
|
+
$ npm install @iamalond/nestjs-i18next
|
|
37
|
+
$ yarn add @iamalond/nestjs-i18next
|
|
38
|
+
$ pnpm add @iamalond/nestjs-i18next
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
## Usage
|
|
40
42
|
|
|
43
|
+
After installation, we can import the `I18nextModule` into the root `AppModule`:
|
|
44
|
+
|
|
41
45
|
```typescript
|
|
42
|
-
import {
|
|
46
|
+
import { I18nextModule } from '@iamalond/nestjs-i18next';
|
|
43
47
|
|
|
44
48
|
@Module({
|
|
45
49
|
imports: [
|
|
@@ -59,14 +63,115 @@ import { I18NextModule } from '@iamalond/nestjs-i18next';
|
|
|
59
63
|
export class AppModule {}
|
|
60
64
|
```
|
|
61
65
|
|
|
66
|
+
Then use `I18nextService` in your service's constructor:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { Injectable } from '@nestjs/common';
|
|
70
|
+
import { I18nextService } from '@iamalond/nestjs-i18next';
|
|
71
|
+
/**
|
|
72
|
+
* Import the types from the path you specified in 'generatedTypesPath'
|
|
73
|
+
* (e.g., src/i18n/index.d.ts)
|
|
74
|
+
*/
|
|
75
|
+
import { I18nTranslations } from './i18n';
|
|
76
|
+
|
|
77
|
+
@Injectable()
|
|
78
|
+
export class AppService {
|
|
79
|
+
// Pass I18nTranslations to the service for strict typing
|
|
80
|
+
constructor(private readonly i18n: I18nextService<I18nTranslations>) {}
|
|
81
|
+
|
|
82
|
+
getHello(): string {
|
|
83
|
+
// 1. Simple translation (autocomplete for keys, when start typing!)
|
|
84
|
+
const simple = this.i18n.t('common.hello');
|
|
85
|
+
|
|
86
|
+
// 2. Translation with arguments (Typescript will REQUIRE 'args' if variables exist)
|
|
87
|
+
const withArgs = this.i18n.t('common.welcome', {
|
|
88
|
+
args: { name: 'Matvey iamAlond' },
|
|
89
|
+
lang: 'en'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const apples = this.i18n.t('common.apples', {
|
|
93
|
+
args: { count: 5 }
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 3. You can also use the 'translate' alias
|
|
97
|
+
return this.i18n.translate('core.errors.notFound', {
|
|
98
|
+
defaultValue: 'Not Found',
|
|
99
|
+
debug: true
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Dynamic Translation Keys
|
|
106
|
+
|
|
107
|
+
If you need to use a dynamic key (e.g., based on an HTTP status code), use the I18nPath type. This type is also automatically generated in your generatedTypesPath and contains all valid translation paths.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { I18nPath, I18nTranslations } from './i18n';
|
|
111
|
+
|
|
112
|
+
@Injectable()
|
|
113
|
+
export class ErrorsService {
|
|
114
|
+
constructor(private readonly i18n: I18nextService<I18nTranslations>) {}
|
|
115
|
+
|
|
116
|
+
handleError(statusCode: number) {
|
|
117
|
+
/**
|
|
118
|
+
* Use 'as I18nPath' to tell TypeScript that this string
|
|
119
|
+
* is a valid translation key.
|
|
120
|
+
*/
|
|
121
|
+
return this.i18n.t(`errors.status.${statusCode}` as I18nPath, {
|
|
122
|
+
defaultValue: 'An unexpected error occurred'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
62
128
|
## File Structure
|
|
63
129
|
|
|
64
|
-
By default, the module uses the locale/ns.json structure. With subfolders: true, you can use recursive nesting. Keys will be resolved as namespace.subfolder.key
|
|
130
|
+
By default, the module uses the locale/ns.json structure. With subfolders: true, you can use recursive nesting. Keys will be resolved as `namespace.subfolder.key`. You can also combine both of these approaches.
|
|
65
131
|
|
|
66
132
|
```bash
|
|
67
133
|
src/i18n/
|
|
68
134
|
├── en/
|
|
69
135
|
│ ├── common.json
|
|
70
|
-
│ └──
|
|
71
|
-
│ └── errors.json <-- key will be "
|
|
136
|
+
│ └── core/
|
|
137
|
+
│ └── errors.json <-- key will be "core.errors.invalid_password"
|
|
138
|
+
└── ru/
|
|
139
|
+
├── common.json
|
|
140
|
+
└── ...
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Examples
|
|
144
|
+
|
|
145
|
+
To support the examples above, your translation directory should look like this:
|
|
146
|
+
|
|
147
|
+
`src/i18n/en/common.json`
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"hello": "Hello!",
|
|
152
|
+
"welcome": "Welcome, {{name}}!",
|
|
153
|
+
"apples": "{count, plural, =0 {No apples} one {# apple} =3 {three apples} other {# apples}}"
|
|
154
|
+
}
|
|
72
155
|
```
|
|
156
|
+
|
|
157
|
+
`src/i18n/en/core/errors.json`
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"invalid_password": "Invalid password",
|
|
162
|
+
"notFound": "Resource not found",
|
|
163
|
+
"status": {
|
|
164
|
+
"400": "Bad request",
|
|
165
|
+
"401": "Unauthorized",
|
|
166
|
+
"403": "Forbidden",
|
|
167
|
+
"404": "Not found",
|
|
168
|
+
"500": "Internal server error"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Key Resolution Rules
|
|
174
|
+
|
|
175
|
+
- Simple file: common.json -> key starts with common (e.g., common.hello).
|
|
176
|
+
- Subfolders: core/errors.json -> key starts with core.errors (e.g., core.errors.status.404).
|
|
177
|
+
- Arguments: Any string containing {{var}} or ICU structures like {count, plural, ...} will automatically require args in your TypeScript code.
|
package/dist/nestjs-i18next.d.ts
CHANGED
|
@@ -9,16 +9,16 @@ export type I18nextModuleOptions = {
|
|
|
9
9
|
throwOnMissingKey?: boolean;
|
|
10
10
|
generatedTypesPath?: string;
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
type BaseOptions = {
|
|
13
13
|
lang?: string;
|
|
14
14
|
defaultValue?: string;
|
|
15
15
|
debug?: boolean;
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
|
+
export type TranslateOptions<P, T> = Extract<T, {
|
|
17
18
|
key: P;
|
|
18
19
|
}> extends {
|
|
19
20
|
args: infer A;
|
|
20
|
-
} ? {
|
|
21
|
+
} ? BaseOptions & {
|
|
21
22
|
args: A;
|
|
22
|
-
} :
|
|
23
|
-
|
|
24
|
-
});
|
|
23
|
+
} : BaseOptions | undefined;
|
|
24
|
+
export {};
|
|
@@ -104,11 +104,6 @@ export type I18nTranslations = ${this.generateUnionType(schema)};
|
|
|
104
104
|
|
|
105
105
|
/* prettier-ignore */
|
|
106
106
|
export type I18nPath = I18nTranslations['key'];
|
|
107
|
-
|
|
108
|
-
/* prettier-ignore */
|
|
109
|
-
export type ExtractArgs<P extends I18nPath> = Extract<I18nTranslations, { key: P }> extends { args: infer A }
|
|
110
|
-
? A
|
|
111
|
-
: never;
|
|
112
107
|
`;
|
|
113
108
|
fs_1.default.mkdirSync(path_1.default.dirname(outputPath), {
|
|
114
109
|
recursive: true
|
|
@@ -17,8 +17,16 @@ export declare class I18nextService<T extends {
|
|
|
17
17
|
getSupportedLanguages(): string[];
|
|
18
18
|
getTranslations(): Record<string, any>;
|
|
19
19
|
getTranslationsForNamespace(lang: string): any;
|
|
20
|
-
translate<P extends T['key']>(key: P,
|
|
21
|
-
|
|
20
|
+
translate<P extends T['key']>(key: P, ...params: Extract<T, {
|
|
21
|
+
key: P;
|
|
22
|
+
}> extends {
|
|
23
|
+
args: any;
|
|
24
|
+
} ? [options: TranslateOptions<P, T>] : [options?: TranslateOptions<P, T>]): string;
|
|
25
|
+
t<P extends T['key']>(key: P, ...params: Extract<T, {
|
|
26
|
+
key: P;
|
|
27
|
+
}> extends {
|
|
28
|
+
args: any;
|
|
29
|
+
} ? [options: TranslateOptions<P, T>] : [options?: TranslateOptions<P, T>]): string;
|
|
22
30
|
private getValueByKey;
|
|
23
31
|
private replaceArgs;
|
|
24
32
|
private handleMissingKey;
|
|
@@ -46,11 +46,11 @@ let I18nextService = I18nextService_1 = class I18nextService {
|
|
|
46
46
|
getTranslationsForNamespace(lang) {
|
|
47
47
|
return this.currentTranslations[lang];
|
|
48
48
|
}
|
|
49
|
-
translate(key,
|
|
50
|
-
return this.t(key,
|
|
49
|
+
translate(key, ...params) {
|
|
50
|
+
return this.t(key, ...params);
|
|
51
51
|
}
|
|
52
|
-
t(key,
|
|
53
|
-
const { lang, debug, defaultValue } =
|
|
52
|
+
t(key, ...params) {
|
|
53
|
+
const { lang, debug, defaultValue, ...options } = params[0] || {};
|
|
54
54
|
const fallback = this.options.fallbackLanguage;
|
|
55
55
|
let result = this.getValueByKey(key, lang ?? fallback);
|
|
56
56
|
if (result === undefined && lang && lang !== fallback) {
|
package/package.json
CHANGED