Skip to content

fix(@angular-devkit/build-angular): respect i18nDuplicateTranslation … #30306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions goldens/public-api/angular/build/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export function executeNgPackagrBuilder(options: NgPackagrBuilderOptions, contex
export type ExtractI18nBuilderOptions = {
buildTarget?: string;
format?: Format;
i18nDuplicateTranslation?: I18NDuplicateTranslation;
outFile?: string;
outputPath?: string;
progress?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export type ExecutionTransformer<T> = (input: T) => T | Promise<T>;
export type ExtractI18nBuilderOptions = {
buildTarget?: string;
format?: Format;
i18nDuplicateTranslation?: I18NDuplicateTranslation;
outFile?: string;
outputPath?: string;
progress?: boolean;
Expand Down
13 changes: 10 additions & 3 deletions packages/angular/build/src/builders/extract-i18n/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,23 @@ export async function execute(
return path.relative(from, to);
},
};
const duplicateTranslationBehavior = normalizedOptions.i18nOptions.duplicateTranslationBehavior;
const diagnostics = checkDuplicateMessages(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
checkFileSystem as any,
extractionResult.messages,
normalizedOptions.i18nOptions.i18nDuplicateTranslation || 'warning',
duplicateTranslationBehavior,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
extractionResult.basePath as any,
);
if (diagnostics.messages.length > 0) {
context.logger.warn(diagnostics.formatDiagnostics(''));
if (diagnostics.messages.length > 0 && duplicateTranslationBehavior !== 'ignore') {
if (duplicateTranslationBehavior === 'error') {
context.logger.error(`Extraction Failed: ${diagnostics.formatDiagnostics('')}`);

return { success: false };
} else {
context.logger.warn(diagnostics.formatDiagnostics(''));
}
}

// Serialize all extracted messages
Expand Down
10 changes: 8 additions & 2 deletions packages/angular/build/src/builders/extract-i18n/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { type DiagnosticHandlingStrategy } from '@angular/localize/tools';
import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect';
import { fail } from 'node:assert';
import path from 'node:path';
import { createI18nOptions } from '../../utils/i18n-options';
import { type I18nOptions, createI18nOptions } from '../../utils/i18n-options';
import { Schema as ExtractI18nOptions, Format } from './schema';

export type NormalizedExtractI18nOptions = Awaited<ReturnType<typeof normalizeOptions>>;
Expand All @@ -36,7 +37,12 @@ export async function normalizeOptions(
// Target specifier defaults to the current project's build target with no specified configuration
const buildTargetSpecifier = options.buildTarget ?? ':';
const buildTarget = targetFromTargetString(buildTargetSpecifier, projectName, 'build');
const i18nOptions = createI18nOptions(projectMetadata, /** inline */ false, context.logger);
const i18nOptions: I18nOptions & {
duplicateTranslationBehavior: DiagnosticHandlingStrategy;
} = {
...createI18nOptions(projectMetadata, /** inline */ false, context.logger),
duplicateTranslationBehavior: options.i18nDuplicateTranslation || 'warning',
};

// Normalize xliff format extensions
let format = options.format;
Expand Down
5 changes: 5 additions & 0 deletions packages/angular/build/src/builders/extract-i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
"outFile": {
"type": "string",
"description": "Name of the file to output."
},
"i18nDuplicateTranslation": {
"type": "string",
"description": "How to handle duplicate translations.",
"enum": ["error", "warning", "ignore"]
}
},
"additionalProperties": false
Expand Down
2 changes: 0 additions & 2 deletions packages/angular/build/src/utils/i18n-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { DiagnosticHandlingStrategy } from '@angular/localize/tools';
import path from 'node:path';
import type { TranslationLoader } from './load-translations';

Expand All @@ -29,7 +28,6 @@ export interface I18nOptions {
flatOutput?: boolean;
readonly shouldInline: boolean;
hasDefinedSourceLocale?: boolean;
i18nDuplicateTranslation?: DiagnosticHandlingStrategy;
}

function normalizeTranslationFileOption(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,23 @@ export async function execute(
return path.relative(from, to);
},
};
const duplicateTranslationBehavior = normalizedOptions.i18nOptions.duplicateTranslationBehavior;
const diagnostics = checkDuplicateMessages(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
checkFileSystem as any,
extractionResult.messages,
'warning',
duplicateTranslationBehavior,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
extractionResult.basePath as any,
);
if (diagnostics.messages.length > 0) {
context.logger.warn(diagnostics.formatDiagnostics(''));
if (diagnostics.messages.length > 0 && duplicateTranslationBehavior !== 'ignore') {
if (duplicateTranslationBehavior === 'error') {
context.logger.error(`Extraction Failed: ${diagnostics.formatDiagnostics('')}`);

return { success: false };
} else {
context.logger.warn(diagnostics.formatDiagnostics(''));
}
}

// Serialize all extracted messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { createI18nOptions } from '@angular/build/private';
import { type I18nOptions, createI18nOptions } from '@angular/build/private';
import { type DiagnosticHandlingStrategy } from '@angular/localize/tools';
import { BuilderContext, targetFromTargetString } from '@angular-devkit/architect';
import { fail } from 'node:assert';
import path from 'node:path';
Expand Down Expand Up @@ -36,7 +37,12 @@ export async function normalizeOptions(
// Target specifier defaults to the current project's build target with no specified configuration
const buildTargetSpecifier = options.buildTarget ?? ':';
const buildTarget = targetFromTargetString(buildTargetSpecifier, projectName, 'build');
const i18nOptions = createI18nOptions(projectMetadata, /** inline */ false, context.logger);
const i18nOptions: I18nOptions & {
duplicateTranslationBehavior: DiagnosticHandlingStrategy;
} = {
...createI18nOptions(projectMetadata, /** inline */ false, context.logger),
duplicateTranslationBehavior: options.i18nDuplicateTranslation || 'warning',
};

// Normalize xliff format extensions
let format = options.format;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
"outFile": {
"type": "string",
"description": "Name of the file to output."
},
"i18nDuplicateTranslation": {
"type": "string",
"description": "How to handle duplicate translations.",
"enum": ["error", "warning", "ignore"]
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,34 @@ describe('Extract i18n Target', () => {
expect(fullLog).toContain('Duplicate messages with id');
});

it('issues errors for duplicate message identifiers', async () => {
host.appendToFile(
'src/app/app.component.ts',
'const c = $localize`:@@message-2:message contents`; const d = $localize`:@@message-2:different message contents`;',
);

const logger = new logging.Logger('');
const logs: string[] = [];
logger.subscribe((e) => logs.push(e.message));

const run = await architect.scheduleTarget(
extractI18nTargetSpec,
{
i18nDuplicateTranslation: 'error',
},
{ logger },
);
await expectAsync(run.result).toBeResolvedTo(jasmine.objectContaining({ success: false }));

await run.stop();

expect(host.scopedSync().exists(extractionFile)).toBe(false);

const fullLog = logs.join();
expect(fullLog).toContain('Duplicate messages with id');
expect(fullLog).toContain('Extraction Failed');
});

it('ignores inline styles', async () => {
host.appendToFile('src/app/app.component.html', '<p i18n>i18n test</p>');
host.replaceInFile('src/app/app.component.ts', 'styleUrls', 'styles');
Expand Down