create-forgeon 0.1.27 → 0.1.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-forgeon",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -140,7 +140,7 @@ describe('addModule', () => {
140
140
  const appTsx = fs.readFileSync(path.join(projectRoot, 'apps', 'web', 'src', 'App.tsx'), 'utf8');
141
141
  assert.match(appTsx, /@forgeon\/i18n-web/);
142
142
  assert.match(appTsx, /react-i18next/);
143
- assert.match(appTsx, /checkApiHealth/);
143
+ assert.match(appTsx, /ui:labels\.language/);
144
144
 
145
145
  const i18nWebPackage = fs.readFileSync(
146
146
  path.join(projectRoot, 'packages', 'i18n-web', 'package.json'),
@@ -184,13 +184,14 @@ describe('addModule', () => {
184
184
  const enCommon = JSON.parse(
185
185
  fs.readFileSync(path.join(projectRoot, 'resources', 'i18n', 'en', 'common.json'), 'utf8'),
186
186
  );
187
- assert.equal(enCommon.checkApiHealth, 'Check API health');
188
- assert.equal(enCommon.languages.english, 'English');
187
+ assert.equal(enCommon.actions.ok, 'OK');
188
+ assert.equal(enCommon.nav.next, 'Next');
189
189
 
190
190
  const enErrors = JSON.parse(
191
191
  fs.readFileSync(path.join(projectRoot, 'resources', 'i18n', 'en', 'errors.json'), 'utf8'),
192
192
  );
193
- assert.equal(enErrors.notFound, 'Resource not found');
193
+ assert.equal(enErrors.http.NOT_FOUND, 'Resource not found');
194
+ assert.equal(enErrors.validation.VALIDATION_ERROR, 'Validation error');
194
195
 
195
196
  const webPackage = fs.readFileSync(path.join(projectRoot, 'apps', 'web', 'package.json'), 'utf8');
196
197
  assert.match(webPackage, /"i18next":/);
@@ -202,6 +203,7 @@ describe('addModule', () => {
202
203
  const i18nTs = fs.readFileSync(path.join(projectRoot, 'apps', 'web', 'src', 'i18n.ts'), 'utf8');
203
204
  assert.match(i18nTs, /initReactI18next/);
204
205
  assert.match(i18nTs, /\.\.\/\.\.\/\.\.\/resources\/i18n\/en\/common\.json/);
206
+ assert.match(i18nTs, /\.\.\/\.\.\/\.\.\/resources\/i18n\/en\/ui\.json/);
205
207
  assert.doesNotMatch(i18nTs, /I18N_DEFAULT_LANG/);
206
208
 
207
209
  const rootPackage = fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8');
@@ -1,6 +1,6 @@
1
1
  import { IsNotEmpty } from 'class-validator';
2
2
 
3
- export class EchoQueryDto {
4
- @IsNotEmpty({ message: 'validation.required' })
5
- value!: string;
6
- }
3
+ export class EchoQueryDto {
4
+ @IsNotEmpty({ message: 'validation.generic.required' })
5
+ value!: string;
6
+ }
@@ -13,19 +13,19 @@ export class HealthController {
13
13
  getHealth(@Query('lang') lang?: string) {
14
14
  return {
15
15
  status: 'ok',
16
- message: this.translate('common.ok', lang),
17
- i18n: this.translate('common.languages.english', lang),
16
+ message: this.translate('common.actions.ok', lang),
17
+ i18n: 'en',
18
18
  };
19
19
  }
20
20
 
21
21
  @Get('error')
22
22
  getErrorProbe(@Query('lang') lang?: string) {
23
23
  throw new ConflictException({
24
- message: this.translate('errors.emailAlreadyExists', lang),
24
+ message: this.translate('errors.http.CONFLICT', lang),
25
25
  details: {
26
26
  feature: 'core-errors',
27
27
  probeId: 'health.error',
28
- probe: this.translate('common.probes.error', lang),
28
+ probe: 'Error envelope probe',
29
29
  },
30
30
  });
31
31
  }
@@ -33,7 +33,7 @@ export class HealthController {
33
33
  @Get('validation')
34
34
  getValidationProbe(@Query('value') value?: string, @Query('lang') lang?: string) {
35
35
  if (!value || value.trim().length === 0) {
36
- const translatedMessage = this.translate('validation.required', lang);
36
+ const translatedMessage = this.translate('validation.generic.required', lang);
37
37
  throw new BadRequestException({
38
38
  message: translatedMessage,
39
39
  details: [{ field: 'value', message: translatedMessage }],
@@ -64,3 +64,5 @@ Must contain:
64
64
  - Contracts package exports are stable from `dist/index` entrypoint.
65
65
  - Module has docs under `docs/AI/MODULES/<module-id>.md`.
66
66
  - If module behavior can be runtime-checked, it also includes API+Web probe hooks (see `docs/AI/MODULE_CHECKS.md`).
67
+ - If i18n is enabled, module-specific namespaces must be created and wired for both API and web.
68
+ - If module is added before i18n, namespace templates must still be prepared and applied when i18n is installed later.
@@ -0,0 +1,171 @@
1
+ # ROADMAP
2
+
3
+ This is a living plan. Scope and priorities may change.
4
+
5
+ ## Current Foundation (Implemented)
6
+
7
+ - [x] Canonical scaffold: NestJS API + React web + Prisma/Postgres + Docker
8
+ - [x] Proxy preset selection: `caddy | nginx | none`
9
+ - [x] `@forgeon/core`:
10
+ - [x] `core-config` (typed env config + validation)
11
+ - [x] `core-errors` (global envelope + exception filter)
12
+ - [x] `core-validation` (global validation pipe)
13
+ - [x] `@forgeon/db-prisma` as default-applied DB module
14
+ - [x] i18n add-module baseline:
15
+ - [x] `@forgeon/i18n`, `@forgeon/i18n-contracts`, `@forgeon/i18n-web`
16
+ - [x] shared dictionaries in `resources/i18n/*`
17
+ - [x] tooling: `i18n:sync`, `i18n:check`, `i18n:types`, `i18n:add`
18
+ - [x] module diagnostics probes pattern (`/api/health/*` + web test buttons)
19
+
20
+ ## Standards (Accepted)
21
+
22
+ - [x] `*-contracts` and `*-web` packages are ESM-first
23
+ - [x] API runtime modules use Node-oriented TS config
24
+ - [x] no cross-package imports via `/src/*`; only package entrypoints
25
+
26
+ ## Updated Priority Backlog
27
+
28
+ ### P0 (Immediate Must-Have)
29
+
30
+ - [ ] `logger`
31
+ - [ ] canonical logger module
32
+ - [ ] requestId / correlationId propagation
33
+ - [ ] structured log conventions
34
+
35
+ - [ ] `openapi / swagger`
36
+ - [ ] env toggle: `SWAGGER_ENABLED`
37
+ - [ ] standard setup
38
+ - [ ] bearer integration hook for jwt-auth
39
+ - [ ] `/docs` route
40
+
41
+ - [ ] `jwt-auth`
42
+ - [ ] module split: contracts/api/web
43
+ - [ ] access + refresh baseline
44
+ - [ ] guards/strategy integration
45
+
46
+ - [ ] `rbac / permissions`
47
+ - [ ] decorators: `@Roles()`, `@Permissions()`
48
+ - [ ] guard + policy helper
49
+ - [ ] contracts: `Role`, `Permission`
50
+ - [ ] integration with jwt-auth claims
51
+
52
+ - [ ] `redis/queue foundation`
53
+ - [ ] base Redis config/service
54
+ - [ ] queue baseline (BullMQ or equivalent)
55
+ - [ ] retry and dead-letter conventions
56
+
57
+ - [ ] `rate-limit`
58
+ - [ ] Nest Throttler add-module
59
+ - [ ] policies: route / user / ip
60
+ - [ ] error code: `TOO_MANY_REQUESTS`
61
+ - [ ] reverse-proxy-aware mode (`trust proxy`)
62
+
63
+ - [ ] `files` (upload + storage)
64
+ - [ ] upload endpoints + DTO + guards
65
+ - [ ] storage presets: local + S3-compatible (MinIO/R2)
66
+ - [ ] MIME/size validation
67
+ - [ ] optional image processing subpackage (`sharp`)
68
+ - [ ] error codes: `UPLOAD_INVALID_TYPE`, `UPLOAD_TOO_LARGE`, `UPLOAD_QUOTA`
69
+
70
+ ### P1 (Strongly Recommended)
71
+
72
+ - [ ] `testing baseline`
73
+ - [ ] unit + e2e presets
74
+ - [ ] test helpers for add-modules
75
+ - [ ] smoke test template for generated project
76
+
77
+ - [ ] `CI quality gates`
78
+ - [ ] `typecheck`, `lint`, `test`, docker build smoke
79
+ - [ ] release gate checklist
80
+
81
+ - [ ] `cache` (Redis)
82
+ - [ ] CacheModule preset
83
+ - [ ] key naming conventions
84
+ - [ ] shared wrapper/service
85
+
86
+ - [ ] `scheduler`
87
+ - [ ] `@nestjs/schedule` integration
88
+ - [ ] task template
89
+ - [ ] optional distributed lock (Redis)
90
+
91
+ - [ ] `mail`
92
+ - [ ] at least one provider preset (SMTP/Resend/SendGrid)
93
+ - [ ] templates: verify email, reset password
94
+ - [ ] optional outbox with queue
95
+
96
+ - [ ] workspace `eslint/prettier` config package
97
+
98
+ ### P2 (Later)
99
+
100
+ - [ ] frontend `http-client` module
101
+ - [ ] frontend UI kit package
102
+ - [ ] migrate reusable parts from `eso-dt` (when available)
103
+ - [ ] extend missing primitives
104
+ - [ ] `realtime` (ws)
105
+ - [ ] gateway baseline
106
+ - [ ] jwt auth for ws
107
+ - [ ] rooms + basic events
108
+ - [ ] `webhooks` module (subject to scope validation)
109
+ - [ ] signed inbound verify (HMAC)
110
+ - [ ] signed outbound sender
111
+ - [ ] replay protection (timestamp/nonce)
112
+
113
+ ## Execution Plan (3 Sprints)
114
+
115
+ ### Sprint 1: Platform Baseline and Security Start
116
+
117
+ Scope:
118
+ - `logger`
119
+ - `openapi/swagger`
120
+ - `jwt-auth`
121
+ - `testing baseline`
122
+ - `CI quality gates`
123
+
124
+ Definition of Done:
125
+ - add-modules install cleanly via `create-forgeon add <module>`
126
+ - local dev (`pnpm dev`) and docker build both pass on fresh generated project
127
+ - each module has probe endpoint and web probe UI hook when applicable
128
+ - docs updated in both root and template docs
129
+
130
+ ### Sprint 2: Authorization and Traffic Control
131
+
132
+ Scope:
133
+ - `rbac/permissions`
134
+ - `redis/queue foundation`
135
+ - `rate-limit`
136
+ - `files`
137
+ - `cache`
138
+
139
+ Definition of Done:
140
+ - claims/roles/permissions flow validated end-to-end (api + web contracts)
141
+ - rate-limit and files include standardized error codes and envelope mapping
142
+ - Redis-backed modules run in docker profile with documented env keys
143
+ - at least one e2e happy-path per module
144
+
145
+ ### Sprint 3: Async Integrations and Frontend Foundation
146
+
147
+ Scope:
148
+ - `scheduler`
149
+ - `mail`
150
+ - workspace `eslint/prettier` config package
151
+ - frontend `http-client`
152
+
153
+ Definition of Done:
154
+ - queue/scheduler/mail basic scenarios work in local + docker
155
+ - frontend http-client consumes api contracts with typed errors
156
+ - lint/typecheck/test/build pass through CI gate preset
157
+ - docs include migration notes and extension points
158
+
159
+ ## Explicit Dependencies and Order Constraints
160
+
161
+ - `rbac` depends on `jwt-auth`
162
+ - `rate-limit` should follow Redis/queue foundation for scalable mode
163
+ - `mail` should reuse queue foundation where possible
164
+ - `openapi` is most useful before/with `jwt-auth` and `http-client`
165
+ - `realtime` and `webhooks` stay post-MVP unless a concrete use-case appears
166
+
167
+ ## i18n Policy For Add-Modules
168
+
169
+ - [ ] each add-module that introduces user-facing text defines its own namespace templates
170
+ - [ ] if i18n is already enabled, namespace files are added during module installation
171
+ - [ ] if module is installed first and i18n later, namespaces are merged during i18n installation
@@ -53,6 +53,7 @@ Requirements:
53
53
  - split module into contracts/api/web packages
54
54
  - contracts is source of truth for routes, DTOs, errors
55
55
  - if feasible, add module probe hooks in API (`/api/health/*`) and web diagnostics UI
56
+ - if i18n is enabled, add module namespace files and wire them for both API and web
56
57
  - add docs note under docs/AI/MODULES/<module-id>.md
57
58
  - keep backward compatibility
58
59
  ```
@@ -2,6 +2,7 @@
2
2
 
3
3
  - `AI/PROJECT.md` - project overview and run modes
4
4
  - `AI/ARCHITECTURE.md` - monorepo design and extension model
5
+ - `AI/ROADMAP.md` - implementation roadmap and feature priorities
5
6
  - `AI/MODULE_SPEC.md` - fullstack module contract (`contracts/api/web`)
6
7
  - `AI/MODULE_CHECKS.md` - required runtime probe hooks for modules
7
8
  - `AI/VALIDATION.md` - DTO/env validation standards
@@ -51,17 +51,29 @@ export class CoreExceptionFilter implements ExceptionFilter {
51
51
  private resolveCode(status: number): string {
52
52
  switch (status) {
53
53
  case HttpStatus.BAD_REQUEST:
54
- return 'validation_error';
54
+ return 'BAD_REQUEST';
55
55
  case HttpStatus.UNAUTHORIZED:
56
- return 'unauthorized';
56
+ return 'UNAUTHORIZED';
57
57
  case HttpStatus.FORBIDDEN:
58
- return 'forbidden';
58
+ return 'FORBIDDEN';
59
59
  case HttpStatus.NOT_FOUND:
60
- return 'not_found';
60
+ return 'NOT_FOUND';
61
61
  case HttpStatus.CONFLICT:
62
- return 'conflict';
62
+ return 'CONFLICT';
63
+ case HttpStatus.TOO_MANY_REQUESTS:
64
+ return 'TOO_MANY_REQUESTS';
65
+ case HttpStatus.METHOD_NOT_ALLOWED:
66
+ return 'METHOD_NOT_ALLOWED';
67
+ case HttpStatus.UNPROCESSABLE_ENTITY:
68
+ return 'UNPROCESSABLE_ENTITY';
69
+ case HttpStatus.SERVICE_UNAVAILABLE:
70
+ return 'SERVICE_UNAVAILABLE';
71
+ case HttpStatus.BAD_GATEWAY:
72
+ return 'BAD_GATEWAY';
73
+ case HttpStatus.GATEWAY_TIMEOUT:
74
+ return 'GATEWAY_TIMEOUT';
63
75
  default:
64
- return 'internal_error';
76
+ return 'INTERNAL_ERROR';
65
77
  }
66
78
  }
67
79
 
@@ -1,14 +1,42 @@
1
1
  {
2
- "ok": "OK",
3
- "checkApiHealth": "Check API health",
4
- "checkErrorEnvelope": "Check error envelope",
5
- "checkValidation": "Check validation (expect 400)",
6
- "checkDatabase": "Check database (create user)",
7
- "language": "Language",
8
- "probes": {
9
- "error": "Error envelope probe"
10
- },
11
- "languages": {
12
- "english": "English"
2
+ "actions": {
3
+ "ok": "OK",
4
+ "cancel": "Cancel",
5
+ "save": "Save",
6
+ "delete": "Delete",
7
+ "edit": "Edit",
8
+ "create": "Create",
9
+ "update": "Update",
10
+ "confirm": "Confirm",
11
+ "retry": "Retry"
12
+ },
13
+ "nav": {
14
+ "back": "Back",
15
+ "next": "Next",
16
+ "close": "Close"
17
+ },
18
+ "state": {
19
+ "loading": "Loading...",
20
+ "empty": "No data",
21
+ "selected": "Selected"
22
+ },
23
+ "status": {
24
+ "active": "Active",
25
+ "inactive": "Inactive",
26
+ "pending": "Pending",
27
+ "disabled": "Disabled",
28
+ "archived": "Archived"
29
+ },
30
+ "time": {
31
+ "today": "Today",
32
+ "yesterday": "Yesterday",
33
+ "tomorrow": "Tomorrow",
34
+ "now": "Now"
35
+ },
36
+ "misc": {
37
+ "yes": "Yes",
38
+ "no": "No",
39
+ "on": "On",
40
+ "off": "Off"
13
41
  }
14
42
  }
@@ -1,5 +1,33 @@
1
1
  {
2
- "accessDenied": "Access denied",
3
- "notFound": "Resource not found",
4
- "emailAlreadyExists": "Email already exists"
2
+ "http": {
3
+ "BAD_REQUEST": "Bad request",
4
+ "NOT_FOUND": "Resource not found",
5
+ "UNAUTHORIZED": "Unauthorized",
6
+ "FORBIDDEN": "Access denied",
7
+ "CONFLICT": "Conflict",
8
+ "TOO_MANY_REQUESTS": "Too many requests",
9
+ "METHOD_NOT_ALLOWED": "Method not allowed",
10
+ "UNPROCESSABLE_ENTITY": "Unprocessable entity",
11
+ "INTERNAL_ERROR": "Internal server error",
12
+ "SERVICE_UNAVAILABLE": "Service unavailable",
13
+ "BAD_GATEWAY": "Bad gateway",
14
+ "GATEWAY_TIMEOUT": "Gateway timeout"
15
+ },
16
+ "network": {
17
+ "NETWORK_ERROR": "Network error",
18
+ "TIMEOUT": "Request timeout",
19
+ "OFFLINE": "You appear to be offline"
20
+ },
21
+ "auth": {
22
+ "AUTH_INVALID_CREDENTIALS": "Invalid credentials",
23
+ "AUTH_TOKEN_EXPIRED": "Session expired"
24
+ },
25
+ "validation": {
26
+ "VALIDATION_ERROR": "Validation error"
27
+ },
28
+ "files": {
29
+ "UPLOAD_FILE_TOO_LARGE": "File is too large",
30
+ "UPLOAD_INVALID_TYPE": "Invalid file type",
31
+ "UPLOAD_QUOTA_EXCEEDED": "Upload quota exceeded"
32
+ }
5
33
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "pages": {
3
+ "home": {
4
+ "title": "Forgeon Fullstack Scaffold",
5
+ "description": "Canonical fullstack starter with NestJS, React, and Prisma."
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "success": {
3
+ "saved": "Saved successfully",
4
+ "updated": "Updated successfully",
5
+ "deleted": "Deleted successfully",
6
+ "copied": "Copied successfully"
7
+ },
8
+ "error": {
9
+ "saveFailed": "Failed to save",
10
+ "updateFailed": "Failed to update",
11
+ "deleteFailed": "Failed to delete"
12
+ },
13
+ "info": {
14
+ "changesDiscarded": "Changes were discarded",
15
+ "sessionExpired": "Your session has expired"
16
+ },
17
+ "progress": {
18
+ "uploading": "Uploading...",
19
+ "processing": "Processing..."
20
+ }
21
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "labels": {
3
+ "name": "Name",
4
+ "email": "Email",
5
+ "password": "Password",
6
+ "search": "Search",
7
+ "language": "Language"
8
+ },
9
+ "placeholders": {
10
+ "email": "Enter email",
11
+ "search": "Search...",
12
+ "password": "Enter password"
13
+ },
14
+ "titles": {
15
+ "home": "Home",
16
+ "dashboard": "Dashboard",
17
+ "settings": "Settings"
18
+ },
19
+ "messages": {
20
+ "emptyState": "Nothing to display yet",
21
+ "noResults": "No results found"
22
+ },
23
+ "table": {
24
+ "rowsPerPage": "Rows per page",
25
+ "noData": "No data"
26
+ },
27
+ "modal": {
28
+ "confirmTitle": "Please confirm",
29
+ "confirmText": "Are you sure you want to continue?"
30
+ }
31
+ }
@@ -1,3 +1,32 @@
1
1
  {
2
- "required": "Field is required"
2
+ "generic": {
3
+ "required": "Field is required",
4
+ "invalid": "Invalid value",
5
+ "outOfRange": "Value is out of range"
6
+ },
7
+ "string": {
8
+ "minLength": "Value is too short",
9
+ "maxLength": "Value is too long",
10
+ "pattern": "Value does not match required format"
11
+ },
12
+ "number": {
13
+ "min": "Value is too small",
14
+ "max": "Value is too large",
15
+ "int": "Value must be an integer"
16
+ },
17
+ "format": {
18
+ "email": "Invalid email format",
19
+ "phone": "Invalid phone format",
20
+ "url": "Invalid URL format",
21
+ "uuid": "Invalid UUID format"
22
+ },
23
+ "date": {
24
+ "minDate": "Date is too early",
25
+ "maxDate": "Date is too late",
26
+ "invalidDate": "Invalid date"
27
+ },
28
+ "file": {
29
+ "fileTooLarge": "File is too large",
30
+ "invalidType": "Invalid file type"
31
+ }
3
32
  }
@@ -84,9 +84,13 @@ async function listJsonFiles(folderPath) {
84
84
  }
85
85
 
86
86
  function runSyncCommand(cwd) {
87
- const command = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
87
+ const command = 'pnpm i18n:sync';
88
88
  return new Promise((resolve, reject) => {
89
- const child = spawn(command, ['i18n:sync'], { cwd, stdio: 'inherit' });
89
+ const child = spawn(command, {
90
+ cwd,
91
+ stdio: 'inherit',
92
+ shell: true,
93
+ });
90
94
  child.on('error', (error) => reject(error));
91
95
  child.on('exit', (code) => {
92
96
  if (code === 0) {
@@ -185,4 +189,3 @@ main().catch((error) => {
185
189
  printUsage();
186
190
  process.exitCode = 1;
187
191
  });
188
-
@@ -6,6 +6,7 @@ Environment keys:
6
6
 
7
7
  Resources location: `resources/i18n`.
8
8
  These dictionaries are shared by backend (`nestjs-i18n`) and frontend (`react-i18next`).
9
+ Default namespaces: `common`, `errors`, `validation`, `ui`, `notifications`, `meta`.
9
10
 
10
11
  Packages:
11
12
  - `@forgeon/i18n`
@@ -8,6 +8,7 @@ Included parts:
8
8
  - `@forgeon/i18n-web` (React-side locale helpers)
9
9
  - `react-i18next` integration for frontend translations
10
10
  - shared dictionaries in `resources/i18n/*` (`en` by default) used by both API and web
11
+ - default namespaces: `common`, `errors`, `validation`, `ui`, `notifications`, `meta`
11
12
 
12
13
  Utility commands:
13
14
  - `pnpm i18n:sync` - regenerate `I18N_LOCALES` and `I18N_NAMESPACES` from `resources/i18n`.
@@ -11,7 +11,7 @@ type ProbeResult = {
11
11
  };
12
12
 
13
13
  export default function App() {
14
- const { t } = useTranslation(['common']);
14
+ const { t } = useTranslation(['ui']);
15
15
  const { I18N_LOCALES, getInitialLocale, persistLocale, toLangQuery } = i18nWeb;
16
16
  const [locale, setLocale] = useState<I18nLocale>(getInitialLocale);
17
17
  const [healthResult, setHealthResult] = useState<ProbeResult | null>(null);
@@ -73,7 +73,7 @@ export default function App() {
73
73
  <main className="page">
74
74
  <h1>Forgeon Fullstack Scaffold</h1>
75
75
  <p>Default frontend preset: React + Vite + TypeScript.</p>
76
- <label htmlFor="language">{t('common:language')}:</label>
76
+ <label htmlFor="language">{t('ui:labels.language')}:</label>
77
77
  <select
78
78
  id="language"
79
79
  value={locale}
@@ -81,20 +81,20 @@ export default function App() {
81
81
  >
82
82
  {I18N_LOCALES.map((item) => (
83
83
  <option key={item} value={item}>
84
- {t(`common:languages.${item}`, { defaultValue: item })}
84
+ {item}
85
85
  </option>
86
86
  ))}
87
87
  </select>
88
88
  <div className="actions">
89
- <button onClick={() => runProbe(setHealthResult, '/health')}>{t('common:checkApiHealth')}</button>
89
+ <button onClick={() => runProbe(setHealthResult, '/health')}>Check API health</button>
90
90
  <button onClick={() => runProbe(setErrorProbeResult, '/health/error')}>
91
- {t('common:checkErrorEnvelope')}
91
+ Check error envelope
92
92
  </button>
93
93
  <button onClick={() => runProbe(setValidationProbeResult, '/health/validation')}>
94
- {t('common:checkValidation')}
94
+ Check validation (expect 400)
95
95
  </button>
96
96
  <button onClick={() => runProbe(setDbProbeResult, '/health/db', { method: 'POST' })}>
97
- {t('common:checkDatabase')}
97
+ Check database (create user)
98
98
  </button>
99
99
  </div>
100
100
  {renderResult('Health response', healthResult)}
@@ -3,12 +3,18 @@ import { initReactI18next } from 'react-i18next';
3
3
  import { getInitialLocale, I18N_LOCALES, type I18nLocale } from '@forgeon/i18n-web';
4
4
  import enCommon from '../../../resources/i18n/en/common.json';
5
5
  import enErrors from '../../../resources/i18n/en/errors.json';
6
+ import enMeta from '../../../resources/i18n/en/meta.json';
7
+ import enNotifications from '../../../resources/i18n/en/notifications.json';
8
+ import enUi from '../../../resources/i18n/en/ui.json';
6
9
  import enValidation from '../../../resources/i18n/en/validation.json';
7
10
 
8
11
  const resources = {
9
12
  en: {
10
13
  common: enCommon,
11
14
  errors: enErrors,
15
+ meta: enMeta,
16
+ notifications: enNotifications,
17
+ ui: enUi,
12
18
  validation: enValidation,
13
19
  },
14
20
  } as const;
@@ -1,15 +1,100 @@
1
1
  /* AUTO-GENERATED BY `pnpm i18n:types`. DO NOT EDIT MANUALLY. */
2
2
 
3
3
  export type I18nTranslationKey =
4
- | "common.checkApiHealth"
5
- | "common.checkDatabase"
6
- | "common.checkErrorEnvelope"
7
- | "common.checkValidation"
8
- | "common.language"
9
- | "common.languages.english"
10
- | "common.ok"
11
- | "common.probes.error"
12
- | "errors.accessDenied"
13
- | "errors.emailAlreadyExists"
14
- | "errors.notFound"
15
- | "validation.required";
4
+ | "common.actions.ok"
5
+ | "common.actions.cancel"
6
+ | "common.actions.save"
7
+ | "common.actions.delete"
8
+ | "common.actions.edit"
9
+ | "common.actions.create"
10
+ | "common.actions.update"
11
+ | "common.actions.confirm"
12
+ | "common.actions.retry"
13
+ | "common.nav.back"
14
+ | "common.nav.next"
15
+ | "common.nav.close"
16
+ | "common.state.loading"
17
+ | "common.state.empty"
18
+ | "common.state.selected"
19
+ | "common.status.active"
20
+ | "common.status.inactive"
21
+ | "common.status.pending"
22
+ | "common.status.disabled"
23
+ | "common.status.archived"
24
+ | "common.time.today"
25
+ | "common.time.yesterday"
26
+ | "common.time.tomorrow"
27
+ | "common.time.now"
28
+ | "common.misc.yes"
29
+ | "common.misc.no"
30
+ | "common.misc.on"
31
+ | "common.misc.off"
32
+ | "errors.http.NOT_FOUND"
33
+ | "errors.http.BAD_REQUEST"
34
+ | "errors.http.UNAUTHORIZED"
35
+ | "errors.http.FORBIDDEN"
36
+ | "errors.http.CONFLICT"
37
+ | "errors.http.TOO_MANY_REQUESTS"
38
+ | "errors.http.METHOD_NOT_ALLOWED"
39
+ | "errors.http.UNPROCESSABLE_ENTITY"
40
+ | "errors.http.INTERNAL_ERROR"
41
+ | "errors.http.SERVICE_UNAVAILABLE"
42
+ | "errors.http.BAD_GATEWAY"
43
+ | "errors.http.GATEWAY_TIMEOUT"
44
+ | "errors.network.NETWORK_ERROR"
45
+ | "errors.network.TIMEOUT"
46
+ | "errors.network.OFFLINE"
47
+ | "errors.auth.AUTH_INVALID_CREDENTIALS"
48
+ | "errors.auth.AUTH_TOKEN_EXPIRED"
49
+ | "errors.validation.VALIDATION_ERROR"
50
+ | "errors.files.UPLOAD_FILE_TOO_LARGE"
51
+ | "errors.files.UPLOAD_INVALID_TYPE"
52
+ | "errors.files.UPLOAD_QUOTA_EXCEEDED"
53
+ | "meta.pages.home.title"
54
+ | "meta.pages.home.description"
55
+ | "notifications.success.saved"
56
+ | "notifications.success.updated"
57
+ | "notifications.success.deleted"
58
+ | "notifications.success.copied"
59
+ | "notifications.error.saveFailed"
60
+ | "notifications.error.updateFailed"
61
+ | "notifications.error.deleteFailed"
62
+ | "notifications.info.changesDiscarded"
63
+ | "notifications.info.sessionExpired"
64
+ | "notifications.progress.uploading"
65
+ | "notifications.progress.processing"
66
+ | "ui.labels.name"
67
+ | "ui.labels.email"
68
+ | "ui.labels.password"
69
+ | "ui.labels.search"
70
+ | "ui.labels.language"
71
+ | "ui.placeholders.email"
72
+ | "ui.placeholders.search"
73
+ | "ui.placeholders.password"
74
+ | "ui.titles.home"
75
+ | "ui.titles.dashboard"
76
+ | "ui.titles.settings"
77
+ | "ui.messages.emptyState"
78
+ | "ui.messages.noResults"
79
+ | "ui.table.rowsPerPage"
80
+ | "ui.table.noData"
81
+ | "ui.modal.confirmTitle"
82
+ | "ui.modal.confirmText"
83
+ | "validation.generic.required"
84
+ | "validation.generic.invalid"
85
+ | "validation.generic.outOfRange"
86
+ | "validation.string.minLength"
87
+ | "validation.string.maxLength"
88
+ | "validation.string.pattern"
89
+ | "validation.number.min"
90
+ | "validation.number.max"
91
+ | "validation.number.int"
92
+ | "validation.format.email"
93
+ | "validation.format.phone"
94
+ | "validation.format.url"
95
+ | "validation.format.uuid"
96
+ | "validation.date.minDate"
97
+ | "validation.date.maxDate"
98
+ | "validation.date.invalidDate"
99
+ | "validation.file.fileTooLarge"
100
+ | "validation.file.invalidType";
@@ -1,7 +1,14 @@
1
1
  /* AUTO-GENERATED BY `pnpm i18n:sync`. DO NOT EDIT MANUALLY. */
2
2
 
3
3
  export const I18N_LOCALES = ["en"] as const;
4
- export const I18N_NAMESPACES = ["common", "errors", "validation"] as const;
4
+ export const I18N_NAMESPACES = [
5
+ "common",
6
+ "errors",
7
+ "meta",
8
+ "notifications",
9
+ "ui",
10
+ "validation"
11
+ ] as const;
5
12
 
6
13
  export type I18nLocale = (typeof I18N_LOCALES)[number];
7
14
  export type I18nNamespace = (typeof I18N_NAMESPACES)[number];