Skip to content

Commit 3c681b6

Browse files
committedNov 23, 2021
feat(@angular-devkit/build-angular): set dir attribute when using localization
We add the `dir` (direction) HTML attribute when using localization. Closes #16047
1 parent 244ff98 commit 3c681b6

File tree

3 files changed

+87
-21
lines changed

3 files changed

+87
-21
lines changed
 

‎packages/angular_devkit/build_angular/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ ts_library(
101101
"//packages/angular_devkit/core/node",
102102
"//packages/ngtools/webpack",
103103
"@npm//@ampproject/remapping",
104+
"@npm//@angular/common",
104105
"@npm//@angular/compiler-cli",
105106
"@npm//@angular/core",
106107
"@npm//@angular/localize",

‎packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html.ts

+39-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { createHash } from 'crypto';
10+
import { loadEsmModule } from '../load-esm';
1011
import { htmlRewritingStream } from './html-rewriting-stream';
1112

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

59+
const warnings: string[] = [];
60+
const errors: string[] = [];
61+
5662
let { crossOrigin = 'none' } = params;
5763
if (sri && crossOrigin === 'none') {
5864
crossOrigin = 'anonymous';
@@ -119,6 +125,7 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
119125
linkTags.push(`<link ${attrs.join(' ')}>`);
120126
}
121127

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

@@ -130,6 +137,10 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
130137
if (isString(lang)) {
131138
updateAttribute(tag, 'lang', lang);
132139
}
140+
141+
if (dir) {
142+
updateAttribute(tag, 'dir', dir);
143+
}
133144
break;
134145
case 'head':
135146
// Base href should be added before any link, meta tags
@@ -174,12 +185,15 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
174185

175186
const content = await transformedContent;
176187

177-
if (linkTags.length || scriptTags.length) {
178-
// In case no body/head tags are not present (dotnet partial templates)
179-
return linkTags.join('') + scriptTags.join('') + content;
180-
}
181-
182-
return content;
188+
return {
189+
content:
190+
linkTags.length || scriptTags.length
191+
? // In case no body/head tags are not present (dotnet partial templates)
192+
linkTags.join('') + scriptTags.join('') + content
193+
: content,
194+
warnings,
195+
errors,
196+
};
183197
}
184198

185199
function generateSriAttributes(content: string): string {
@@ -207,3 +221,21 @@ function updateAttribute(
207221
function isString(value: unknown): value is string {
208222
return typeof value === 'string';
209223
}
224+
225+
async function getLanguageDirection(lang: string, warnings: string[]): Promise<string | undefined> {
226+
try {
227+
const localeData = (
228+
await loadEsmModule<typeof import('@angular/common/locales/en')>(
229+
`@angular/common/locales/${lang}`,
230+
)
231+
).default;
232+
233+
const dir = localeData[localeData.length - 2];
234+
235+
return isString(dir) ? dir : undefined;
236+
} catch {
237+
warnings.push(
238+
`Locale data for '${lang}' cannot be found. 'dir' attribute will not be set for this locale.`,
239+
);
240+
}
241+
}

‎packages/angular_devkit/build_angular/src/utils/index-file/augment-index-html_spec.ts

+47-14
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('augment-index-html', () => {
2828
tags.stripIndents`${html}`.replace(/(>\s+)/g, '>');
2929

3030
it('can generate index.html', async () => {
31-
const source = augmentIndexHtml({
31+
const { content } = await augmentIndexHtml({
3232
...indexGeneratorOptions,
3333
files: [
3434
{ file: 'styles.css', extension: '.css', name: 'styles' },
@@ -39,8 +39,7 @@ describe('augment-index-html', () => {
3939
],
4040
});
4141

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

5756
it('should replace base href value', async () => {
58-
const source = augmentIndexHtml({
57+
const { content } = await augmentIndexHtml({
5958
...indexGeneratorOptions,
6059
html: '<html><head><base href="/"></head><body></body></html>',
6160
baseHref: '/Apps/',
6261
});
6362

64-
const html = await source;
65-
expect(html).toEqual(oneLineHtml`
63+
expect(content).toEqual(oneLineHtml`
6664
<html>
6765
<head><base href="/Apps/">
6866
</head>
@@ -72,15 +70,51 @@ describe('augment-index-html', () => {
7270
`);
7371
});
7472

75-
it('should add lang attribute', async () => {
76-
const source = augmentIndexHtml({
73+
it('should add lang and dir LTR attribute for French (fr)', async () => {
74+
const { content } = await augmentIndexHtml({
7775
...indexGeneratorOptions,
7876
lang: 'fr',
7977
});
8078

81-
const html = await source;
82-
expect(html).toEqual(oneLineHtml`
83-
<html lang="fr">
79+
expect(content).toEqual(oneLineHtml`
80+
<html lang="fr" dir="ltr">
81+
<head>
82+
<base href="/">
83+
</head>
84+
<body>
85+
</body>
86+
</html>
87+
`);
88+
});
89+
90+
it('should add lang and dir RTL attribute for Pashto (ps)', async () => {
91+
const { content } = await augmentIndexHtml({
92+
...indexGeneratorOptions,
93+
lang: 'ps',
94+
});
95+
96+
expect(content).toEqual(oneLineHtml`
97+
<html lang="ps" dir="rtl">
98+
<head>
99+
<base href="/">
100+
</head>
101+
<body>
102+
</body>
103+
</html>
104+
`);
105+
});
106+
107+
it(`should work when lang (locale) is not provided by '@angular/common'`, async () => {
108+
const { content, warnings } = await augmentIndexHtml({
109+
...indexGeneratorOptions,
110+
lang: 'xx-XX',
111+
});
112+
113+
expect(warnings).toEqual([
114+
`Locale data for 'xx-XX' cannot be found. 'dir' attribute will not be set for this locale.`,
115+
]);
116+
expect(content).toEqual(oneLineHtml`
117+
<html lang="xx-XX">
84118
<head>
85119
<base href="/">
86120
</head>
@@ -91,7 +125,7 @@ describe('augment-index-html', () => {
91125
});
92126

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

106-
const html = await source;
107-
expect(html).toEqual(oneLineHtml`
140+
expect(content).toEqual(oneLineHtml`
108141
<link rel="stylesheet" href="styles.css">
109142
<script src="runtime.js" type="module"></script>
110143
<script src="polyfills.js" type="module"></script>

0 commit comments

Comments
 (0)
Please sign in to comment.