@bgord/bun 1.12.4 → 1.12.5

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.
Files changed (99) hide show
  1. package/dist/i18n.service.d.ts +0 -6
  2. package/dist/i18n.service.d.ts.map +1 -1
  3. package/dist/i18n.service.js.map +1 -1
  4. package/dist/index.d.ts +7 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +7 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/language-detector-cookie.strategy.d.ts +10 -0
  9. package/dist/language-detector-cookie.strategy.d.ts.map +1 -0
  10. package/dist/language-detector-cookie.strategy.js +11 -0
  11. package/dist/language-detector-cookie.strategy.js.map +1 -0
  12. package/dist/language-detector-header.strategy.d.ts +8 -0
  13. package/dist/language-detector-header.strategy.d.ts.map +1 -0
  14. package/dist/language-detector-header.strategy.js +15 -0
  15. package/dist/language-detector-header.strategy.js.map +1 -0
  16. package/dist/language-detector-hono.middleware.d.ts +13 -0
  17. package/dist/language-detector-hono.middleware.d.ts.map +1 -0
  18. package/dist/language-detector-hono.middleware.js +17 -0
  19. package/dist/language-detector-hono.middleware.js.map +1 -0
  20. package/dist/language-detector-query.strategy.d.ts +10 -0
  21. package/dist/language-detector-query.strategy.d.ts.map +1 -0
  22. package/dist/language-detector-query.strategy.js +11 -0
  23. package/dist/language-detector-query.strategy.js.map +1 -0
  24. package/dist/language-detector.middleware.d.ts +17 -0
  25. package/dist/language-detector.middleware.d.ts.map +1 -0
  26. package/dist/language-detector.middleware.js +20 -0
  27. package/dist/language-detector.middleware.js.map +1 -0
  28. package/dist/language-detector.strategy.d.ts +7 -0
  29. package/dist/language-detector.strategy.d.ts.map +1 -0
  30. package/dist/language-detector.strategy.js +2 -0
  31. package/dist/language-detector.strategy.js.map +1 -0
  32. package/dist/languages.vo.d.ts +13 -0
  33. package/dist/languages.vo.d.ts.map +1 -0
  34. package/dist/languages.vo.js +23 -0
  35. package/dist/languages.vo.js.map +1 -0
  36. package/dist/modules/preferences/command-handlers/handleSetUserLanguageCommand.d.ts +5 -2
  37. package/dist/modules/preferences/command-handlers/handleSetUserLanguageCommand.d.ts.map +1 -1
  38. package/dist/modules/preferences/command-handlers/handleSetUserLanguageCommand.js +8 -3
  39. package/dist/modules/preferences/command-handlers/handleSetUserLanguageCommand.js.map +1 -1
  40. package/dist/modules/preferences/index.d.ts +0 -1
  41. package/dist/modules/preferences/index.d.ts.map +1 -1
  42. package/dist/modules/preferences/index.js +0 -1
  43. package/dist/modules/preferences/index.js.map +1 -1
  44. package/dist/modules/preferences/open-host-queries/user-language.d.ts +10 -7
  45. package/dist/modules/preferences/open-host-queries/user-language.d.ts.map +1 -1
  46. package/dist/modules/preferences/open-host-queries/user-language.js +7 -4
  47. package/dist/modules/preferences/open-host-queries/user-language.js.map +1 -1
  48. package/dist/modules/preferences/ports/user-language-query.d.ts +1 -1
  49. package/dist/modules/preferences/ports/user-language-query.d.ts.map +1 -1
  50. package/dist/modules/preferences/ports/user-language-resolver.d.ts +4 -3
  51. package/dist/modules/preferences/ports/user-language-resolver.d.ts.map +1 -1
  52. package/dist/modules/preferences/ports/user-language-resolver.js +4 -4
  53. package/dist/modules/preferences/ports/user-language-resolver.js.map +1 -1
  54. package/dist/prerequisite-verifier-translations.adapter.d.ts +4 -6
  55. package/dist/prerequisite-verifier-translations.adapter.d.ts.map +1 -1
  56. package/dist/prerequisite-verifier-translations.adapter.js +3 -3
  57. package/dist/prerequisite-verifier-translations.adapter.js.map +1 -1
  58. package/dist/setup-hono.service.d.ts +6 -4
  59. package/dist/setup-hono.service.d.ts.map +1 -1
  60. package/dist/setup-hono.service.js +2 -8
  61. package/dist/setup-hono.service.js.map +1 -1
  62. package/dist/translations-hono.handler.d.ts +8 -12
  63. package/dist/translations-hono.handler.d.ts.map +1 -1
  64. package/dist/translations-hono.handler.js.map +1 -1
  65. package/dist/translations.handler.d.ts +7 -7
  66. package/dist/translations.handler.d.ts.map +1 -1
  67. package/dist/translations.handler.js +1 -1
  68. package/dist/translations.handler.js.map +1 -1
  69. package/dist/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +4 -4
  71. package/readme.md +10 -5
  72. package/src/i18n.service.ts +0 -8
  73. package/src/index.ts +7 -0
  74. package/src/language-detector-cookie.strategy.ts +16 -0
  75. package/src/language-detector-header.strategy.ts +23 -0
  76. package/src/language-detector-hono.middleware.ts +29 -0
  77. package/src/language-detector-query.strategy.ts +16 -0
  78. package/src/language-detector.middleware.ts +28 -0
  79. package/src/language-detector.strategy.ts +7 -0
  80. package/src/languages.vo.ts +27 -0
  81. package/src/modules/preferences/command-handlers/handleSetUserLanguageCommand.ts +11 -4
  82. package/src/modules/preferences/index.ts +0 -1
  83. package/src/modules/preferences/open-host-queries/user-language.ts +10 -7
  84. package/src/modules/preferences/ports/user-language-query.ts +1 -1
  85. package/src/modules/preferences/ports/user-language-resolver.ts +6 -3
  86. package/src/prerequisite-verifier-translations.adapter.ts +9 -7
  87. package/src/setup-hono.service.ts +10 -12
  88. package/src/translations-hono.handler.ts +8 -5
  89. package/src/translations.handler.ts +8 -9
  90. package/dist/modules/preferences/value-objects/index.d.ts +0 -2
  91. package/dist/modules/preferences/value-objects/index.d.ts.map +0 -1
  92. package/dist/modules/preferences/value-objects/index.js +0 -2
  93. package/dist/modules/preferences/value-objects/index.js.map +0 -1
  94. package/dist/modules/preferences/value-objects/supported-languages-set.d.ts +0 -10
  95. package/dist/modules/preferences/value-objects/supported-languages-set.d.ts.map +0 -1
  96. package/dist/modules/preferences/value-objects/supported-languages-set.js +0 -14
  97. package/dist/modules/preferences/value-objects/supported-languages-set.js.map +0 -1
  98. package/src/modules/preferences/value-objects/index.ts +0 -1
  99. package/src/modules/preferences/value-objects/supported-languages-set.ts +0 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgord/bun",
3
- "version": "1.12.4",
3
+ "version": "1.12.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": "Bartosz Gordon",
@@ -30,8 +30,8 @@
30
30
  "better-auth": "1.5.4",
31
31
  "cspell": "9.7.0",
32
32
  "csv": "6.4.1",
33
- "knip": "5.85.0",
34
- "lefthook": "2.1.2",
33
+ "knip": "5.86.0",
34
+ "lefthook": "2.1.3",
35
35
  "lockfile-lint": "5.0.0",
36
36
  "nodemailer": "8.0.1",
37
37
  "only-allow": "1.2.2",
@@ -43,7 +43,7 @@
43
43
  "zod": "4.3.6"
44
44
  },
45
45
  "dependencies": {
46
- "@bgord/tools": "1.3.22",
46
+ "@bgord/tools": "1.3.24",
47
47
  "emittery": "2.0.0",
48
48
  "hono": "4.12.5",
49
49
  "node-cache": "5.1.2"
package/readme.md CHANGED
@@ -230,6 +230,13 @@ src/
230
230
  ├── job-handler-with-logger.strategy.ts
231
231
  ├── job-handler.strategy.ts
232
232
  ├── jobs.service.ts
233
+ ├── language-detector-cookie.strategy.ts
234
+ ├── language-detector-header.strategy.ts
235
+ ├── language-detector-hono.middleware.ts
236
+ ├── language-detector-query.strategy.ts
237
+ ├── language-detector.middleware.ts
238
+ ├── language-detector.strategy.ts
239
+ ├── languages.vo.ts
233
240
  ├── logger-collecting.adapter.ts
234
241
  ├── logger-noop.adapter.ts
235
242
  ├── logger-stats-provider-noop.adapter.ts
@@ -280,11 +287,9 @@ src/
280
287
  │   │   │   └── user-language-has-changed.ts
281
288
  │   │   ├── open-host-queries
282
289
  │   │   │   └── user-language.ts
283
- │   │   ├── ports
284
- │   │   │   ├── user-language-query.ts
285
- │   │   │   └── user-language-resolver.ts
286
- │   │   └── value-objects
287
- │   │   └── supported-languages-set.ts
290
+ │   │   └── ports
291
+ │   │   ├── user-language-query.ts
292
+ │   │   └── user-language-resolver.ts
288
293
  │   └── system
289
294
  │   ├── events
290
295
  │   │   ├── HOUR_HAS_PASSED_EVENT.ts
@@ -9,14 +9,6 @@ export type TranslationPlaceholderType = string;
9
9
  export type TranslationPlaceholderValueType = string | number;
10
10
  export type TranslationVariableType = Record<TranslationPlaceholderType, TranslationPlaceholderValueType>;
11
11
 
12
- export type TranslationsSupportedLanguagesType = Record<string, string>;
13
-
14
- export type I18nConfig = {
15
- supportedLanguages: TranslationsSupportedLanguagesType;
16
- translationsPath?: tools.DirectoryPathRelativeType;
17
- defaultLanguage?: string;
18
- };
19
-
20
12
  type Dependencies = { FileReaderJson: FileReaderJsonPort; Logger: LoggerPort };
21
13
 
22
14
  export class I18n {
package/src/index.ts CHANGED
@@ -199,6 +199,13 @@ export * from "./job-handler-bare.strategy";
199
199
  export * from "./job-handler-noop.strategy";
200
200
  export * from "./job-handler-with-logger.strategy";
201
201
  export * from "./jobs.service";
202
+ export * from "./language-detector.middleware";
203
+ export * from "./language-detector.strategy";
204
+ export * from "./language-detector-cookie.strategy";
205
+ export * from "./language-detector-header.strategy";
206
+ export * from "./language-detector-hono.middleware";
207
+ export * from "./language-detector-query.strategy";
208
+ export * from "./languages.vo";
202
209
  export * from "./logger.port";
203
210
  export * from "./logger-collecting.adapter";
204
211
  export * from "./logger-noop.adapter";
@@ -0,0 +1,16 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { LanguageDetectorStrategy } from "./language-detector.strategy";
3
+ import type { Languages } from "./languages.vo";
4
+ import type { HasRequestCookie } from "./request-context.port";
5
+
6
+ export class LanguageDetectorCookieStrategy<T extends tools.LanguageType>
7
+ implements LanguageDetectorStrategy<T>
8
+ {
9
+ constructor(private readonly name: string) {}
10
+
11
+ detect(context: HasRequestCookie, languages: Languages<T>): T | null {
12
+ const detected = context.request.cookie(this.name);
13
+
14
+ return languages.isSupported(detected) ? detected : null;
15
+ }
16
+ }
@@ -0,0 +1,23 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { LanguageDetectorStrategy } from "./language-detector.strategy";
3
+ import type { Languages } from "./languages.vo";
4
+ import type { HasRequestHeaders } from "./request-context.port";
5
+
6
+ export class LanguageDetectorHeaderStrategy<T extends tools.LanguageType>
7
+ implements LanguageDetectorStrategy<T>
8
+ {
9
+ detect(context: HasRequestHeaders, languages: Languages<T>): T | null {
10
+ const header = context.request.headers().get("Accept-Language");
11
+
12
+ if (!header) return null;
13
+
14
+ // Example Accept-Language header: en-US,en;q=0.9,pl;q=0.8
15
+ // Stryker disable all
16
+ const incoming = header
17
+ .split(",")
18
+ .map((language) => language.split(";")[0]?.trim().toLowerCase().split("-")[0]);
19
+ // Stryker restore all
20
+
21
+ return incoming.find((language) => languages.isSupported(language)) ?? null;
22
+ }
23
+ }
@@ -0,0 +1,29 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { MiddlewareHandler } from "hono";
3
+ import {
4
+ LanguageDetectorMiddleware,
5
+ type LanguageDetectorMiddlewareConfig,
6
+ } from "./language-detector.middleware";
7
+ import type { MiddlewareHonoPort } from "./middleware-hono.port";
8
+ import { RequestContextHonoAdapter } from "./request-context-hono.adapter";
9
+
10
+ export type LanguageDetectorVariables = { language: tools.LanguageType };
11
+
12
+ export class LanguageDetectorHonoMiddleware<T extends tools.LanguageType> implements MiddlewareHonoPort {
13
+ private readonly middleware: LanguageDetectorMiddleware<T>;
14
+
15
+ constructor(config: LanguageDetectorMiddlewareConfig<T>) {
16
+ this.middleware = new LanguageDetectorMiddleware(config);
17
+ }
18
+
19
+ handle(): MiddlewareHandler {
20
+ return async (c, next) => {
21
+ const context = new RequestContextHonoAdapter(c);
22
+
23
+ const language = this.middleware.evaluate(context);
24
+ c.set("language", language);
25
+
26
+ await next();
27
+ };
28
+ }
29
+ }
@@ -0,0 +1,16 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { LanguageDetectorStrategy } from "./language-detector.strategy";
3
+ import type { Languages } from "./languages.vo";
4
+ import type { HasRequestQuery } from "./request-context.port";
5
+
6
+ export class LanguageDetectorQueryStrategy<T extends tools.LanguageType>
7
+ implements LanguageDetectorStrategy<T>
8
+ {
9
+ constructor(private readonly name: string) {}
10
+
11
+ detect(context: HasRequestQuery, languages: Languages<T>): T | null {
12
+ const detected = context.request.query()[this.name];
13
+
14
+ return languages.isSupported(detected) ? detected : null;
15
+ }
16
+ }
@@ -0,0 +1,28 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { LanguageDetectorStrategy } from "./language-detector.strategy";
3
+ import type { Languages } from "./languages.vo";
4
+ import type { RequestContext } from "./request-context.port";
5
+
6
+ export const LanguageDetectorMiddlewareError = {
7
+ MaxStrategies: "language.detector.middleware.max.strategies",
8
+ };
9
+
10
+ export type LanguageDetectorMiddlewareConfig<T extends tools.LanguageType> = {
11
+ languages: Languages<T>;
12
+ strategies: Array<LanguageDetectorStrategy<T>>;
13
+ };
14
+
15
+ export class LanguageDetectorMiddleware<T extends tools.LanguageType> {
16
+ constructor(private readonly config: LanguageDetectorMiddlewareConfig<T>) {
17
+ if (config.strategies.length > 5) throw new Error(LanguageDetectorMiddlewareError.MaxStrategies);
18
+ }
19
+
20
+ evaluate(context: RequestContext): T {
21
+ for (const strategy of this.config.strategies) {
22
+ const detected = strategy.detect(context, this.config.languages);
23
+ if (detected !== null) return detected;
24
+ }
25
+
26
+ return this.config.languages.fallback;
27
+ }
28
+ }
@@ -0,0 +1,7 @@
1
+ import type * as tools from "@bgord/tools";
2
+ import type { Languages } from "./languages.vo";
3
+ import type { RequestContext } from "./request-context.port";
4
+
5
+ export interface LanguageDetectorStrategy<T extends tools.LanguageType> {
6
+ detect(context: RequestContext, languages: Languages<T>): T | null;
7
+ }
@@ -0,0 +1,27 @@
1
+ import type * as tools from "@bgord/tools";
2
+
3
+ export const LanguagesError = {
4
+ Empty: "languages.empty",
5
+ FallbackNotSupported: "languages.fallback.not.supported",
6
+ };
7
+
8
+ export class Languages<T extends tools.LanguageType> {
9
+ constructor(
10
+ readonly values: ReadonlyArray<T>,
11
+ readonly fallback: T,
12
+ ) {
13
+ if (values.length === 0) throw new Error(LanguagesError.Empty);
14
+ if (!values.includes(fallback)) throw new Error(LanguagesError.FallbackNotSupported);
15
+ }
16
+
17
+ get supported(): Record<T, T> {
18
+ return this.values.reduce(
19
+ (result, language) => ({ ...result, [language]: language }),
20
+ {} as Record<T, T>,
21
+ );
22
+ }
23
+
24
+ isSupported(value: unknown): value is T {
25
+ return this.values.includes(value as T);
26
+ }
27
+ }
@@ -3,11 +3,15 @@ import type { ClockPort } from "../../../clock.port";
3
3
  import { createEventEnvelope } from "../../../event-envelope";
4
4
  import type { EventStoreLike } from "../../../event-store-like.types";
5
5
  import type { IdProviderPort } from "../../../id-provider.port";
6
+ import type { Languages } from "../../../languages.vo";
6
7
  import type * as Commands from "../commands";
7
8
  import * as Events from "../events";
8
9
  import * as Invariants from "../invariants";
9
10
  import type * as Ports from "../ports";
10
- import type * as VO from "../value-objects";
11
+
12
+ export const HandleSetUserLanguageCommandError = {
13
+ Missing: "handle.set.user.language.command.error.missing",
14
+ };
11
15
 
12
16
  type Dependencies = {
13
17
  EventStore: EventStoreLike<AcceptedEvent>;
@@ -19,12 +23,15 @@ type Dependencies = {
19
23
  type AcceptedEvent = Events.UserLanguageSetEventType;
20
24
 
21
25
  export const handleSetUserLanguageCommand =
22
- <L extends ReadonlyArray<tools.LanguageType>>(supported: VO.SupportedLanguagesSet<L>, deps: Dependencies) =>
26
+ <T extends tools.LanguageType>(languages: Languages<T>, deps: Dependencies) =>
23
27
  async (command: Commands.SetUserLanguageCommandType) => {
24
- const candidate = supported.ensure(command.payload.language);
28
+ const candidate = command.payload.language;
29
+
30
+ if (!languages.isSupported(candidate)) throw new Error(HandleSetUserLanguageCommandError.Missing);
31
+
25
32
  const current = await deps.UserLanguageQuery.get(command.payload.userId);
26
33
 
27
- if (!Invariants.UserLanguageHasChanged.passes({ current, candidate: command.payload.language })) return;
34
+ if (!Invariants.UserLanguageHasChanged.passes({ current, candidate })) return;
28
35
 
29
36
  const event = Events.UserLanguageSetEvent.parse({
30
37
  ...createEventEnvelope(`preferences_${command.payload.userId}`, deps),
@@ -4,4 +4,3 @@ export * as Events from "./events";
4
4
  export * as Invariants from "./invariants";
5
5
  export * as OHQ from "./open-host-queries";
6
6
  export * as Ports from "./ports";
7
- export * as VO from "./value-objects";
@@ -1,23 +1,26 @@
1
1
  import type * as tools from "@bgord/tools";
2
+ import type { Languages } from "../../../languages.vo";
2
3
  import type { UUIDType } from "../../../uuid.vo";
3
4
  import type * as Ports from "../ports";
4
- import type * as VO from "../value-objects";
5
5
 
6
- export interface UserLanguagePort<L extends ReadonlyArray<tools.LanguageType>> {
7
- get(userId: UUIDType): Promise<L[number]>;
6
+ export const UserLanguageAdapterError = { Missing: "user.language.ohq.error.missing" };
7
+
8
+ export interface UserLanguagePort<T extends tools.LanguageType> {
9
+ get(userId: UUIDType): Promise<T>;
8
10
  }
9
11
 
10
- export class UserLanguageAdapter<L extends ReadonlyArray<tools.LanguageType>> implements UserLanguagePort<L> {
12
+ export class UserLanguageAdapter<T extends tools.LanguageType> implements UserLanguagePort<T> {
11
13
  constructor(
14
+ private readonly languages: Languages<T>,
12
15
  private readonly query: Ports.UserLanguageQueryPort,
13
- private readonly validator: VO.SupportedLanguagesSet<L>,
14
16
  private readonly resolver: Ports.UserLanguageResolverPort,
15
17
  ) {}
16
18
 
17
- async get(userId: UUIDType): Promise<L[number]> {
19
+ async get(userId: UUIDType): Promise<T> {
18
20
  const stored = await this.query.get(userId);
19
21
  const candidate = await this.resolver.resolve(stored);
20
22
 
21
- return this.validator.ensure(candidate);
23
+ if (!this.languages.isSupported(candidate)) throw new Error(UserLanguageAdapterError.Missing);
24
+ return candidate;
22
25
  }
23
26
  }
@@ -1,5 +1,5 @@
1
1
  import type * as tools from "@bgord/tools";
2
2
 
3
3
  export interface UserLanguageQueryPort {
4
- get(userId: tools.LanguageType): Promise<tools.LanguageType | null>;
4
+ get(userId: string): Promise<tools.LanguageType | null>;
5
5
  }
@@ -1,4 +1,5 @@
1
1
  import type * as tools from "@bgord/tools";
2
+ import type { Languages } from "../../../languages.vo";
2
3
 
3
4
  export interface UserLanguageResolverPort {
4
5
  resolve(input: tools.LanguageType | null): tools.LanguageType | Promise<tools.LanguageType>;
@@ -13,10 +14,12 @@ export class UserLanguageResolverThrowIfMissing implements UserLanguageResolverP
13
14
  }
14
15
  }
15
16
 
16
- export class UserLanguageResolverSystemDefaultFallback implements UserLanguageResolverPort {
17
- constructor(private readonly systemDefaultLanguage: tools.LanguageType) {}
17
+ export class UserLanguageResolverSystemDefaultFallback<T extends tools.LanguageType>
18
+ implements UserLanguageResolverPort
19
+ {
20
+ constructor(private readonly languages: Languages<T>) {}
18
21
 
19
22
  resolve(stored: tools.LanguageType | null) {
20
- return stored ?? this.systemDefaultLanguage;
23
+ return stored ?? this.languages.fallback;
21
24
  }
22
25
  }
@@ -2,6 +2,7 @@ import type * as tools from "@bgord/tools";
2
2
  import type { FileReaderJsonPort } from "./file-reader-json.port";
3
3
  import type * as types from "./i18n.service";
4
4
  import { I18n } from "./i18n.service";
5
+ import type { Languages } from "./languages.vo";
5
6
  import type { LoggerPort } from "./logger.port";
6
7
  import {
7
8
  PrerequisiteVerification,
@@ -16,21 +17,22 @@ type PrerequisiteTranslationsProblemType = {
16
17
  };
17
18
 
18
19
  type Dependencies = { Logger: LoggerPort; FileReaderJson: FileReaderJsonPort };
19
- type Config = { supportedLanguages: types.I18nConfig["supportedLanguages"] };
20
20
 
21
- export class PrerequisiteVerifierTranslationsAdapter implements PrerequisiteVerifierPort {
21
+ export class PrerequisiteVerifierTranslationsAdapter<T extends tools.LanguageType>
22
+ implements PrerequisiteVerifierPort
23
+ {
22
24
  constructor(
23
- private readonly config: Config,
25
+ private readonly config: Languages<T>,
24
26
  private readonly deps: Dependencies,
25
27
  ) {}
26
28
 
27
29
  async verify(): Promise<PrerequisiteVerificationResult> {
28
- const supportedLanguages = Object.keys(this.config.supportedLanguages);
29
30
  const i18n = new I18n(this.deps);
31
+ const languages = this.config.values;
30
32
 
31
- const dictionary: Partial<Record<tools.LanguageType, ReadonlyArray<types.TranslationsKeyType>>> = {};
33
+ const dictionary: Partial<Record<T, ReadonlyArray<types.TranslationsKeyType>>> = {};
32
34
 
33
- for (const language of supportedLanguages) {
35
+ for (const language of languages) {
34
36
  try {
35
37
  const translations = await i18n.getTranslations(language);
36
38
  dictionary[language] = Object.keys(translations);
@@ -47,7 +49,7 @@ export class PrerequisiteVerifierTranslationsAdapter implements PrerequisiteVeri
47
49
  // Stryker restore all
48
50
 
49
51
  for (const phrase of phrases) {
50
- for (const supportedLanguage of supportedLanguages) {
52
+ for (const supportedLanguage of languages) {
51
53
  const phraseExists = new Set(dictionary[supportedLanguage]).has(phrase);
52
54
 
53
55
  if (!phraseExists) problems.push({ key: phrase, existsIn: language, missingIn: supportedLanguage });
@@ -1,6 +1,6 @@
1
1
  import * as tools from "@bgord/tools";
2
+ import type { MiddlewareHandler } from "hono";
2
3
  import { cors } from "hono/cors";
3
- import { languageDetector } from "hono/language";
4
4
  import { secureHeaders } from "hono/secure-headers";
5
5
  import { ApiVersionHonoMiddleware } from "./api-version-hono.middleware";
6
6
  import type { BuildInfoRepositoryStrategy } from "./build-info-repository.strategy";
@@ -11,8 +11,9 @@ import { ETagExtractorHonoMiddleware } from "./etag-extractor-hono.middleware";
11
11
  import type { HashContentStrategy } from "./hash-content.strategy";
12
12
  import type { HttpLoggerConfig } from "./http-logger.middleware";
13
13
  import { HttpLoggerHonoMiddleware } from "./http-logger-hono.middleware";
14
- import type { I18nConfig } from "./i18n.service";
15
14
  import type { IdProviderPort } from "./id-provider.port";
15
+ import type { LanguageDetectorMiddlewareConfig } from "./language-detector.middleware";
16
+ import { LanguageDetectorHonoMiddleware } from "./language-detector-hono.middleware";
16
17
  import type { LoggerPort } from "./logger.port";
17
18
  import type { ShieldCsrfConfig } from "./shield-csrf.strategy";
18
19
  import { ShieldCsrfHonoStrategy } from "./shield-csrf-hono.strategy";
@@ -26,15 +27,15 @@ import { WeakETagExtractorHonoMiddleware } from "./weak-etag-extractor-hono.midd
26
27
  type Dependencies = {
27
28
  Logger: LoggerPort;
28
29
  IdProvider: IdProviderPort;
29
- I18n: I18nConfig;
30
30
  Clock: ClockPort;
31
31
  CacheResolver: CacheResolverStrategy;
32
32
  HashContent: HashContentStrategy;
33
33
  BuildInfoRepository: BuildInfoRepositoryStrategy;
34
34
  };
35
35
 
36
- type Config = {
36
+ type Config<T extends tools.LanguageType> = {
37
37
  csrf: ShieldCsrfConfig;
38
+ I18n: LanguageDetectorMiddlewareConfig<T>;
38
39
  cors?: Parameters<typeof cors>[0];
39
40
  httpLogger?: HttpLoggerConfig;
40
41
  maintenanceMode?: ShieldMaintenanceConfig;
@@ -44,7 +45,10 @@ export class SetupHono {
44
45
  // Configure body size limit at the framework level
45
46
  // - Bun: maxRequestBodySize in Bun.serve()
46
47
  // - Express: limit in express.json()
47
- static essentials(config: Config, deps: Dependencies) {
48
+ static essentials<T extends tools.LanguageType>(
49
+ config: Config<T>,
50
+ deps: Dependencies,
51
+ ): Array<MiddlewareHandler> {
48
52
  return [
49
53
  new ShieldMaintenanceHonoStrategy(config.maintenanceMode).handle(),
50
54
  new TrailingSlashHonoMiddleware().handle(),
@@ -80,13 +84,7 @@ export class SetupHono {
80
84
  maxAge: tools.Duration.Minutes(10).seconds,
81
85
  ...config.cors,
82
86
  }),
83
- languageDetector({
84
- supportedLanguages: Object.keys(deps.I18n.supportedLanguages),
85
- fallbackLanguage: deps.I18n.defaultLanguage,
86
- // Stryker disable all
87
- caches: false,
88
- // Stryker restore all
89
- }),
87
+ new LanguageDetectorHonoMiddleware(config.I18n).handle(),
90
88
  new TimeZoneOffsetHonoMiddleware().handle(),
91
89
  new WeakETagExtractorHonoMiddleware().handle(),
92
90
  new ETagExtractorHonoMiddleware().handle(),
@@ -1,22 +1,25 @@
1
+ import type * as tools from "@bgord/tools";
1
2
  import { createFactory } from "hono/factory";
2
3
  import type { FileReaderJsonPort } from "./file-reader-json.port";
3
4
  import type { HandlerHonoPort } from "./handler-hono.port";
5
+ import type { LanguageDetectorVariables } from "./language-detector-hono.middleware";
6
+ import type { Languages } from "./languages.vo";
4
7
  import type { LoggerPort } from "./logger.port";
5
- import { type TranslationsConfig, TranslationsHandler } from "./translations.handler";
8
+ import { TranslationsHandler } from "./translations.handler";
6
9
 
7
10
  type Dependencies = { FileReaderJson: FileReaderJsonPort; Logger: LoggerPort };
8
11
 
9
12
  const factory = createFactory();
10
13
 
11
- export class TranslationsHonoHandler implements HandlerHonoPort {
12
- private readonly handler: TranslationsHandler;
14
+ export class TranslationsHonoHandler<T extends tools.LanguageType> implements HandlerHonoPort {
15
+ private readonly handler: TranslationsHandler<T>;
13
16
 
14
- constructor(config: TranslationsConfig, deps: Dependencies) {
17
+ constructor(config: Languages<T>, deps: Dependencies) {
15
18
  this.handler = new TranslationsHandler(config, deps);
16
19
  }
17
20
 
18
21
  handle() {
19
- return factory.createHandlers(async (c) => {
22
+ return factory.createHandlers<{}, any, { Variables: LanguageDetectorVariables }>(async (c) => {
20
23
  const language = c.get("language");
21
24
  const result = await this.handler.execute(language);
22
25
 
@@ -1,31 +1,30 @@
1
1
  import type * as tools from "@bgord/tools";
2
2
  import type { FileReaderJsonPort } from "./file-reader-json.port";
3
- import { I18n, type TranslationsSupportedLanguagesType, type TranslationsType } from "./i18n.service";
3
+ import { I18n, type TranslationsType } from "./i18n.service";
4
+ import type { Languages } from "./languages.vo";
4
5
  import type { LoggerPort } from "./logger.port";
5
6
 
6
- export type TranslationsConfig = TranslationsSupportedLanguagesType;
7
-
8
7
  type Dependencies = { FileReaderJson: FileReaderJsonPort; Logger: LoggerPort };
9
8
 
10
- export type TranslationsResult = {
9
+ export type TranslationsResult<T extends tools.LanguageType> = {
11
10
  translations: TranslationsType;
12
11
  language: tools.LanguageType;
13
- supportedLanguages: TranslationsSupportedLanguagesType;
12
+ supportedLanguages: Languages<T>["supported"];
14
13
  };
15
14
 
16
- export class TranslationsHandler {
15
+ export class TranslationsHandler<T extends tools.LanguageType> {
17
16
  constructor(
18
- private readonly config: TranslationsConfig,
17
+ private readonly config: Languages<T>,
19
18
  private readonly deps: Dependencies,
20
19
  ) {}
21
20
 
22
- async execute(language: string): Promise<TranslationsResult> {
21
+ async execute(language: tools.LanguageType): Promise<TranslationsResult<T>> {
23
22
  const translations = await new I18n(this.deps).getTranslations(language);
24
23
 
25
24
  return {
26
25
  translations,
27
26
  language,
28
- supportedLanguages: this.config,
27
+ supportedLanguages: this.config.supported,
29
28
  };
30
29
  }
31
30
  }
@@ -1,2 +0,0 @@
1
- export * from "./supported-languages-set";
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/preferences/value-objects/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from "./supported-languages-set";
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/preferences/value-objects/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC"}
@@ -1,10 +0,0 @@
1
- import type * as tools from "@bgord/tools";
2
- export declare const SupportedLanguagesSetError: {
3
- Missing: string;
4
- };
5
- export declare class SupportedLanguagesSet<L extends ReadonlyArray<tools.LanguageType>> {
6
- private readonly index;
7
- constructor(allowed: L);
8
- ensure(language: tools.LanguageType): L[number];
9
- }
10
- //# sourceMappingURL=supported-languages-set.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"supported-languages-set.d.ts","sourceRoot":"","sources":["../../../../src/modules/preferences/value-objects/supported-languages-set.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAE3C,eAAO,MAAM,0BAA0B;;CAAuD,CAAC;AAE/F,qBAAa,qBAAqB,CAAC,CAAC,SAAS,aAAa,CAAC,KAAK,CAAC,YAAY,CAAC;IAC5E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;gBAEpC,OAAO,EAAE,CAAC;IAKtB,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;CAIhD"}
@@ -1,14 +0,0 @@
1
- export const SupportedLanguagesSetError = { Missing: "supported.languages.set.error.missing" };
2
- export class SupportedLanguagesSet {
3
- index;
4
- constructor(allowed) {
5
- this.index = new Set(allowed);
6
- Object.freeze(this);
7
- }
8
- ensure(language) {
9
- if (!this.index.has(language))
10
- throw new Error(SupportedLanguagesSetError.Missing);
11
- return language;
12
- }
13
- }
14
- //# sourceMappingURL=supported-languages-set.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"supported-languages-set.js","sourceRoot":"","sources":["../../../../src/modules/preferences/value-objects/supported-languages-set.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,OAAO,EAAE,uCAAuC,EAAE,CAAC;AAE/F,MAAM,OAAO,qBAAqB;IACf,KAAK,CAA0B;IAEhD,YAAY,OAAU;QACpB,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,QAA4B;QACjC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,OAAO,CAAC,CAAC;QACnF,OAAO,QAAqB,CAAC;IAC/B,CAAC;CACF"}
@@ -1 +0,0 @@
1
- export * from "./supported-languages-set";
@@ -1,17 +0,0 @@
1
- import type * as tools from "@bgord/tools";
2
-
3
- export const SupportedLanguagesSetError = { Missing: "supported.languages.set.error.missing" };
4
-
5
- export class SupportedLanguagesSet<L extends ReadonlyArray<tools.LanguageType>> {
6
- private readonly index: Set<tools.LanguageType>;
7
-
8
- constructor(allowed: L) {
9
- this.index = new Set(allowed);
10
- Object.freeze(this);
11
- }
12
-
13
- ensure(language: tools.LanguageType): L[number] {
14
- if (!this.index.has(language)) throw new Error(SupportedLanguagesSetError.Missing);
15
- return language as L[number];
16
- }
17
- }