Skip to content

Commit

Permalink
feat: 🎸 Add support for self closing tag and control flow (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
kekel87 authored Mar 15, 2024
1 parent ac283b7 commit 0e3ab54
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 6 deletions.
23 changes: 19 additions & 4 deletions __tests__/buildTranslationFiles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ type TranslationCategory =
| 'multi-input'
| 'scope-mapping'
| 'comments'
| 'remove-extra-keys';
| 'remove-extra-keys'
| 'self-closing'
| 'control-flow';

interface assertTranslationParams extends Pick<Config, 'fileFormat'> {
type: TranslationCategory;
Expand Down Expand Up @@ -135,7 +137,7 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
'49.50.51.52': defaultValue,
...generateKeys({ start: 53, end: 62 }),
'63.64.65': defaultValue,
...generateKeys({ start: 66, end: 78 }),
...generateKeys({ start: 66, end: 79 }),
'{{count}} items': defaultValue,
};
[
Expand All @@ -160,7 +162,7 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
beforeEach(() => removeI18nFolder(type));

it('should work with directive', () => {
const expected = generateKeys({ end: 23 });
const expected = generateKeys({ end: 24 });
['Processing archive...', 'Restore Options'].forEach(
(nonNumericKey) => {
expected[nonNumericKey] = defaultValue;
Expand Down Expand Up @@ -214,7 +216,7 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
beforeEach(() => removeI18nFolder(type));

it('should work with ngTemplate', () => {
let expected = generateKeys({ end: 41 });
let expected = generateKeys({ end: 42 });
createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
Expand All @@ -238,6 +240,19 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
});
});

describe('Control flow', () => {
const type: TranslationCategory = 'control-flow';
const config = gConfig(type);

beforeEach(() => removeI18nFolder(type));

it('should work with control flow', () => {
let expected = generateKeys({ end: 26 });
createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
});

describe('read', () => {
const type: TranslationCategory = 'read';
const config = gConfig(type);
Expand Down
79 changes: 79 additions & 0 deletions __tests__/control-flow/1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<span>{{ '1' | transloco }}</span>
<span transloco="2"></span>
<ng-container *transloco="let t">
<span>{{ t('3') }}</span>
</ng-container>
<ng-template transloco let-t>
<span>{{ t('4') }}</span>
</ng-template>


@if (a > b) {
<span>{{ '5' | transloco }}</span>
<span transloco="6">
</span>
<ng-container *transloco="let t">
<span>{{ t('7') }}</span>
</ng-container>
<ng-template transloco let-t>
<span>{{ t('8') }}</span>
</ng-template>
} @else if (b > a) {
<span>{{ '9' | transloco }}</span>
} @else {
<span [innerHtml]="'10' | transloco"></span>
}

@for (item of items; track item.id) {
{{ '11' | transloco }}
} @empty {
<span>{{ '12' | transloco }}</span>
}

@switch (condition) {
@case (caseA) {
<span>{{ '13' | transloco }}</span>
}
@default {
<span>{{ '14' | transloco }}</span>
}
}

@defer {
<span>{{ '15' | transloco }}</span>
} @error {
<span>{{ '16' | transloco }}</span>
} @placeholder {
<span>{{ '17' | transloco }}</span>
} @loading {
<span>{{ '18' | transloco }}</span>
}

<ng-container *transloco="let t">
@if (a > b) {
<span>{{ t('19') }}</span>
} @else if (b > a) {
<span [innerHtml]="t('20')"></span>
} @else {
@for (item of items; track item.id) {
<span>{{ t('21') }}</span>
} @empty {
@switch (condition) {
@case (caseA) {
<span>{{ t('22') }}</span>
}
@default {
@defer {
<span>{{ '23' | transloco }}</span>
} @error {
<span transloco="24"></span>
} @placeholder {
<span>{{ t('25') }}</span>
} @loading {
<span>{{ t('26') }}</span>
}
}
}
}
}
</ng-container>
1 change: 1 addition & 0 deletions __tests__/directive/2.html
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,5 @@
<div class="my-agenda-header d-flex" transloco="{{'20'}}"></div>
<div class="my-agenda-header d-flex" transloco="{{condition ? '21' : dontTake}}"></div>
<div class="my-agenda-header d-flex" transloco="{{condition ? dontTake : (condition2 ? '22' : '23')}}"></div>
<self-closing transloco="24" />
</div>
3 changes: 3 additions & 0 deletions __tests__/ngTemplate/5.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ <h1>ddsds</h1>
<my-comp [aa]="t('39') + 'a'">
<comp a="{{t('40') + 'a'}}">{{t('41') + 'a'}}</comp>
</my-comp>
<ng-template>
<self-closing [value]="t('42')" />
</ng-template>
</ng-container>
2 changes: 2 additions & 0 deletions __tests__/pipe/6.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@

{{ '{{count}} items' | transloco:{ count: item.usageCount } }}
</my-comp>

<self-closing [value]="'79' | transloco" />
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@
},
"type": "module",
"exports": {
"types": "./public-api.d.ts",
"default": "./public-api.js"
".": {
"import": "./public-api.js",
"types": "./public-api.d.ts"
},
"./marker": {
"import": "./marker.js",
"types": "./marker.d.ts"
}
},
"bin": {
"transloco-keys-manager": "index.js"
Expand Down
7 changes: 7 additions & 0 deletions src/keys-builder/template/directive.extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { resolveAliasAndKey } from '../utils/resolvers.utils';

import { TemplateExtractorConfig } from './types';
import {
isBlockNode,
isBoundAttribute,
isConditionalExpression,
isElement,
Expand All @@ -21,6 +22,7 @@ import {
isTemplate,
isTextAttribute,
parseTemplate,
resolveBlockChildNodes,
} from './utils';

export function directiveExtractor(config: TemplateExtractorConfig) {
Expand All @@ -30,6 +32,11 @@ export function directiveExtractor(config: TemplateExtractorConfig) {

function traverse(nodes: TmplAstNode[], config: ExtractorConfig) {
for (const node of nodes) {
if (isBlockNode(node)) {
traverse(resolveBlockChildNodes(node), config);
continue;
}

if (!isSupportedNode(node, [isTemplate, isElement])) {
continue;
}
Expand Down
7 changes: 7 additions & 0 deletions src/keys-builder/template/pipe.extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
isPropertyRead,
isTemplate,
parseTemplate,
isBlockNode,
resolveBlockChildNodes,
} from './utils';

export function pipeExtractor(config: TemplateExtractorConfig) {
Expand All @@ -27,6 +29,11 @@ export function pipeExtractor(config: TemplateExtractorConfig) {

function traverse(nodes: TmplAstNode[], config: ExtractorConfig) {
for (const node of nodes) {
if (isBlockNode(node)) {
traverse(resolveBlockChildNodes(node), config);
continue;
}

let astTrees: AST[] = [];

if (isElement(node) || isTemplate(node)) {
Expand Down
7 changes: 7 additions & 0 deletions src/keys-builder/template/structural-directive.extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
isSupportedNode,
isTemplate,
parseTemplate,
isBlockNode,
resolveBlockChildNodes,
} from './utils';

interface ContainerMetaData {
Expand All @@ -49,6 +51,11 @@ export function traverse(
config: TemplateExtractorConfig,
) {
for (const node of nodes) {
if (isBlockNode(node)) {
traverse(resolveBlockChildNodes(node), containers, config);
continue;
}

let methodUsages: ContainerMetaData[] = [];

if (isBoundText(node)) {
Expand Down
93 changes: 93 additions & 0 deletions src/keys-builder/template/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ import {
TmplAstElement,
TmplAstTemplate,
TmplAstTextAttribute,
TmplAstNode,
TmplAstDeferredBlock,
TmplAstDeferredBlockError,
TmplAstDeferredBlockLoading,
TmplAstDeferredBlockPlaceholder,
TmplAstForLoopBlock,
TmplAstForLoopBlockEmpty,
TmplAstIfBlockBranch,
TmplAstSwitchBlockCase,
TmplAstIfBlock,
TmplAstSwitchBlock,
} from '@angular/compiler';

import { readFile } from '../../utils/file.utils';
Expand Down Expand Up @@ -98,3 +109,85 @@ export function isSupportedNode<Predicates extends any[]>(
): node is GuardedType<Predicates[number]> {
return predicates.some((predicate) => predicate(node));
}

type BlockNode =
| TmplAstDeferredBlockError
| TmplAstDeferredBlockLoading
| TmplAstDeferredBlockPlaceholder
| TmplAstForLoopBlockEmpty
| TmplAstIfBlockBranch
| TmplAstSwitchBlockCase
| TmplAstForLoopBlock
| TmplAstDeferredBlock
| TmplAstIfBlock
| TmplAstSwitchBlock;

export function isBlockWithChildren(
node: unknown,
): node is { children: TmplAstNode[] } {
return (
node instanceof TmplAstDeferredBlockError ||
node instanceof TmplAstDeferredBlockLoading ||
node instanceof TmplAstDeferredBlockPlaceholder ||
node instanceof TmplAstForLoopBlockEmpty ||
node instanceof TmplAstIfBlockBranch ||
node instanceof TmplAstSwitchBlockCase
);
}

export function isTmplAstForLoopBlock(
node: unknown,
): node is TmplAstForLoopBlock {
return node instanceof TmplAstForLoopBlock;
}

export function isTmplAstDeferredBlock(
node: unknown,
): node is TmplAstDeferredBlock {
return node instanceof TmplAstDeferredBlock;
}

export function isTmplAstIfBlock(node: unknown): node is TmplAstIfBlock {
return node instanceof TmplAstIfBlock;
}

export function isTmplAstSwitchBlock(
node: unknown,
): node is TmplAstSwitchBlock {
return node instanceof TmplAstSwitchBlock;
}

export function isBlockNode(node: TmplAstNode): node is BlockNode {
return (
isTmplAstIfBlock(node) ||
isTmplAstForLoopBlock(node) ||
isTmplAstDeferredBlock(node) ||
isTmplAstSwitchBlock(node) ||
isBlockWithChildren(node)
);
}

export function resolveBlockChildNodes(node: BlockNode): TmplAstNode[] {
if (isTmplAstIfBlock(node)) {
return node.branches;
}

if (isTmplAstForLoopBlock(node)) {
return node.empty ? [...node.children, node.empty] : node.children;
}

if (isTmplAstDeferredBlock(node)) {
return [
...node.children,
...([node.loading, node.error, node.placeholder].filter(
Boolean,
) as TmplAstNode[]),
];
}

if (isTmplAstSwitchBlock(node)) {
return node.cases;
}

return node.children;
}

0 comments on commit 0e3ab54

Please sign in to comment.