diff --git a/__tests__/buildTranslationFiles/build-translation-utils.ts b/__tests__/buildTranslationFiles/build-translation-utils.ts index b2304ab..958a20d 100644 --- a/__tests__/buildTranslationFiles/build-translation-utils.ts +++ b/__tests__/buildTranslationFiles/build-translation-utils.ts @@ -1,32 +1,15 @@ import nodePath from 'node:path'; import { - BuildConfigOptions, - defaultValue, - buildConfig as _buildConfig, - removeI18nFolder as _removeI18nFolder, assertPartialTranslation as _assertPartialTranslation, assertTranslation as _assertTranslation, AssertTranslationParams, + buildConfig as _buildConfig, + BuildConfigOptions, + removeI18nFolder as _removeI18nFolder, } from '../spec-utils'; export const sourceRoot = '__tests__/buildTranslationFiles'; -export function generateKeys({ - start = 1, - end, - prefix, -}: { - start?: number; - end: number; - prefix?: string; -}): { [index: string]: string } { - const keys = {}; - for (let i = start; i <= end; i++) { - keys[prefix ? `${prefix}.${i}` : i] = defaultValue; - } - return keys; -} - export type TranslationTestCase = | 'template-extraction/pipe' | 'template-extraction/directive' diff --git a/__tests__/buildTranslationFiles/comments/comments-spec.ts b/__tests__/buildTranslationFiles/comments/comments-spec.ts index cbf99d4..090461b 100644 --- a/__tests__/buildTranslationFiles/comments/comments-spec.ts +++ b/__tests__/buildTranslationFiles/comments/comments-spec.ts @@ -1,12 +1,15 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../build-translation-utils'; -import { defaultValue, mockResolveProjectBasePath } from '../../spec-utils'; +import { + defaultValue, + generateKeys, + mockResolveProjectBasePath, +} from '../../spec-utils'; import { Config } from '../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/config-options/multi-input/multi-input-spec.ts b/__tests__/buildTranslationFiles/config-options/multi-input/multi-input-spec.ts index 0183a0e..c9f754b 100644 --- a/__tests__/buildTranslationFiles/config-options/multi-input/multi-input-spec.ts +++ b/__tests__/buildTranslationFiles/config-options/multi-input/multi-input-spec.ts @@ -1,12 +1,15 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { defaultValue, mockResolveProjectBasePath } from '../../../spec-utils'; +import { + defaultValue, + generateKeys, + mockResolveProjectBasePath, +} from '../../../spec-utils'; import { Config } from '../../../../src/types'; import nodePath from 'node:path'; diff --git a/__tests__/buildTranslationFiles/config-options/scope-mapping/scope-mapping-spec.ts b/__tests__/buildTranslationFiles/config-options/scope-mapping/scope-mapping-spec.ts index 819eda2..6315265 100644 --- a/__tests__/buildTranslationFiles/config-options/scope-mapping/scope-mapping-spec.ts +++ b/__tests__/buildTranslationFiles/config-options/scope-mapping/scope-mapping-spec.ts @@ -1,12 +1,11 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { mockResolveProjectBasePath } from '../../../spec-utils'; +import { generateKeys, mockResolveProjectBasePath } from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/template-extraction/control-flow/control-flow-spec.ts b/__tests__/buildTranslationFiles/template-extraction/control-flow/control-flow-spec.ts index c0e3081..eeaf0c1 100644 --- a/__tests__/buildTranslationFiles/template-extraction/control-flow/control-flow-spec.ts +++ b/__tests__/buildTranslationFiles/template-extraction/control-flow/control-flow-spec.ts @@ -1,12 +1,11 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { mockResolveProjectBasePath } from '../../../spec-utils'; +import { generateKeys, mockResolveProjectBasePath } from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/template-extraction/directive/directive-spec.ts b/__tests__/buildTranslationFiles/template-extraction/directive/directive-spec.ts index 8726bd7..bda4d45 100644 --- a/__tests__/buildTranslationFiles/template-extraction/directive/directive-spec.ts +++ b/__tests__/buildTranslationFiles/template-extraction/directive/directive-spec.ts @@ -1,12 +1,15 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { defaultValue, mockResolveProjectBasePath } from '../../../spec-utils'; +import { + defaultValue, + generateKeys, + mockResolveProjectBasePath, +} from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/template-extraction/ng-container/ng-container-spec.ts b/__tests__/buildTranslationFiles/template-extraction/ng-container/ng-container-spec.ts index c4e8615..cbb6def 100644 --- a/__tests__/buildTranslationFiles/template-extraction/ng-container/ng-container-spec.ts +++ b/__tests__/buildTranslationFiles/template-extraction/ng-container/ng-container-spec.ts @@ -1,12 +1,15 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { defaultValue, mockResolveProjectBasePath } from '../../../spec-utils'; +import { + defaultValue, + generateKeys, + mockResolveProjectBasePath, +} from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/template-extraction/ng-template/ng-template-spec.ts b/__tests__/buildTranslationFiles/template-extraction/ng-template/ng-template-spec.ts index 5df383f..52dc294 100644 --- a/__tests__/buildTranslationFiles/template-extraction/ng-template/ng-template-spec.ts +++ b/__tests__/buildTranslationFiles/template-extraction/ng-template/ng-template-spec.ts @@ -1,12 +1,15 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { defaultValue, mockResolveProjectBasePath } from '../../../spec-utils'; +import { + defaultValue, + generateKeys, + mockResolveProjectBasePath, +} from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/template-extraction/pipe/pipe-spec.ts b/__tests__/buildTranslationFiles/template-extraction/pipe/pipe-spec.ts index 3f62589..5d46580 100644 --- a/__tests__/buildTranslationFiles/template-extraction/pipe/pipe-spec.ts +++ b/__tests__/buildTranslationFiles/template-extraction/pipe/pipe-spec.ts @@ -1,12 +1,15 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { defaultValue, mockResolveProjectBasePath } from '../../../spec-utils'; +import { + defaultValue, + generateKeys, + mockResolveProjectBasePath, +} from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/template-extraction/prefix/prefix-spec.ts b/__tests__/buildTranslationFiles/template-extraction/prefix/prefix-spec.ts index 78a2f62..55aff23 100644 --- a/__tests__/buildTranslationFiles/template-extraction/prefix/prefix-spec.ts +++ b/__tests__/buildTranslationFiles/template-extraction/prefix/prefix-spec.ts @@ -1,12 +1,11 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { mockResolveProjectBasePath } from '../../../spec-utils'; +import { generateKeys, mockResolveProjectBasePath } from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/template-extraction/scope/scope-spec.ts b/__tests__/buildTranslationFiles/template-extraction/scope/scope-spec.ts index 2491b93..586fb38 100644 --- a/__tests__/buildTranslationFiles/template-extraction/scope/scope-spec.ts +++ b/__tests__/buildTranslationFiles/template-extraction/scope/scope-spec.ts @@ -1,12 +1,11 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { mockResolveProjectBasePath } from '../../../spec-utils'; +import { generateKeys, mockResolveProjectBasePath } from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/ts-extraction/inline-template/inline-template-spec.ts b/__tests__/buildTranslationFiles/ts-extraction/inline-template/inline-template-spec.ts index 4b0d15a..d3942a5 100644 --- a/__tests__/buildTranslationFiles/ts-extraction/inline-template/inline-template-spec.ts +++ b/__tests__/buildTranslationFiles/ts-extraction/inline-template/inline-template-spec.ts @@ -1,12 +1,15 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { defaultValue, mockResolveProjectBasePath } from '../../../spec-utils'; +import { + defaultValue, + generateKeys, + mockResolveProjectBasePath, +} from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); diff --git a/__tests__/buildTranslationFiles/ts-extraction/pure-function/pure-function-spec.ts b/__tests__/buildTranslationFiles/ts-extraction/pure-function/pure-function-spec.ts index e24364f..83c9443 100644 --- a/__tests__/buildTranslationFiles/ts-extraction/pure-function/pure-function-spec.ts +++ b/__tests__/buildTranslationFiles/ts-extraction/pure-function/pure-function-spec.ts @@ -1,12 +1,17 @@ import { assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { mockResolveProjectBasePath } from '../../../spec-utils'; +import { + buildKeysFromParams, + mockResolveProjectBasePath, + setParamsInput, + generateKeys, + resolveValueWithParams, +} from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); @@ -26,10 +31,20 @@ export function testPureFunctionExtraction(fileFormat: Config['fileFormat']) { beforeEach(() => removeI18nFolder(type)); it('should work with the pure `translate` function', () => { - const expected = generateKeys({ end: 3 }); + const expected = generateKeys({ end: 4 }); buildTranslationFiles(config); assertTranslation({ type, expected, fileFormat }); }); + + it('should extract params', () => { + const expected = { + ...generateKeys({ end: 3, withParams: true }), + 4: resolveValueWithParams(['foo', 'a', 'b.c']), + }; + + buildTranslationFiles(setParamsInput(config)); + assertTranslation({ type, expected, fileFormat }); + }); }); } diff --git a/__tests__/buildTranslationFiles/ts-extraction/pure-function/src/issue-192.ts b/__tests__/buildTranslationFiles/ts-extraction/pure-function/src/issue-192.ts index b72ac58..50a1203 100644 --- a/__tests__/buildTranslationFiles/ts-extraction/pure-function/src/issue-192.ts +++ b/__tests__/buildTranslationFiles/ts-extraction/pure-function/src/issue-192.ts @@ -12,12 +12,12 @@ export class AppComponent { } getString(): string { - return '5'; + return '9'; } extractionProblem(): void { translate('2'); const foo = this.getString(); - translate('3'); + translate(['3', '4']); } } diff --git a/__tests__/buildTranslationFiles/ts-extraction/pure-function/with-params/1.ts b/__tests__/buildTranslationFiles/ts-extraction/pure-function/with-params/1.ts new file mode 100644 index 0000000..46d1d9c --- /dev/null +++ b/__tests__/buildTranslationFiles/ts-extraction/pure-function/with-params/1.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { translate } from '@jsverse/transloco'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrl: './app.component.scss', +}) +export class AppComponent { + constructor() { + translate('1', { 1: '' }); + translate(['2'], { 2: '' }); + } + + getString(): string { + return '9'; + } + + extractionProblem(): void { + const foo = this.getString(); + translate('4', { foo: '', a: '', b: { c: '' } }); + } +} + +translate('3', { 3: '' }); diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/service-spec.ts b/__tests__/buildTranslationFiles/ts-extraction/service/service-spec.ts index 524fe7c..7beafa5 100644 --- a/__tests__/buildTranslationFiles/ts-extraction/service/service-spec.ts +++ b/__tests__/buildTranslationFiles/ts-extraction/service/service-spec.ts @@ -2,12 +2,17 @@ import { assertPartialTranslation, assertTranslation, buildConfig, - generateKeys, removeI18nFolder, sourceRoot, TranslationTestCase, } from '../../build-translation-utils'; -import { defaultValue, mockResolveProjectBasePath } from '../../../spec-utils'; +import { + defaultValue, + generateKeys, + mockResolveProjectBasePath, + buildKeysFromParams, + setParamsInput, +} from '../../../spec-utils'; import { Config } from '../../../../src/types'; mockResolveProjectBasePath(sourceRoot); @@ -84,5 +89,20 @@ export function testServiceExtraction(fileFormat: Config['fileFormat']) { buildTranslationFiles(config); assertPartialTranslation({ type, expected, fileFormat }); }); + + it('should extract params', () => { + const expected = { + ...generateKeys({ end: 11, withParams: true }), + ...buildKeysFromParams([ + 'inject.test', + 'private-class-field.test', + 'variable', + 'another.variable', + ]), + }; + + buildTranslationFiles(setParamsInput(config)); + assertTranslation({ type, expected, fileFormat }); + }); }); } diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/src/1.ts b/__tests__/buildTranslationFiles/ts-extraction/service/src/constructor-injection-1.ts similarity index 95% rename from __tests__/buildTranslationFiles/ts-extraction/service/src/1.ts rename to __tests__/buildTranslationFiles/ts-extraction/service/src/constructor-injection-1.ts index 206253c..a998f9b 100644 --- a/__tests__/buildTranslationFiles/ts-extraction/service/src/1.ts +++ b/__tests__/buildTranslationFiles/ts-extraction/service/src/constructor-injection-1.ts @@ -144,7 +144,7 @@ export class NavPartitionsComponent implements OnInit, OnDestroy { .pipe(untilDestroyed(this)) .subscribe((res: string) => { this.lightConnectStore.setDataSegmentation(res); - this.transloco.translate('7', { hey: 'dsds' }, 'es'); + this.transloco.translate('7', null, 'es'); }); const dataSegmentation = @@ -155,14 +155,14 @@ export class NavPartitionsComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - const f = this.transloco.translate('8', { id: 1 }); + const f = this.transloco.translate('8'); if (this.dispose) { this.dispose.forEach((i) => i()); } } get iconDisabledColor() { - return this.transloco.translate('9', { id: 1 }); + return this.transloco.translate('9'); } get textDisabledColor() { @@ -174,7 +174,7 @@ export class NavPartitionsComponent implements OnInit, OnDestroy { get shouldDisableAccordion() { const shouldUsePartitions = this.form && this.form.get('shouldUsePartitions'); - this.cc = this.transloco.translate('10', { id: 1, d: 'dsds' }); + this.cc = this.transloco.translate('10'); return !shouldUsePartitions || !shouldUsePartitions.value; } diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/src/2.ts b/__tests__/buildTranslationFiles/ts-extraction/service/src/constructor-injection-2.ts similarity index 93% rename from __tests__/buildTranslationFiles/ts-extraction/service/src/2.ts rename to __tests__/buildTranslationFiles/ts-extraction/service/src/constructor-injection-2.ts index a6c51f3..93054a4 100644 --- a/__tests__/buildTranslationFiles/ts-extraction/service/src/2.ts +++ b/__tests__/buildTranslationFiles/ts-extraction/service/src/constructor-injection-2.ts @@ -41,11 +41,7 @@ export class Something implements OnInit, AfterViewInit, OnDestroy { this._dispose = [ this._onStateChange(), this.transloco.translate('1', {}, 'todos-page'), - transloco.selectTranslate( - '2.1', - { myVAr: { nested: 'bla' } }, - 'todos-page', - ), + transloco.selectTranslate('2.1', {}, 'todos-page'), reaction( () => this.leftNavStore.itemClicked, (data) => { @@ -113,7 +109,7 @@ export class Something implements OnInit, AfterViewInit, OnDestroy { } onScroll(event) { - this.transloco.selectTranslate(`6.1`, { some: 'asd' }, `nested/scope/es`); + this.transloco.selectTranslate(`6.1`, {}, `nested/scope/es`); } onSearch() { diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/src/3.ts b/__tests__/buildTranslationFiles/ts-extraction/service/src/inject.ts similarity index 100% rename from __tests__/buildTranslationFiles/ts-extraction/service/src/3.ts rename to __tests__/buildTranslationFiles/ts-extraction/service/src/inject.ts diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/src/4.ts b/__tests__/buildTranslationFiles/ts-extraction/service/src/private-property.ts similarity index 100% rename from __tests__/buildTranslationFiles/ts-extraction/service/src/4.ts rename to __tests__/buildTranslationFiles/ts-extraction/service/src/private-property.ts diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/src/5.ts b/__tests__/buildTranslationFiles/ts-extraction/service/src/store-in-variable.ts similarity index 100% rename from __tests__/buildTranslationFiles/ts-extraction/service/src/5.ts rename to __tests__/buildTranslationFiles/ts-extraction/service/src/store-in-variable.ts diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/with-params/constructor-injection.ts b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/constructor-injection.ts new file mode 100644 index 0000000..51afa48 --- /dev/null +++ b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/constructor-injection.ts @@ -0,0 +1,155 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Inject, + Input, + OnDestroy, + OnInit, + ViewChild, +} from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { MatExpansionPanel } from '@angular/material'; +import { ID } from '@datorama/akita'; +import { + TranslocoService, + translate, + TRANSLOCO_SCOPE, +} from '@jsverse/transloco'; +import { untilDestroyed } from 'ngx-take-until-destroy'; +@Component({ + selector: 'nav-partitions', + templateUrl: './partitions.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: TRANSLOCO_SCOPE, + useValue: { scope: 'admin-page', alias: 'adminPage' }, + }, + ], +}) +export class NavPartitionsComponent implements OnInit, OnDestroy { + brandDataSourceId: ID; + dataSegmentation = new FormControl(DataSegmentation.DAILY); + dataSegmentationList = Object.keys(DataSegmentation).map((key) => ({ + id: DataSegmentation[key], + })); + arrayResult: string[]; + arrayResult2: string[]; + /** me no */ + private dispose: any[] = []; + + constructor( + private cdr: ChangeDetectorRef, + private formBuilder: FormBuilder, + + public transloco: TranslocoService, + ) { + this.transloco.translate('1', { 1: '' }); + const a = 'asdf'; + const b = 'asdf'; + const c = 'asdf'; + transloco + .selectTranslate('3', { 3: '' }) + .subscribe((t) => (this.arrayResult = t)); + transloco + .selectTranslate(['2'], { 2: '' }) + .subscribe((t) => (this.arrayResult = t)); + } + + ngOnInit(): void { + this.form = this.formBuilder.group({ + segmentationBucketsNum: [metadata.segmentationBucketsNum || 0], + segmentationFields: [metadata.segmentationFields || []], + dd: translate(dontTake, { id: 'fdfd' }, 'ess'), + dfjdlfd: this.transloco.translate('4', { 4: 123 }), + }); + + this.form + .get('segmentationBucketsNum') + .valueChanges.pipe(untilDestroyed(this)) + .subscribe((res: number) => { + this.cdr.detectChanges(); + this.transloco = this.transloco.translate(`5`, { 5: {} }); + }); + + this.form + .get('segmentationFields') + .valueChanges.pipe(untilDestroyed(this)) + .subscribe((res: IDataPreviewColumn[]) => { + this.cdr.detectChanges(); + }); + + this.transloco.translate(''); + this.form + .get('shouldUsePartitions') + .valueChanges.pipe(untilDestroyed(this)) + .subscribe((res: boolean) => { + if (res) { + this.transloco.translate('6', { 6: new Date() }); + this.form.patchValue({ + segmentationBucketsNum: this.transloco.translate(`7`, { 7: 12 }), + }); + } else { + this.form.patchValue({ + segmentationFields: [this.transloco.translate(`8`, { 8: '' })], + segmentationBucketsNum: 0, + }); + this.activeIds = []; + } + + this.cdr.detectChanges(); + }); + + this.dispose.push( + autorun(() => {}), + autorun(() => { + const fields = toJS(this.lightConnectStore.segmentationFields); + segmentationFields.patchValue(fields); + this.form.get('shouldUsePartitions').patchValue(true); + this.cdr.detectChanges(); + }), + autorun(() => { + this.isDimensionOnly = this.lightConnectStore.isDimensionOnly; + /** Close all the accordions */ + this.activeIds = []; + this.cdr.detectChanges(); + }), + ); + + this.brandDataSourceId = () => + this.transloco.translate('9', { 9: 1 }, 'en'); + this.dataSegmentation.valueChanges + .pipe(untilDestroyed(this)) + .subscribe((res: string) => { + this.lightConnectStore.setDataSegmentation(res); + this.transloco.translate('10', { 10: 1 }, 'es'); + }); + + const dataSegmentation = + this.lightConnectStore.dataPreview.metadata.dataSegmentation; + if (!isNil(dataSegmentation)) { + this.dataSegmentation.patchValue(dataSegmentation); + } + } + + ngOnDestroy(): void { + if (this.dispose) { + this.dispose.forEach((i) => i()); + } + } + + get textDisabledColor() { + return this.form.get('shouldUsePartitions').value + ? 'primary-400' + : 'primary-200'; + } + + get shouldDisableAccordion() { + const shouldUsePartitions = + this.form && this.form.get('shouldUsePartitions'); + this.cc = this.transloco.translate('11', { 11: 11 }); + + return !shouldUsePartitions || !shouldUsePartitions.value; + } +} diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/with-params/inject.ts b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/inject.ts new file mode 100644 index 0000000..ed0432c --- /dev/null +++ b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/inject.ts @@ -0,0 +1,14 @@ +import { Component, OnInit, inject } from '@angular/core'; +import { TranslocoService } from '@jsverse/transloco'; + +@Component({ + selector: 'inject-test', + template: ``, +}) +export class InjectTestClass implements OnInit { + transloco = inject(TranslocoService); + + ngOnInit() { + this.transloco.translate('inject.test', { inject: { test: 1 } }); + } +} diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/with-params/private-property.ts b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/private-property.ts new file mode 100644 index 0000000..c982aae --- /dev/null +++ b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/private-property.ts @@ -0,0 +1,16 @@ +import { Component, OnInit, inject } from '@angular/core'; +import { TranslocoService } from '@jsverse/transloco'; + +@Component({ + selector: 'private-class-field-test', + template: ``, +}) +export class PrivateClassFieldInjectTestClass implements OnInit { + #transloco = inject(TranslocoService); + + ngOnInit() { + this.#transloco.translate('private-class-field.test', { + 'private-class-field': { test: 1 }, + }); + } +} diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/with-params/store-in-variable.ts b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/store-in-variable.ts new file mode 100644 index 0000000..aad43ea --- /dev/null +++ b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/store-in-variable.ts @@ -0,0 +1,33 @@ +import { TranslocoService } from '@jsverse/transloco'; +import { inject } from '@angular/core'; +import { Observable, zip, switchMap, tap, map, of } from 'rxjs'; +import { PermissionService } from './permission.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +export function hasPermissionFactory(): Observable { + const permission = inject(PermissionService); + const translate = inject(TranslocoService); + const snackBarManager = inject(MatSnackBar); + + return permission.hasPermissions().pipe( + switchMap((hasPermission) => { + if (!hasPermission) { + return zip([ + translate.selectTranslate('variable', { variable: 'hasPermission' }), + translate.selectTranslate('another.variable', { + another: { variable: 'hasPermission' }, + }), + ]).pipe( + tap(([message, close]) => { + snackBarManager.open(message, close, { + duration: 3000, + horizontalPosition: 'right', + }); + }), + map(() => false), + ); + } + return of(true); + }), + ); +} diff --git a/__tests__/buildTranslationFiles/ts-extraction/service/with-params/todos-scope.ts b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/todos-scope.ts new file mode 100644 index 0000000..cf8823f --- /dev/null +++ b/__tests__/buildTranslationFiles/ts-extraction/service/with-params/todos-scope.ts @@ -0,0 +1,4 @@ +export const d = { + provide: TRANSLOCO_SCOPE, + useValue: 'todos-page', +}; diff --git a/__tests__/spec-utils.ts b/__tests__/spec-utils.ts index 57b780d..dcf8842 100644 --- a/__tests__/spec-utils.ts +++ b/__tests__/spec-utils.ts @@ -33,6 +33,22 @@ export function mockResolveProjectBasePath(projectBasePath: string) { } export const defaultValue = 'missing'; +export const defaultValueWithParams = 'missing{{params}}'; + +export function resolveValueWithParams(params: string[]) { + return defaultValueWithParams.replace( + '{{params}}', + params.map((p) => `{{${p}}}`).join(' '), + ); +} + +export function buildKeysFromParams(params: string[]): Record { + return params.reduce((acc, p) => { + acc[p] = resolveValueWithParams([p]); + + return acc; + }, {}); +} export interface BuildConfigOptions { config?: Partial; @@ -51,6 +67,14 @@ export function buildConfig({ config = {}, sourceRoot }: BuildConfigOptions) { } as Config; } +export function setParamsInput(config: Config) { + return { + ...config, + input: [nodePath.resolve(config.input[0], '../', 'with-params')], + defaultValue: defaultValueWithParams, + }; +} + export function removeI18nFolder(root = sourceRoot) { fs.removeSync(nodePath.join(root, 'i18n')); } @@ -85,3 +109,30 @@ function loadTranslationFile({ fileFormat, }); } + +interface GenerateKeysParams { + start?: number; + end: number; + prefix?: string; + withParams?: boolean; +} +export function generateKeys({ + start = 1, + end, + prefix, + withParams = false, +}: GenerateKeysParams): { [index: string]: string } { + let keys = {}; + for (let i = start; i <= end; i++) { + const key = prefix ? `${prefix}.${i}` : `${i}`; + if (withParams) { + keys = { + ...keys, + ...buildKeysFromParams([key]), + }; + } else { + keys[key] = defaultValue; + } + } + return keys; +} diff --git a/src/keys-builder/add-key.ts b/src/keys-builder/add-key.ts index 3a98367..a027547 100644 --- a/src/keys-builder/add-key.ts +++ b/src/keys-builder/add-key.ts @@ -1,10 +1,11 @@ import { messages } from '../messages'; import { BaseParams } from '../types'; -import { isNil } from '../utils/validators.utils'; +import { isFunction, isNil, isString } from '../utils/validators.utils'; interface AddKeysParams extends BaseParams { scopeAlias: string | null; keyWithoutScope: string; + params?: string[]; } export function addKey({ @@ -13,6 +14,7 @@ export function addKey({ scopeAlias, keyWithoutScope, scopes, + params = [], }: AddKeysParams) { if (!keyWithoutScope) { return; @@ -22,11 +24,14 @@ export function addKey({ const keyWithScope = scopeAlias ? `${scopeAlias}.${keyWithoutScope}` : keyWithoutScope; + const paramsWithInterpolation = params.map((p) => `{{${p}}}`).join(' '); + const keyValue = isNil(defaultValue) ? `${messages.missingValue} '${keyWithScope}'` : defaultValue .replace('{{key}}', keyWithScope) .replace('{{keyWithoutScope}}', keyWithoutScope) + .replace('{{params}}', paramsWithInterpolation) .replace('{{scope}}', scopeAlias || ''); if (scopePath) { diff --git a/src/keys-builder/typescript/build-keys-from-ast-nodes.ts b/src/keys-builder/typescript/build-keys-from-ast-nodes.ts index 8648b7f..5289c52 100644 --- a/src/keys-builder/typescript/build-keys-from-ast-nodes.ts +++ b/src/keys-builder/typescript/build-keys-from-ast-nodes.ts @@ -2,6 +2,7 @@ import { Node, StringLiteral, NoSubstitutionTemplateLiteral } from 'typescript'; import ts from 'typescript'; import { TSExtractorResult } from './types'; +import { flatten } from 'flat'; export function buildKeysFromASTNodes( nodes: Node[], @@ -10,31 +11,35 @@ export function buildKeysFromASTNodes( const result: TSExtractorResult = []; for (let node of nodes) { - if (ts.isCallExpression(node.parent)) { - const method = node.parent.expression; - let methodName = ''; - if (ts.isIdentifier(method)) { - methodName = method.text; - } else if (ts.isPropertyAccessExpression(method)) { - methodName = method.name.text; - } - if (!allowedMethods.includes(methodName)) { - continue; - } - - const [keyNode, _, langNode] = node.parent.arguments; - let lang = isStringNode(langNode) ? langNode.text : ''; - let keys: string[] = []; - - if (isStringNode(keyNode)) { - keys = [keyNode.text]; - } else if (ts.isArrayLiteralExpression(keyNode)) { - keys = keyNode.elements.filter(isStringNode).map((node) => node.text); - } - - for (const key of keys) { - result.push({ key, lang }); - } + if (!ts.isCallExpression(node.parent)) continue; + + const method = node.parent.expression; + let methodName = ''; + if (ts.isIdentifier(method)) { + methodName = method.text; + } else if (ts.isPropertyAccessExpression(method)) { + methodName = method.name.text; + } + if (!allowedMethods.includes(methodName)) { + continue; + } + + const [keyNode, paramsNode, langNode] = node.parent.arguments; + let lang = isStringNode(langNode) ? langNode.text : ''; + let keys: string[] = []; + const params: string[] = + paramsNode && ts.isObjectLiteralExpression(paramsNode) + ? resolveParams(paramsNode) + : []; + + if (isStringNode(keyNode)) { + keys = [keyNode.text]; + } else if (ts.isArrayLiteralExpression(keyNode)) { + keys = keyNode.elements.filter(isStringNode).map((node) => node.text); + } + + for (const key of keys) { + result.push({ key, lang, params }); } } @@ -49,3 +54,39 @@ function isStringNode( (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ); } + +function resolveParams(params: ts.ObjectLiteralExpression): string[] { + return Object.keys(flatten(traverseParams(params))); +} + +function traverseParams( + params: ts.ObjectLiteralExpression, +): Record { + const properties: Record = {}; + + // Recursive function to handle nested properties + function processProperty(property: ts.PropertyAssignment) { + const key = property.name.getText().replace(/['"]/g, ''); + const initializer = property.initializer; + + if (!initializer) return; + + if (ts.isObjectLiteralExpression(initializer)) { + // Handle nested object + properties[key] = traverseParams(initializer); + } else { + // Simple value (string, number, etc.) + properties[key] = initializer.getText(); + } + } + + // Iterate through the properties of the ObjectLiteralExpression + for (const property of params.properties) { + if (ts.isPropertyAssignment(property)) { + processProperty(property); + } + } + + // Convert the properties object to a JSON string + return properties; +} diff --git a/src/keys-builder/typescript/index.ts b/src/keys-builder/typescript/index.ts index 5845d02..75c58ad 100644 --- a/src/keys-builder/typescript/index.ts +++ b/src/keys-builder/typescript/index.ts @@ -48,7 +48,7 @@ function TSExtractor(config: ExtractorConfig): ScopeMap { extractors .map((ex) => ex(ast)) .flat() - .forEach(({ key, lang }) => { + .forEach(({ key, lang, params }) => { const [keyWithoutScope, scopeAlias] = resolveAliasAndKeyFromService( key, lang, @@ -57,6 +57,7 @@ function TSExtractor(config: ExtractorConfig): ScopeMap { addKey({ scopeAlias, keyWithoutScope, + params, ...baseParams, }); }); diff --git a/src/keys-builder/typescript/types.ts b/src/keys-builder/typescript/types.ts index 26bac15..4b4a36f 100644 --- a/src/keys-builder/typescript/types.ts +++ b/src/keys-builder/typescript/types.ts @@ -1 +1,5 @@ -export type TSExtractorResult = { key: string; lang: string }[]; +export type TSExtractorResult = { + key: string; + lang: string; + params: string[]; +}[]; diff --git a/src/utils/validators.utils.ts b/src/utils/validators.utils.ts index b887938..50aefd4 100644 --- a/src/utils/validators.utils.ts +++ b/src/utils/validators.utils.ts @@ -4,6 +4,10 @@ export function isObject(value: any): value is Record { return value && typeof value === 'object' && !Array.isArray(value); } +export function isFunction(value: any): value is (...args: any[]) => any { + return typeof value === 'function'; +} + export function isNil(value: unknown): value is undefined | null { return isUndefined(value) || value === null; }