Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): set dir attribute when using l…
Browse files Browse the repository at this point in the history
…ocalization

We add the `dir` (direction) HTML attribute when using localization.

Closes #16047
  • Loading branch information
alan-agius4 committed Nov 23, 2021
1 parent 244ff98 commit 3c681b6
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 21 deletions.
1 change: 1 addition & 0 deletions packages/angular_devkit/build_angular/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ ts_library(
"//packages/angular_devkit/core/node",
"//packages/ngtools/webpack",
"@npm//@ampproject/remapping",
"@npm//@angular/common",
"@npm//@angular/compiler-cli",
"@npm//@angular/core",
"@npm//@angular/localize",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { createHash } from 'crypto';
import { loadEsmModule } from '../load-esm';
import { htmlRewritingStream } from './html-rewriting-stream';

export type LoadOutputFileFunctionType = (file: string) => Promise<string>;
Expand Down Expand Up @@ -50,9 +51,14 @@ export interface FileInfo {
* after processing several configurations in order to build different sets of
* bundles for differential serving.
*/
export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<string> {
export async function augmentIndexHtml(
params: AugmentIndexHtmlOptions,
): Promise<{ content: string; warnings: string[]; errors: string[] }> {
const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html } = params;

const warnings: string[] = [];
const errors: string[] = [];

let { crossOrigin = 'none' } = params;
if (sri && crossOrigin === 'none') {
crossOrigin = 'anonymous';
Expand Down Expand Up @@ -119,6 +125,7 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
linkTags.push(`<link ${attrs.join(' ')}>`);
}

const dir = lang ? await getLanguageDirection(lang, warnings) : undefined;
const { rewriter, transformedContent } = await htmlRewritingStream(html);
const baseTagExists = html.includes('<base');

Expand All @@ -130,6 +137,10 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
if (isString(lang)) {
updateAttribute(tag, 'lang', lang);
}

if (dir) {
updateAttribute(tag, 'dir', dir);
}
break;
case 'head':
// Base href should be added before any link, meta tags
Expand Down Expand Up @@ -174,12 +185,15 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise

const content = await transformedContent;

if (linkTags.length || scriptTags.length) {
// In case no body/head tags are not present (dotnet partial templates)
return linkTags.join('') + scriptTags.join('') + content;
}

return content;
return {
content:
linkTags.length || scriptTags.length
? // In case no body/head tags are not present (dotnet partial templates)
linkTags.join('') + scriptTags.join('') + content
: content,
warnings,
errors,
};
}

function generateSriAttributes(content: string): string {
Expand Down Expand Up @@ -207,3 +221,21 @@ function updateAttribute(
function isString(value: unknown): value is string {
return typeof value === 'string';
}

async function getLanguageDirection(lang: string, warnings: string[]): Promise<string | undefined> {
try {
const localeData = (
await loadEsmModule<typeof import('@angular/common/locales/en')>(
`@angular/common/locales/${lang}`,
)
).default;

const dir = localeData[localeData.length - 2];

return isString(dir) ? dir : undefined;
} catch {
warnings.push(
`Locale data for '${lang}' cannot be found. 'dir' attribute will not be set for this locale.`,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('augment-index-html', () => {
tags.stripIndents`${html}`.replace(/(>\s+)/g, '>');

it('can generate index.html', async () => {
const source = augmentIndexHtml({
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
files: [
{ file: 'styles.css', extension: '.css', name: 'styles' },
Expand All @@ -39,8 +39,7 @@ describe('augment-index-html', () => {
],
});

const html = await source;
expect(html).toEqual(oneLineHtml`
expect(content).toEqual(oneLineHtml`
<html>
<head><base href="/">
<link rel="stylesheet" href="styles.css">
Expand All @@ -55,14 +54,13 @@ describe('augment-index-html', () => {
});

it('should replace base href value', async () => {
const source = augmentIndexHtml({
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
html: '<html><head><base href="/"></head><body></body></html>',
baseHref: '/Apps/',
});

const html = await source;
expect(html).toEqual(oneLineHtml`
expect(content).toEqual(oneLineHtml`
<html>
<head><base href="/Apps/">
</head>
Expand All @@ -72,15 +70,51 @@ describe('augment-index-html', () => {
`);
});

it('should add lang attribute', async () => {
const source = augmentIndexHtml({
it('should add lang and dir LTR attribute for French (fr)', async () => {
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
lang: 'fr',
});

const html = await source;
expect(html).toEqual(oneLineHtml`
<html lang="fr">
expect(content).toEqual(oneLineHtml`
<html lang="fr" dir="ltr">
<head>
<base href="/">
</head>
<body>
</body>
</html>
`);
});

it('should add lang and dir RTL attribute for Pashto (ps)', async () => {
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
lang: 'ps',
});

expect(content).toEqual(oneLineHtml`
<html lang="ps" dir="rtl">
<head>
<base href="/">
</head>
<body>
</body>
</html>
`);
});

it(`should work when lang (locale) is not provided by '@angular/common'`, async () => {
const { content, warnings } = await augmentIndexHtml({
...indexGeneratorOptions,
lang: 'xx-XX',
});

expect(warnings).toEqual([
`Locale data for 'xx-XX' cannot be found. 'dir' attribute will not be set for this locale.`,
]);
expect(content).toEqual(oneLineHtml`
<html lang="xx-XX">
<head>
<base href="/">
</head>
Expand All @@ -91,7 +125,7 @@ describe('augment-index-html', () => {
});

it(`should add script and link tags even when body and head element doesn't exist`, async () => {
const source = augmentIndexHtml({
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
html: `<app-root></app-root>`,
files: [
Expand All @@ -103,8 +137,7 @@ describe('augment-index-html', () => {
],
});

const html = await source;
expect(html).toEqual(oneLineHtml`
expect(content).toEqual(oneLineHtml`
<link rel="stylesheet" href="styles.css">
<script src="runtime.js" type="module"></script>
<script src="polyfills.js" type="module"></script>
Expand Down

0 comments on commit 3c681b6

Please sign in to comment.