diff --git a/__tests__/buildTranslationFiles.spec.ts b/__tests__/buildTranslationFiles.spec.ts
index 18876f4..cdf3897 100644
--- a/__tests__/buildTranslationFiles.spec.ts
+++ b/__tests__/buildTranslationFiles.spec.ts
@@ -127,9 +127,11 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
describe('Template Extraction', () => {
describe('Pipe', () => {
const type: TranslationCategory = 'pipe';
- const config = gConfig(type);
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(type));
+ });
it('should work with pipe', () => {
const expected = {
@@ -150,16 +152,17 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
expected[nonNumericKey] = defaultValue;
});
- createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
});
describe('Directive', () => {
const type: TranslationCategory = 'directive';
- const config = gConfig(type);
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(type));
+ });
it('should work with directive', () => {
const expected = generateKeys({ end: 24 });
@@ -168,16 +171,17 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
expected[nonNumericKey] = defaultValue;
},
);
- createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
});
describe('ngContainer', () => {
const type: TranslationCategory = 'ngContainer';
- const config = gConfig(type);
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(type));
+ });
it('should work with ngContainer', () => {
let expected = generateKeys({ end: 46 });
@@ -186,7 +190,6 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
expected['another(test)'] =
expected['last "one"'] =
defaultValue;
- createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
@@ -199,7 +202,6 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
'5': defaultValue,
};
- createTranslations(config);
assertTranslation({
type,
expected,
@@ -211,13 +213,14 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
describe('ngTemplate', () => {
const type: TranslationCategory = 'ngTemplate';
- const config = gConfig(type);
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(type));
+ });
it('should work with ngTemplate', () => {
let expected = generateKeys({ end: 42 });
- createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
@@ -230,7 +233,6 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
'5': defaultValue,
};
- createTranslations(config);
assertTranslation({
type,
expected,
@@ -242,22 +244,25 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
describe('Control flow', () => {
const type: TranslationCategory = 'control-flow';
- const config = gConfig(type);
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(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);
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(type));
+ });
it('should work with read', () => {
const expected = {
@@ -287,7 +292,6 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
},
};
- createTranslations(config);
assertTranslation({ type, expected: expected.global, fileFormat });
assertTranslation({
type,
@@ -302,9 +306,11 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
describe('Typescript Extraction', () => {
describe('service', () => {
const type: TranslationCategory = 'service';
- const config = gConfig(type);
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(type));
+ });
it('should work with service', () => {
const expected = {
@@ -314,7 +320,6 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
'inject.test': defaultValue,
};
- createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
@@ -334,7 +339,6 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
},
};
- createTranslations(config);
assertTranslation({
type,
expected: expected.todos,
@@ -357,8 +361,6 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
it('should work when passing an array of keys', () => {
const expected = generateKeys({ start: 26, end: 33 });
-
- createTranslations(config);
assertPartialTranslation({ type, expected, fileFormat });
});
});
@@ -366,26 +368,42 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
describe('marker', () => {
const type: TranslationCategory = 'marker';
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(type));
+ });
it('should work with marker', () => {
- const config = gConfig(type);
-
let expected = {};
expected['username4'] = defaultValue;
expected['password4'] = defaultValue;
expected['username'] = defaultValue;
expected['password'] = defaultValue;
- createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
+
+ it('should work with scopes', () => {
+ const expected = {
+ username: defaultValue,
+ password: defaultValue,
+ };
+
+ assertTranslation({
+ type,
+ expected: expected,
+ path: 'nested/scope/',
+ fileFormat,
+ });
+ });
});
describe('inline template', () => {
const type: TranslationCategory = 'inline-template';
- const config = gConfig(type);
- beforeEach(() => removeI18nFolder(type));
+ beforeEach(() => {
+ removeI18nFolder(type);
+ createTranslations(gConfig(type));
+ });
it('should work with inline templates', () => {
const expected = generateKeys({ end: 23 });
@@ -394,7 +412,6 @@ describe.each(formats)('buildTranslationFiles in %s', (fileFormat) => {
expected[nonNumericKey] = defaultValue;
},
);
- createTranslations(config);
assertTranslation({ type, expected, fileFormat });
});
});
diff --git a/__tests__/marker/nested-scope.ts b/__tests__/marker/nested-scope.ts
new file mode 100644
index 0000000..47c785b
--- /dev/null
+++ b/__tests__/marker/nested-scope.ts
@@ -0,0 +1,4 @@
+const f = {
+ provide: TRANSLOCO_SCOPE,
+ useValue: 'nested/scope'
+};
diff --git a/__tests__/marker/with-scope.ts b/__tests__/marker/with-scope.ts
new file mode 100644
index 0000000..157f84a
--- /dev/null
+++ b/__tests__/marker/with-scope.ts
@@ -0,0 +1,26 @@
+import { marker } from '@jsverse/transloco-keys-manager/marker';
+
+@Component({
+ selector: 'bla-bla',
+ template: `
+
+
+ |
+ {{ column | transloco }}
+ |
+
+
+ |
+ {{ row[column] }}
+ |
+
+
+ `
+})
+export class Basic {
+ data = [
+ { username: 'alex', password: '12345678' },
+ { username: 'bob', password: 'password' }
+ ];
+ displayedColumns = [marker('username', 'nested/scope'), marker('password', 'nested/scope')];
+}
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..b9ef426 100644
--- a/src/keys-builder/typescript/build-keys-from-ast-nodes.ts
+++ b/src/keys-builder/typescript/build-keys-from-ast-nodes.ts
@@ -1,46 +1,98 @@
-import { Node, StringLiteral, NoSubstitutionTemplateLiteral } from 'typescript';
+import { Node, StringLiteral, NoSubstitutionTemplateLiteral, CallExpression } from 'typescript';
import ts from 'typescript';
import { TSExtractorResult } from './types';
-export function buildKeysFromASTNodes(
- nodes: Node[],
- allowedMethods = ['translate', 'selectTranslate'],
-): TSExtractorResult {
+/**
+ * It can be one of the following:
+ *
+ * translate('2', {}, 'some/nested');
+ * translate('3', {}, 'some/nested/en');
+ * translate(['2', '3'], {}, 'some/nested/en');
+ * translate('globalKey');
+ *
+ *
+ * selectTranslate('2', {}, 'some/nested');
+ * selectTranslate('3', {}, 'some/nested/en');
+ * selectTranslate(['2', '3'], {}, 'some/nested/en');
+ * selectTranslate('globalKey');
+ */
+export function buildTranslateKeysFromASTNodes(nodes: Node[]): TSExtractorResult {
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 methodName = getMethodName(node.parent);
+ if (!['translate', 'selectTranslate'].includes(methodName)) {
+ continue;
+ }
+
+ const [keyNode, _, langNode] = node.parent.arguments;
+ let scope = 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, scope });
}
}
return result;
}
+/**
+ * It can be one of the following:
+ *
+ * marker('globalKey');
+ * marker('globalKey', 'some/nested/en');
+ *
+ * alias('globalKey');
+ * alias('globalKey', 'some/nested/en');
+ */
+export function buildMarkerKeysFromASTNodes(nodes: Node[], markerName: string): TSExtractorResult {
+ const result: TSExtractorResult = [];
+
+ for (let node of nodes) {
+ if (!ts.isCallExpression(node.parent)) {
+ continue;
+ }
+
+ if (markerName !== getMethodName(node.parent)) {
+ continue;
+ }
+
+ const [keyNode, scopeNode] = node.parent.arguments;
+ result.push({
+ key: isStringNode(keyNode) ? keyNode.text : '',
+ scope: isStringNode(scopeNode) ? scopeNode.text : ''
+ });
+ }
+
+ return result;
+}
+
+function getMethodName(node: CallExpression): string {
+ const method = node.expression;
+
+ let methodName = '';
+ if (ts.isIdentifier(method)) {
+ methodName = method.text;
+ } else if (ts.isPropertyAccessExpression(method)) {
+ methodName = method.name.text;
+ }
+
+ return methodName;
+}
+
+
function isStringNode(
node: Node,
): node is StringLiteral | NoSubstitutionTemplateLiteral {
diff --git a/src/keys-builder/typescript/index.ts b/src/keys-builder/typescript/index.ts
index 14e5723..3b522d9 100644
--- a/src/keys-builder/typescript/index.ts
+++ b/src/keys-builder/typescript/index.ts
@@ -48,10 +48,10 @@ function TSExtractor(config: ExtractorConfig): ScopeMap {
extractors
.map((ex) => ex(ast))
.flat()
- .forEach(({ key, lang }) => {
- const [keyWithoutScope, scopeAlias] = resolveAliasAndKeyFromService(
+ .forEach(({ key, scope }) => {
+ const [keyWithoutScope, scopeAlias] = resolveKeyAndScopeAliasFunctions(
key,
- lang,
+ scope,
scopes,
);
addKey({
@@ -73,16 +73,7 @@ function TSExtractor(config: ExtractorConfig): ScopeMap {
return scopeToKeys;
}
-/**
- *
- * It can be one of the following:
- *
- * translate('2', {}, 'some/nested');
- * translate('3', {}, 'some/nested/en');
- * translate('globalKey');
- *
- */
-function resolveAliasAndKeyFromService(
+function resolveKeyAndScopeAliasFunctions(
key: string,
scopePath: string,
scopes: Scopes,
diff --git a/src/keys-builder/typescript/marker.extractor.ts b/src/keys-builder/typescript/marker.extractor.ts
index a0c87f6..44656f1 100644
--- a/src/keys-builder/typescript/marker.extractor.ts
+++ b/src/keys-builder/typescript/marker.extractor.ts
@@ -1,7 +1,7 @@
import { SourceFile, Node } from 'typescript';
import { tsquery } from '@phenomnomnominal/tsquery';
-import { buildKeysFromASTNodes } from './build-keys-from-ast-nodes';
+import { buildMarkerKeysFromASTNodes, buildTranslateKeysFromASTNodes } from './build-keys-from-ast-nodes';
import { TSExtractorResult } from './types';
export function markerExtractor(ast: SourceFile): TSExtractorResult {
@@ -16,7 +16,7 @@ export function markerExtractor(ast: SourceFile): TSExtractorResult {
const markerName = getMarkerName(importNode);
const fns = tsquery(ast, `CallExpression Identifier[text=${markerName}]`);
- return buildKeysFromASTNodes(fns, [markerName]);
+ return buildMarkerKeysFromASTNodes(fns, markerName);
}
function getMarkerName(importNode: Node) {
diff --git a/src/keys-builder/typescript/pure-function.extractor.ts b/src/keys-builder/typescript/pure-function.extractor.ts
index 3f38613..1d0782d 100644
--- a/src/keys-builder/typescript/pure-function.extractor.ts
+++ b/src/keys-builder/typescript/pure-function.extractor.ts
@@ -1,11 +1,11 @@
import { SourceFile } from 'typescript';
import { tsquery } from '@phenomnomnominal/tsquery';
-import { buildKeysFromASTNodes } from './build-keys-from-ast-nodes';
+import { buildTranslateKeysFromASTNodes } from './build-keys-from-ast-nodes';
import { TSExtractorResult } from './types';
export function pureFunctionExtractor(ast: SourceFile): TSExtractorResult {
const fns = tsquery(ast, `CallExpression Identifier[text=translate]`);
- return buildKeysFromASTNodes(fns);
+ return buildTranslateKeysFromASTNodes(fns);
}
diff --git a/src/keys-builder/typescript/service.extractor.ts b/src/keys-builder/typescript/service.extractor.ts
index efbd46d..0e798d2 100644
--- a/src/keys-builder/typescript/service.extractor.ts
+++ b/src/keys-builder/typescript/service.extractor.ts
@@ -2,7 +2,7 @@ import { tsquery } from '@phenomnomnominal/tsquery';
import { SourceFile } from 'typescript';
import ts from 'typescript';
-import { buildKeysFromASTNodes } from './build-keys-from-ast-nodes';
+import { buildTranslateKeysFromASTNodes } from './build-keys-from-ast-nodes';
import { TSExtractorResult } from './types';
export function serviceExtractor(ast: SourceFile): TSExtractorResult {
@@ -26,7 +26,7 @@ export function serviceExtractor(ast: SourceFile): TSExtractorResult {
`PropertyAccessExpression:has([name=${propName}])`,
);
- result = result.concat(buildKeysFromASTNodes(methodNodes));
+ result = result.concat(buildTranslateKeysFromASTNodes(methodNodes));
}
}
diff --git a/src/keys-builder/typescript/types.ts b/src/keys-builder/typescript/types.ts
index 26bac15..37c6d61 100644
--- a/src/keys-builder/typescript/types.ts
+++ b/src/keys-builder/typescript/types.ts
@@ -1 +1 @@
-export type TSExtractorResult = { key: string; lang: string }[];
+export type TSExtractorResult = { key: string; scope: string }[];
diff --git a/src/marker.ts b/src/marker.ts
index 4a8ad4d..6481d97 100644
--- a/src/marker.ts
+++ b/src/marker.ts
@@ -1,3 +1,3 @@
-export function marker(key: T): T {
+export function marker(key: T, scope?: string): T {
return key;
}