Skip to content
Merged
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
25 changes: 24 additions & 1 deletion packages/angular/build/src/builders/application/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { BuilderContext } from '@angular-devkit/architect';
import type { Metafile } from 'esbuild';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import {
ExecutionResult,
Expand All @@ -18,6 +19,7 @@ import { I18nInliner } from '../../tools/esbuild/i18n-inliner';
import { maxWorkers } from '../../utils/environment-options';
import { loadTranslations } from '../../utils/i18n-options';
import { createTranslationLoader } from '../../utils/load-translations';
import { createProjectResolver } from '../../utils/resolve-project';
import { executePostBundleSteps } from './execute-post-bundle';
import { NormalizedApplicationBuildOptions, getLocaleBaseHref } from './options';

Expand Down Expand Up @@ -48,6 +50,7 @@ export async function inlineI18n(
outputFiles: executionResult.outputFiles,
shouldOptimize: optimizationOptions.scripts,
persistentCachePath: cacheOptions.enabled ? cacheOptions.path : undefined,
localizeVersion: i18nOptions.localizeVersion,
},
maxWorkers,
);
Expand All @@ -72,10 +75,21 @@ export async function inlineI18n(

try {
for (const locale of i18nOptions.inlineLocales) {
const localeDescription = i18nOptions.locales[locale];
let translationIntegrity: string | undefined = '';
for (const file of localeDescription.files) {
if (!file.integrity) {
translationIntegrity = undefined;
break;
}
translationIntegrity += (translationIntegrity ? '|' : '') + file.integrity;
}
Comment thread
clydin marked this conversation as resolved.

// A locale specific set of files is returned from the inliner.
const localeInlineResult = await inliner.inlineForLocale(
locale,
i18nOptions.locales[locale].translation,
localeDescription.translation,
translationIntegrity,
);
const localeOutputFiles = localeInlineResult.outputFiles;
inlineResult.errors.push(...localeInlineResult.errors);
Expand Down Expand Up @@ -176,6 +190,15 @@ export async function loadActiveTranslations(
context: BuilderContext,
i18n: NormalizedApplicationBuildOptions['i18nOptions'],
) {
if (!i18n.localizeVersion) {
try {
const projectResolve = createProjectResolver(context.workspaceRoot);
const manifestPath = projectResolve('@angular/localize/package.json');
const manifest = JSON.parse(await readFile(manifestPath, 'utf-8')) as { version?: string };
i18n.localizeVersion = manifest.version;
Comment thread
clydin marked this conversation as resolved.
} catch {}
}

// Load locale data and translations (if present)
let loader;
for (const [locale, desc] of Object.entries(i18n.locales)) {
Expand Down
11 changes: 10 additions & 1 deletion packages/angular/build/src/tools/esbuild/i18n-inliner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface I18nInlinerOptions {
outputFiles: BuildOutputFile[];
shouldOptimize?: boolean;
persistentCachePath?: string;
localizeVersion?: string;
}

/**
Expand Down Expand Up @@ -131,11 +132,13 @@ export class I18nInliner {
* of the localize function keyword.
* @param locale The string representing the locale to inline.
* @param translation The translation messages to use when inlining.
* @param translationIntegrity An optional integrity value for the translation messages to use for caching.
* @returns A promise that resolves to an array of OutputFiles representing a translated result.
*/
async inlineForLocale(
locale: string,
translation: Record<string, unknown> | undefined,
translationIntegrity?: string,
): Promise<{ outputFiles: BuildOutputFile[]; errors: string[]; warnings: string[] }> {
await this.initCache();

Expand All @@ -161,7 +164,13 @@ export class I18nInliner {
// of bytes. Hashing the options directly would re-hash the full set of messages, which
// can be several megabytes, once for every file.
fileCacheKeyBase ??= calculateHash(
JSON.stringify({ locale, translation, missingTranslation, shouldOptimize }),
JSON.stringify({
locale,
translation: translationIntegrity ?? translation,
missingTranslation,
shouldOptimize,
localizeVersion: this.options.localizeVersion,
}),
);

// NOTE: If additional options are added, this may need to be updated.
Expand Down
33 changes: 33 additions & 0 deletions packages/angular/build/src/tools/esbuild/i18n-inliner_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,37 @@ describe('I18nInliner', () => {
'Malformed escape sequence in $localize template literal in file "main.js".',
]);
});

it('inlines the translations of a locale when translationIntegrity is provided', async () => {
const { outputFiles, errors, warnings } = await createInliner([
browserFile('main.js', GREETING_SOURCE),
]).inlineForLocale('fr', { greeting: translationFor('Bonjour') }, 'sha256-test-integrity');

expect(errors).toEqual([]);
expect(warnings).toEqual([]);
expect(findFile(outputFiles, 'main.js').text).toContain('"Bonjour"');
expect(findFile(outputFiles, 'main.js').text).not.toContain('$localize');
});

it('inlines the translations of a locale when localizeVersion is configured in options', async () => {
inliner = new I18nInliner(
{
missingTranslation: 'warning',
outputFiles: [browserFile('main.js', GREETING_SOURCE)],
localizeVersion: '20.2.0',
},
1,
);

const { outputFiles, errors, warnings } = await inliner.inlineForLocale(
'fr',
{ greeting: translationFor('Bonjour') },
'sha256-test-integrity',
);

expect(errors).toEqual([]);
expect(warnings).toEqual([]);
expect(findFile(outputFiles, 'main.js').text).toContain('"Bonjour"');
expect(findFile(outputFiles, 'main.js').text).not.toContain('$localize');
});
});
1 change: 1 addition & 0 deletions packages/angular/build/src/utils/i18n-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface I18nOptions {
flatOutput?: boolean;
readonly shouldInline: boolean;
hasDefinedSourceLocale?: boolean;
localizeVersion?: string;
}

function normalizeTranslationFileOption(
Expand Down