create-forgeon 0.1.8 → 0.1.10

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.8",
3
+ "version": "0.1.10",
4
4
  "description": "Forgeon project generator CLI",
5
5
  "license": "MIT",
6
6
  "author": "Forgeon",
@@ -104,7 +104,8 @@ describe('addModule', () => {
104
104
 
105
105
  const appTsx = fs.readFileSync(path.join(projectRoot, 'apps', 'web', 'src', 'App.tsx'), 'utf8');
106
106
  assert.match(appTsx, /@forgeon\/i18n-web/);
107
- assert.match(appTsx, /Language:/);
107
+ assert.match(appTsx, /\/api\/health\/meta/);
108
+ assert.match(appTsx, /checkApiHealth/);
108
109
 
109
110
  const i18nWebPackage = fs.readFileSync(
110
111
  path.join(projectRoot, 'packages', 'i18n-web', 'package.json'),
@@ -118,6 +119,12 @@ describe('addModule', () => {
118
119
  );
119
120
  assert.match(i18nWebTsconfig, /"module": "ESNext"/);
120
121
 
122
+ const i18nWebSource = fs.readFileSync(
123
+ path.join(projectRoot, 'packages', 'i18n-web', 'src', 'index.ts'),
124
+ 'utf8',
125
+ );
126
+ assert.match(i18nWebSource, /@forgeon\/i18n-contracts\/src\/index/);
127
+
121
128
  const caddyDockerfile = fs.readFileSync(
122
129
  path.join(projectRoot, 'infra', 'docker', 'caddy.Dockerfile'),
123
130
  'utf8',
@@ -1,31 +1,57 @@
1
- import { Controller, Get, Optional, Query } from '@nestjs/common';
2
- import { I18nService } from 'nestjs-i18n';
3
- import { EchoQueryDto } from '../common/dto/echo-query.dto';
4
-
5
- @Controller('health')
6
- export class HealthController {
7
- constructor(@Optional() private readonly i18n?: I18nService) {}
8
-
9
- @Get()
10
- getHealth(@Query('lang') lang?: string) {
11
- return {
12
- status: 'ok',
13
- message: this.translate('common.ok', lang),
14
- };
15
- }
16
-
17
- @Get('echo')
18
- getEcho(@Query() query: EchoQueryDto) {
19
- return { value: query.value };
20
- }
21
-
1
+ import { Controller, Get, Optional, Query } from '@nestjs/common';
2
+ import { I18nService } from 'nestjs-i18n';
3
+ import { EchoQueryDto } from '../common/dto/echo-query.dto';
4
+
5
+ @Controller('health')
6
+ export class HealthController {
7
+ constructor(@Optional() private readonly i18n?: I18nService) {}
8
+
9
+ @Get()
10
+ getHealth(@Query('lang') lang?: string) {
11
+ const locale = this.resolveLocale(lang);
12
+ return {
13
+ status: 'ok',
14
+ message: this.translate('common.ok', lang),
15
+ i18n: this.translate(this.localeNameKey(locale), lang),
16
+ };
17
+ }
18
+
19
+ @Get('meta')
20
+ getMeta(@Query('lang') lang?: string) {
21
+ return {
22
+ checkApiHealth: this.translate('common.checkApiHealth', lang),
23
+ languageLabel: this.translate('common.language', lang),
24
+ };
25
+ }
26
+
27
+ @Get('echo')
28
+ getEcho(@Query() query: EchoQueryDto) {
29
+ return { value: query.value };
30
+ }
31
+
22
32
  private translate(key: string, lang?: string): string {
23
33
  if (!this.i18n) {
24
- if (key === 'common.ok') return 'OK';
25
- return key;
26
- }
27
-
28
- const value = this.i18n.t(key, { lang, defaultValue: key });
29
- return typeof value === 'string' ? value : key;
30
- }
31
- }
34
+ if (key === 'common.ok') return 'OK';
35
+ if (key === 'common.checkApiHealth') return 'Check API health';
36
+ if (key === 'common.language') return 'Language';
37
+ if (key === 'languages.english') return 'English';
38
+ if (key === 'languages.ukrainian') return 'Ukrainian';
39
+ return key;
40
+ }
41
+
42
+ const value = this.i18n.t(key, { lang, defaultValue: key });
43
+ return typeof value === 'string' ? value : key;
44
+ }
45
+
46
+ private resolveLocale(lang?: string): 'en' | 'uk' {
47
+ const normalized = (lang ?? '').toLowerCase();
48
+ if (normalized.startsWith('uk')) {
49
+ return 'uk';
50
+ }
51
+ return 'en';
52
+ }
53
+
54
+ private localeNameKey(locale: 'en' | 'uk'): string {
55
+ return locale === 'uk' ? 'languages.ukrainian' : 'languages.english';
56
+ }
57
+ }
@@ -1,5 +1,9 @@
1
- {
2
- "errors.accessDenied": "Access denied",
3
- "errors.notFound": "Resource not found",
4
- "common.ok": "OK"
5
- }
1
+ {
2
+ "errors.accessDenied": "Access denied",
3
+ "errors.notFound": "Resource not found",
4
+ "common.ok": "OK",
5
+ "common.checkApiHealth": "Check API health",
6
+ "common.language": "Language",
7
+ "languages.english": "English",
8
+ "languages.ukrainian": "Ukrainian"
9
+ }
@@ -1,5 +1,9 @@
1
- {
2
- "errors.accessDenied": "Доступ заборонено",
3
- "errors.notFound": "Ресурс не знайдено",
4
- "common.ok": "OK"
5
- }
1
+ {
2
+ "errors.accessDenied": "Доступ заборонено",
3
+ "errors.notFound": "Ресурс не знайдено",
4
+ "common.ok": "OK",
5
+ "common.checkApiHealth": "Перевірити API health",
6
+ "common.language": "Мова",
7
+ "languages.english": "Англійська",
8
+ "languages.ukrainian": "Українська"
9
+ }
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
  import * as i18nWeb from '@forgeon/i18n-web';
3
3
  import type { I18nLocale } from '@forgeon/i18n-web';
4
4
  import './styles.css';
@@ -6,6 +6,12 @@ import './styles.css';
6
6
  type HealthResponse = {
7
7
  status: string;
8
8
  message: string;
9
+ i18n: string;
10
+ };
11
+
12
+ type HealthMetaResponse = {
13
+ checkApiHealth: string;
14
+ languageLabel: string;
9
15
  };
10
16
 
11
17
  export default function App() {
@@ -13,12 +19,37 @@ export default function App() {
13
19
  const [locale, setLocale] = useState<I18nLocale>(getInitialLocale);
14
20
  const [data, setData] = useState<HealthResponse | null>(null);
15
21
  const [error, setError] = useState<string | null>(null);
22
+ const [labels, setLabels] = useState<HealthMetaResponse>({
23
+ checkApiHealth: 'Check API health',
24
+ languageLabel: 'Language',
25
+ });
16
26
 
17
27
  const changeLocale = (nextLocale: I18nLocale) => {
18
28
  setLocale(nextLocale);
19
29
  persistLocale(nextLocale);
20
30
  };
21
31
 
32
+ useEffect(() => {
33
+ const loadLabels = async () => {
34
+ try {
35
+ const response = await fetch(`/api/health/meta${toLangQuery(locale)}`, {
36
+ headers: {
37
+ 'Accept-Language': locale,
38
+ },
39
+ });
40
+ if (!response.ok) {
41
+ return;
42
+ }
43
+ const payload = (await response.json()) as HealthMetaResponse;
44
+ setLabels(payload);
45
+ } catch {
46
+ // Keep fallback labels if meta request fails.
47
+ }
48
+ };
49
+
50
+ void loadLabels();
51
+ }, [locale, toLangQuery]);
52
+
22
53
  const checkApi = async () => {
23
54
  setError(null);
24
55
  try {
@@ -41,7 +72,7 @@ export default function App() {
41
72
  <main className="page">
42
73
  <h1>Forgeon Fullstack Scaffold</h1>
43
74
  <p>Default frontend preset: React + Vite + TypeScript.</p>
44
- <label htmlFor="language">Language:</label>
75
+ <label htmlFor="language">{labels.languageLabel}:</label>
45
76
  <select
46
77
  id="language"
47
78
  value={locale}
@@ -53,7 +84,7 @@ export default function App() {
53
84
  </option>
54
85
  ))}
55
86
  </select>
56
- <button onClick={checkApi}>Check API health</button>
87
+ <button onClick={checkApi}>{labels.checkApiHealth}</button>
57
88
  {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : null}
58
89
  {error ? <p className="error">{error}</p> : null}
59
90
  </main>
@@ -3,7 +3,7 @@ import {
3
3
  I18N_LOCALES,
4
4
  LANG_QUERY_PARAM,
5
5
  type I18nLocale,
6
- } from '@forgeon/i18n-contracts';
6
+ } from '@forgeon/i18n-contracts/src/index';
7
7
 
8
8
  const LOCALE_STORAGE_KEY = 'forgeon.locale';
9
9