fix(localize): add triple slash type reference on @angular/localize on `ng add (#48502)

This commits add a triple slash type reference to the `main.ts` of the project when running `ng add @angular/localize`. This is purely needed for IDE purposes so that `$localize` is available globally. For the compilations `@angular/localize` types are adding the the respective TS configs files.

This commits also add better support for using `@angular/localize` in `/// <reference types="@angular/localize" />`. To support this we need to move the global definition outside of a barrel file and into the index.ts file. Without this change the `$localize` method will not available globally when using triple slash type references.

Closes #48434

PR Close #48502
This commit is contained in:
Alan Agius
2022-12-15 09:06:36 +00:00
committed by Alex Rickabaugh
parent acff032f4b
commit 881ecb0936
4 changed files with 136 additions and 123 deletions
+103
View File
@@ -11,3 +11,106 @@
// public_api_guard rules
export * from './localize';
// The global declaration must be in the index.d.ts as otherwise it will not be picked up when used
// with
// /// <reference types="@angular/localize" />
import {LocalizeFn} from './src/localize';
// `declare global` allows us to escape the current module and place types on the global namespace
declare global {
/**
* Tag a template literal string for localization.
*
* For example:
*
* ```ts
* $localize `some string to localize`
* ```
*
* **Providing meaning, description and id**
*
* You can optionally specify one or more of `meaning`, `description` and `id` for a localized
* string by pre-pending it with a colon delimited block of the form:
*
* ```ts
* $localize`:meaning|description@@id:source message text`;
*
* $localize`:meaning|:source message text`;
* $localize`:description:source message text`;
* $localize`:@@id:source message text`;
* ```
*
* This format is the same as that used for `i18n` markers in Angular templates. See the
* [Angular i18n guide](guide/i18n-common-prepare#mark-text-in-component-template).
*
* **Naming placeholders**
*
* If the template literal string contains expressions, then the expressions will be automatically
* associated with placeholder names for you.
*
* For example:
*
* ```ts
* $localize `Hi ${name}! There are ${items.length} items.`;
* ```
*
* will generate a message-source of `Hi {$PH}! There are {$PH_1} items`.
*
* The recommended practice is to name the placeholder associated with each expression though.
*
* Do this by providing the placeholder name wrapped in `:` characters directly after the
* expression. These placeholder names are stripped out of the rendered localized string.
*
* For example, to name the `items.length` expression placeholder `itemCount` you write:
*
* ```ts
* $localize `There are ${items.length}:itemCount: items`;
* ```
*
* **Escaping colon markers**
*
* If you need to use a `:` character directly at the start of a tagged string that has no
* metadata block, or directly after a substitution expression that has no name you must escape
* the `:` by preceding it with a backslash:
*
* For example:
*
* ```ts
* // message has a metadata block so no need to escape colon
* $localize `:some description::this message starts with a colon (:)`;
* // no metadata block so the colon must be escaped
* $localize `\:this message starts with a colon (:)`;
* ```
*
* ```ts
* // named substitution so no need to escape colon
* $localize `${label}:label:: ${}`
* // anonymous substitution so colon must be escaped
* $localize `${label}\: ${}`
* ```
*
* **Processing localized strings:**
*
* There are three scenarios:
*
* * **compile-time inlining**: the `$localize` tag is transformed at compile time by a
* transpiler, removing the tag and replacing the template literal string with a translated
* literal string from a collection of translations provided to the transpilation tool.
*
* * **run-time evaluation**: the `$localize` tag is a run-time function that replaces and
* reorders the parts (static strings and expressions) of the template literal string with strings
* from a collection of translations loaded at run-time.
*
* * **pass-through evaluation**: the `$localize` tag is a run-time function that simply evaluates
* the original template literal string without applying any translations to the parts. This
* version is used during development or where there is no need to translate the localized
* template literals.
*
* @param messageParts a collection of the static parts of the template string.
* @param expressions a collection of the values of each placeholder in the template string.
* @returns the translated string, with the `messageParts` and `expressions` interleaved together.
*/
const $localize: LocalizeFn;
}
-98
View File
@@ -7,107 +7,9 @@
*/
// This file contains the public API of the `@angular/localize` entry-point
import {LocalizeFn} from './src/localize';
export {clearTranslations, loadTranslations} from './src/translate';
export {MessageId, TargetMessage} from './src/utils';
// Exports that are not part of the public API
export * from './private';
// `declare global` allows us to escape the current module and place types on the global namespace
declare global {
/**
* Tag a template literal string for localization.
*
* For example:
*
* ```ts
* $localize `some string to localize`
* ```
*
* **Providing meaning, description and id**
*
* You can optionally specify one or more of `meaning`, `description` and `id` for a localized
* string by pre-pending it with a colon delimited block of the form:
*
* ```ts
* $localize`:meaning|description@@id:source message text`;
*
* $localize`:meaning|:source message text`;
* $localize`:description:source message text`;
* $localize`:@@id:source message text`;
* ```
*
* This format is the same as that used for `i18n` markers in Angular templates. See the
* [Angular i18n guide](guide/i18n-common-prepare#mark-text-in-component-template).
*
* **Naming placeholders**
*
* If the template literal string contains expressions, then the expressions will be automatically
* associated with placeholder names for you.
*
* For example:
*
* ```ts
* $localize `Hi ${name}! There are ${items.length} items.`;
* ```
*
* will generate a message-source of `Hi {$PH}! There are {$PH_1} items`.
*
* The recommended practice is to name the placeholder associated with each expression though.
*
* Do this by providing the placeholder name wrapped in `:` characters directly after the
* expression. These placeholder names are stripped out of the rendered localized string.
*
* For example, to name the `items.length` expression placeholder `itemCount` you write:
*
* ```ts
* $localize `There are ${items.length}:itemCount: items`;
* ```
*
* **Escaping colon markers**
*
* If you need to use a `:` character directly at the start of a tagged string that has no
* metadata block, or directly after a substitution expression that has no name you must escape
* the `:` by preceding it with a backslash:
*
* For example:
*
* ```ts
* // message has a metadata block so no need to escape colon
* $localize `:some description::this message starts with a colon (:)`;
* // no metadata block so the colon must be escaped
* $localize `\:this message starts with a colon (:)`;
* ```
*
* ```ts
* // named substitution so no need to escape colon
* $localize `${label}:label:: ${}`
* // anonymous substitution so colon must be escaped
* $localize `${label}\: ${}`
* ```
*
* **Processing localized strings:**
*
* There are three scenarios:
*
* * **compile-time inlining**: the `$localize` tag is transformed at compile time by a
* transpiler, removing the tag and replacing the template literal string with a translated
* literal string from a collection of translations provided to the transpilation tool.
*
* * **run-time evaluation**: the `$localize` tag is a run-time function that replaces and
* reorders the parts (static strings and expressions) of the template literal string with strings
* from a collection of translations loaded at run-time.
*
* * **pass-through evaluation**: the `$localize` tag is a run-time function that simply evaluates
* the original template literal string without applying any translations to the parts. This
* version is used during development or where there is no need to translate the localized
* template literals.
*
* @param messageParts a collection of the static parts of the template string.
* @param expressions a collection of the values of each placeholder in the template string.
* @returns the translated string, with the `messageParts` and `expressions` interleaved together.
*/
const $localize: LocalizeFn;
}
+16 -1
View File
@@ -18,6 +18,7 @@ import {Builders} from '@schematics/angular/utility/workspace-models';
import {Schema} from './schema';
const localizeType = `@angular/localize`;
const localizeTripleSlashType = `/// <reference types="@angular/localize" />`;
function addTypeScriptConfigTypes(projectName: string): Rule {
return async (host: Tree) => {
@@ -28,7 +29,7 @@ function addTypeScriptConfigTypes(projectName: string): Rule {
}
// We add the root workspace tsconfig for better IDE support.
const tsConfigFiles = new Set<string>(['tsconfig.json']);
const tsConfigFiles = new Set<string>();
for (const target of project.targets.values()) {
switch (target.builder) {
case Builders.Karma:
@@ -41,6 +42,13 @@ function addTypeScriptConfigTypes(projectName: string): Rule {
break;
}
if (target.builder === Builders.Browser) {
const value = target.options?.['main'];
if (typeof value === 'string') {
addTripleSlashType(host, value);
}
}
}
const typesJsonPath: JSONPath = ['compilerOptions', 'types'];
@@ -68,6 +76,13 @@ function addTypeScriptConfigTypes(projectName: string): Rule {
};
}
function addTripleSlashType(host: Tree, path: string): void {
const content = host.readText(path);
if (!content.includes(localizeTripleSlashType)) {
host.overwrite(path, localizeTripleSlashType + '\n\n' + content);
}
}
function moveToDependencies(host: Tree, context: SchematicContext): void {
if (!host.exists('package.json')) {
return;
@@ -16,7 +16,8 @@ interface TsConfig {
}
describe('ng-add schematic', () => {
const localizeType = '@angular/localize';
const localizeTripleSlashType = `/// <reference types="@angular/localize" />`;
const defaultOptions = {project: 'demo'};
const schematicRunner = new SchematicTestRunner(
'@angular/localize', runfiles.resolvePackageRelative('../collection.json'));
@@ -33,6 +34,11 @@ describe('ng-add schematic', () => {
},
}));
host.create('main.ts', `
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
`);
host.create('angular.json', JSON.stringify({
version: 1,
projects: {
@@ -42,6 +48,7 @@ describe('ng-add schematic', () => {
build: {
builder: '@angular-devkit/build-angular:browser',
options: {
main: './main.ts',
tsConfig: './tsconfig.app.json',
},
},
@@ -69,36 +76,22 @@ describe('ng-add schematic', () => {
}));
});
it(`should add '@angular/localize' in 'types' in the root level 'tsconfig.json'`, async () => {
host.create('tsconfig.json', JSON.stringify({
compilerOptions: {
types: ['node'],
},
}));
it(`should add '@angular/localize' type reference in 'main.ts'`, async () => {
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
const {compilerOptions} = host.readJson('tsconfig.json') as TsConfig;
const types = compilerOptions?.types;
expect(types).toContain(localizeType);
expect(types).toHaveSize(2);
expect(host.readText('main.ts')).toContain(localizeTripleSlashType);
});
it(`should not add '@angular/localize' in 'types' tsconfig when '@angular/localize/init' is present`,
it(`should not add '@angular/localize' type reference in 'main.ts' if already present`,
async () => {
host.create('tsconfig.json', JSON.stringify({
compilerOptions: {
types: ['node', '@angular/localize/init'],
},
}));
const mainContentInput = `
${localizeTripleSlashType}
import { enableProdMode } from '@angular/core';
`;
host.overwrite('main.ts', mainContentInput);
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
const {compilerOptions} = host.readJson('tsconfig.json') as TsConfig;
const types = compilerOptions?.types;
expect(types).not.toContain(localizeType);
expect(types).toHaveSize(2);
expect(host.readText('main.ts')).toBe(mainContentInput);
});
it(`should not add '@angular/localize' in 'types' tsconfigs referenced in non official builders`,
async () => {
const tsConfig = JSON.stringify({