@nestjs-dash/translation 0.2.0

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 (54) hide show
  1. package/README.md +131 -0
  2. package/dist/constants.d.ts +3 -0
  3. package/dist/constants.js +3 -0
  4. package/dist/context/translation-context.middleware.d.ts +12 -0
  5. package/dist/context/translation-context.middleware.js +66 -0
  6. package/dist/context/translation-context.service.d.ts +17 -0
  7. package/dist/context/translation-context.service.js +52 -0
  8. package/dist/decorators/locale.decorator.d.ts +1 -0
  9. package/dist/decorators/locale.decorator.js +6 -0
  10. package/dist/decorators/response.decorator.d.ts +7 -0
  11. package/dist/decorators/response.decorator.js +7 -0
  12. package/dist/decorators/translatable-column.decorator.d.ts +3 -0
  13. package/dist/decorators/translatable-column.decorator.js +13 -0
  14. package/dist/decorators/translatable-property.decorator.d.ts +13 -0
  15. package/dist/decorators/translatable-property.decorator.js +20 -0
  16. package/dist/index.d.ts +22 -0
  17. package/dist/index.js +22 -0
  18. package/dist/interceptors/translation.interceptor.d.ts +12 -0
  19. package/dist/interceptors/translation.interceptor.js +43 -0
  20. package/dist/metadata/translation-metadata.storage.d.ts +4 -0
  21. package/dist/metadata/translation-metadata.storage.js +22 -0
  22. package/dist/migration/cli-args.d.ts +12 -0
  23. package/dist/migration/cli-args.js +38 -0
  24. package/dist/migration/cli.d.ts +2 -0
  25. package/dist/migration/cli.js +66 -0
  26. package/dist/migration/migration-dialect.adapter.d.ts +12 -0
  27. package/dist/migration/migration-dialect.adapter.js +1 -0
  28. package/dist/migration/migration-template.d.ts +1 -0
  29. package/dist/migration/migration-template.js +29 -0
  30. package/dist/migration/mysql-migration.adapter.d.ts +7 -0
  31. package/dist/migration/mysql-migration.adapter.js +17 -0
  32. package/dist/migration/postgres-migration.adapter.d.ts +7 -0
  33. package/dist/migration/postgres-migration.adapter.js +17 -0
  34. package/dist/migration/translatable-migration.generator.d.ts +14 -0
  35. package/dist/migration/translatable-migration.generator.js +173 -0
  36. package/dist/query/mysql-query.adapter.d.ts +6 -0
  37. package/dist/query/mysql-query.adapter.js +12 -0
  38. package/dist/query/postgres-query.adapter.d.ts +6 -0
  39. package/dist/query/postgres-query.adapter.js +12 -0
  40. package/dist/query/translation-query.adapter.d.ts +5 -0
  41. package/dist/query/translation-query.adapter.js +1 -0
  42. package/dist/query/translation-query.service.d.ts +15 -0
  43. package/dist/query/translation-query.service.js +69 -0
  44. package/dist/services/translation.service.d.ts +15 -0
  45. package/dist/services/translation.service.js +109 -0
  46. package/dist/testing/fixtures.d.ts +21 -0
  47. package/dist/testing/fixtures.js +53 -0
  48. package/dist/transformers/translation-response.transformer.d.ts +11 -0
  49. package/dist/transformers/translation-response.transformer.js +75 -0
  50. package/dist/translatable.module.d.ts +10 -0
  51. package/dist/translatable.module.js +108 -0
  52. package/dist/types.d.ts +100 -0
  53. package/dist/types.js +1 -0
  54. package/package.json +50 -0
@@ -0,0 +1,75 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { Inject, Injectable } from '@nestjs/common';
14
+ import { TRANSLATABLE_MODULE_OPTIONS } from '../constants.js';
15
+ import { TranslationContextService } from '../context/translation-context.service.js';
16
+ import { getTranslatableColumns } from '../metadata/translation-metadata.storage.js';
17
+ import { TranslationService } from '../services/translation.service.js';
18
+ let TranslationResponseTransformer = class TranslationResponseTransformer {
19
+ constructor(options, context, translations) {
20
+ this.options = options;
21
+ this.context = context;
22
+ this.translations = translations;
23
+ }
24
+ transform(value, mode = this.context.responseMode, locale = this.context.locale) {
25
+ return this.visit(value, mode, locale, new WeakMap());
26
+ }
27
+ visit(value, mode, locale, seen) {
28
+ if (value == null || typeof value !== 'object')
29
+ return value;
30
+ if (value instanceof Date || Buffer.isBuffer(value) || value instanceof Promise) {
31
+ return value;
32
+ }
33
+ if (seen.has(value))
34
+ return seen.get(value);
35
+ if (Array.isArray(value)) {
36
+ const output = [];
37
+ seen.set(value, output);
38
+ value.forEach((item) => output.push(this.visit(item, mode, locale, seen)));
39
+ return output;
40
+ }
41
+ const source = value;
42
+ const output = this.options.mutateResponses ? source : {};
43
+ seen.set(value, output);
44
+ const columns = getTranslatableColumns(value.constructor);
45
+ const columnKeys = new Set(columns.map((column) => String(column.propertyKey)));
46
+ for (const [key, item] of Object.entries(source)) {
47
+ if (columnKeys.has(key))
48
+ continue;
49
+ output[key] = this.visit(item, mode, locale, seen);
50
+ }
51
+ for (const column of columns) {
52
+ const key = String(column.propertyKey);
53
+ const raw = source[key];
54
+ if (mode === 'raw') {
55
+ output[key] = this.visit(raw, mode, locale, seen);
56
+ continue;
57
+ }
58
+ const localized = this.translations.resolve(raw, locale, column.fallbackLocale ?? this.context.fallbackLocale, column.fallbackStrategy ?? this.options.fallbackStrategy);
59
+ if (mode === 'both') {
60
+ output[`${key}Translations`] = this.visit(raw, mode, locale, seen);
61
+ }
62
+ output[key] = localized;
63
+ }
64
+ return output;
65
+ }
66
+ };
67
+ TranslationResponseTransformer = __decorate([
68
+ Injectable(),
69
+ __param(0, Inject(TRANSLATABLE_MODULE_OPTIONS)),
70
+ __param(1, Inject(TranslationContextService)),
71
+ __param(2, Inject(TranslationService)),
72
+ __metadata("design:paramtypes", [Object, TranslationContextService,
73
+ TranslationService])
74
+ ], TranslationResponseTransformer);
75
+ export { TranslationResponseTransformer };
@@ -0,0 +1,10 @@
1
+ import { type DynamicModule, type MiddlewareConsumer, type NestModule } from '@nestjs/common';
2
+ import type { TranslatableModuleAsyncOptions, TranslatableModuleOptions } from './types.js';
3
+ export declare class TranslatableModule implements NestModule {
4
+ private readonly _options;
5
+ constructor(_options: TranslatableModuleOptions);
6
+ static forRoot(options: TranslatableModuleOptions): DynamicModule;
7
+ static forRootAsync(options: TranslatableModuleAsyncOptions): DynamicModule;
8
+ configure(consumer: MiddlewareConsumer): void;
9
+ private static validate;
10
+ }
@@ -0,0 +1,108 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ var TranslatableModule_1;
14
+ import { Inject, Module, } from '@nestjs/common';
15
+ import { APP_INTERCEPTOR, Reflector } from '@nestjs/core';
16
+ import { TRANSLATABLE_MODULE_OPTIONS } from './constants.js';
17
+ import { TranslationContextMiddleware } from './context/translation-context.middleware.js';
18
+ import { TranslationContextService } from './context/translation-context.service.js';
19
+ import { TranslationInterceptor } from './interceptors/translation.interceptor.js';
20
+ import { TranslationQueryService } from './query/translation-query.service.js';
21
+ import { TranslationService } from './services/translation.service.js';
22
+ import { TranslationResponseTransformer } from './transformers/translation-response.transformer.js';
23
+ const interceptorProvider = {
24
+ provide: APP_INTERCEPTOR,
25
+ inject: [TRANSLATABLE_MODULE_OPTIONS, TranslationInterceptor],
26
+ useFactory: (options, interceptor) => options.registerInterceptor === false
27
+ ? {
28
+ intercept: (_ctx, next) => next.handle(),
29
+ }
30
+ : interceptor,
31
+ };
32
+ const coreProviders = [
33
+ Reflector,
34
+ TranslationContextService,
35
+ TranslationContextMiddleware,
36
+ TranslationService,
37
+ TranslationInterceptor,
38
+ TranslationResponseTransformer,
39
+ TranslationQueryService,
40
+ interceptorProvider,
41
+ ];
42
+ let TranslatableModule = TranslatableModule_1 = class TranslatableModule {
43
+ constructor(_options) {
44
+ this._options = _options;
45
+ }
46
+ static forRoot(options) {
47
+ TranslatableModule_1.validate(options);
48
+ return {
49
+ global: true,
50
+ module: TranslatableModule_1,
51
+ providers: [{ provide: TRANSLATABLE_MODULE_OPTIONS, useValue: options }, ...coreProviders],
52
+ exports: [
53
+ TRANSLATABLE_MODULE_OPTIONS,
54
+ TranslationContextService,
55
+ TranslationService,
56
+ TranslationInterceptor,
57
+ TranslationResponseTransformer,
58
+ TranslationQueryService,
59
+ ],
60
+ };
61
+ }
62
+ static forRootAsync(options) {
63
+ return {
64
+ global: true,
65
+ module: TranslatableModule_1,
66
+ imports: options.imports ?? [],
67
+ providers: [
68
+ {
69
+ provide: TRANSLATABLE_MODULE_OPTIONS,
70
+ inject: options.inject ?? [],
71
+ useFactory: async (...args) => {
72
+ const value = await options.useFactory(...args);
73
+ TranslatableModule_1.validate(value);
74
+ return value;
75
+ },
76
+ },
77
+ ...coreProviders,
78
+ ],
79
+ exports: [
80
+ TRANSLATABLE_MODULE_OPTIONS,
81
+ TranslationContextService,
82
+ TranslationService,
83
+ TranslationInterceptor,
84
+ TranslationResponseTransformer,
85
+ TranslationQueryService,
86
+ ],
87
+ };
88
+ }
89
+ configure(consumer) {
90
+ consumer.apply(TranslationContextMiddleware).forRoutes('*');
91
+ }
92
+ static validate(options) {
93
+ if (!options.defaultLocale)
94
+ throw new Error('defaultLocale is required.');
95
+ if (!options.supportedLocales.includes(options.defaultLocale)) {
96
+ throw new Error('supportedLocales must contain defaultLocale.');
97
+ }
98
+ if (options.fallbackLocale && !options.supportedLocales.includes(options.fallbackLocale)) {
99
+ throw new Error('supportedLocales must contain fallbackLocale.');
100
+ }
101
+ }
102
+ };
103
+ TranslatableModule = TranslatableModule_1 = __decorate([
104
+ Module({}),
105
+ __param(0, Inject(TRANSLATABLE_MODULE_OPTIONS)),
106
+ __metadata("design:paramtypes", [Object])
107
+ ], TranslatableModule);
108
+ export { TranslatableModule };
@@ -0,0 +1,100 @@
1
+ import type { Type } from '@nestjs/common';
2
+ import type { ColumnOptions, DataSource } from 'typeorm';
3
+ export type TranslationMap<T = string> = Record<string, T | null | undefined>;
4
+ export type TranslationFallbackStrategy = 'default-locale' | 'first-available' | 'null' | 'throw';
5
+ export type TranslationResponseMode = 'localized' | 'raw' | 'both';
6
+ export interface TranslationBehaviorOptions {
7
+ fallbackLocale?: string;
8
+ fallbackStrategy?: TranslationFallbackStrategy;
9
+ /**
10
+ * Locale that a pre-existing, not-yet-translatable scalar column's value
11
+ * should be backfilled under when the migration generator converts it to
12
+ * JSON. Defaults to the module's `defaultLocale` when omitted.
13
+ */
14
+ sourceLocale?: string;
15
+ }
16
+ export interface TranslatableColumnOptions {
17
+ column?: ColumnOptions;
18
+ translation?: TranslationBehaviorOptions;
19
+ }
20
+ export type TranslatableStorageType = 'jsonb' | 'json' | 'simple-json';
21
+ export interface TranslatableColumnMetadata extends TranslationBehaviorOptions {
22
+ propertyKey: string | symbol;
23
+ }
24
+ export interface TranslationRequestContext {
25
+ locale: string;
26
+ fallbackLocale: string;
27
+ responseMode: TranslationResponseMode;
28
+ }
29
+ export interface TranslationRouteOptions {
30
+ skip?: boolean;
31
+ locale?: string;
32
+ mode?: TranslationResponseMode;
33
+ }
34
+ export interface LocaleResolverContext {
35
+ query?: Record<string, unknown>;
36
+ headers?: Record<string, string | string[] | undefined>;
37
+ }
38
+ export interface LocaleResolver {
39
+ resolve(context: LocaleResolverContext): string | undefined;
40
+ }
41
+ export interface TranslatableModuleOptions {
42
+ defaultLocale: string;
43
+ fallbackLocale?: string;
44
+ supportedLocales: string[];
45
+ responseMode?: TranslationResponseMode;
46
+ fallbackStrategy?: TranslationFallbackStrategy;
47
+ queryParameter?: string;
48
+ headerName?: string;
49
+ strictLocales?: boolean;
50
+ mutateResponses?: boolean;
51
+ localeResolvers?: LocaleResolver[];
52
+ /**
53
+ * Whether to automatically register `TranslationInterceptor` as a global
54
+ * `APP_INTERCEPTOR`. Defaults to true. Set to false to attach it yourself
55
+ * (e.g. `app.useGlobalInterceptors(app.get(TranslationInterceptor))` in
56
+ * `main.ts`) instead of having it wired up implicitly by this module.
57
+ */
58
+ registerInterceptor?: boolean;
59
+ }
60
+ export interface TranslatableModuleAsyncOptions {
61
+ imports?: any[];
62
+ inject?: Array<string | symbol | Type<unknown>>;
63
+ useFactory: (...args: unknown[]) => Promise<TranslatableModuleOptions> | TranslatableModuleOptions;
64
+ }
65
+ export interface LocalizedExpressionOptions {
66
+ alias: string;
67
+ property: string;
68
+ locale?: string;
69
+ fallbackLocale?: string;
70
+ }
71
+ export interface TranslatableMigrationOptions {
72
+ dataSource: DataSource;
73
+ supportedLocales: string[];
74
+ defaultLocale: string;
75
+ /** Restrict generation to these entity classes. Defaults to every entity registered on the DataSource. */
76
+ entities?: Type<unknown>[];
77
+ /** Directory the migration file is written to. Defaults to './migrations'. */
78
+ outputDir?: string;
79
+ /** Base name used to build the migration class/file name. Defaults to 'TranslatableColumns'. */
80
+ name?: string;
81
+ /** Compute the plan without writing a migration file. Defaults to false. */
82
+ dryRun?: boolean;
83
+ }
84
+ export type TranslatableMigrationAction = 'create' | 'convert' | 'skip';
85
+ export interface TranslatableMigrationColumnPlan {
86
+ table: string;
87
+ column: string;
88
+ action: TranslatableMigrationAction;
89
+ sourceLocale?: string;
90
+ reason?: string;
91
+ }
92
+ export interface TranslatableMigrationPlan {
93
+ className: string;
94
+ fileName: string;
95
+ filePath?: string;
96
+ upStatements: string[];
97
+ downStatements: string[];
98
+ columns: TranslatableMigrationColumnPlan[];
99
+ written: boolean;
100
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@nestjs-dash/translation",
3
+ "version": "0.2.0",
4
+ "description": "Database-aware translatable JSON columns for NestJS and TypeORM",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "nestjs-dash-translation": "./dist/migration/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "type": "module",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "devDependencies": {
23
+ "@nestjs-dash/typescript-config": "0.1.0",
24
+ "@nestjs/common": "^12.0.1",
25
+ "@nestjs/core": "^12.0.1",
26
+ "@types/express": "^5.0.6",
27
+ "@types/node": "^22.20.2",
28
+ "reflect-metadata": "^0.2.2",
29
+ "rxjs": "^7.8.2",
30
+ "typeorm": "^1.1.1",
31
+ "typescript": "6.0.3",
32
+ "vitest": "^5.0.0"
33
+ },
34
+ "peerDependencies": {
35
+ "@nestjs/common": "^12.0.0",
36
+ "@nestjs/core": "^12.0.0",
37
+ "reflect-metadata": "^0.2.0",
38
+ "rxjs": "^7.8.0",
39
+ "typeorm": ">=0.2.41 <0.3.0-0 || ^0.3.20 || ^1.0.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.json && chmod +x dist/migration/cli.js",
43
+ "check-types": "tsc -p tsconfig.json --noEmit",
44
+ "format": "oxfmt",
45
+ "lint": "oxlint src",
46
+ "test": "vitest run",
47
+ "test:coverage": "vitest run --coverage",
48
+ "dev": "tsc -p tsconfig.json --watch"
49
+ }
50
+ }