Skip to content

Commit ec84f31

Browse files
committed
⚡️(frontend) set html lang attribute dynamically based on current loc
ensures proper language tag is set for accessibility and SEO compliance Signed-off-by: Cyril <[email protected]>
1 parent 7813219 commit ec84f31

File tree

5 files changed

+55
-14
lines changed

5 files changed

+55
-14
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ and this project adheres to
2525
- ♻️(frontend) redirect to doc after duplicate #1175
2626
- 🔧(project) change env.d system by using local files #1200
2727
- ⚡️(frontend) improve tree stability #1207
28-
- ⚡️(frontend) improve accessibility #1232
28+
- ⚡️(frontend) improve accessibility
29+
- #1232
30+
- #1248
2931
- 🛂(frontend) block drag n drop when not desktop #1239
3032

3133
### Fixed

src/frontend/apps/e2e/__tests__/app-impress/language.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ test.describe.serial('Language', () => {
2626
test('checks language switching', async ({ page }) => {
2727
const header = page.locator('header').first();
2828

29+
await expect(page.locator('html')).toHaveAttribute('lang', 'en-us');
30+
2931
// initial language should be english
3032
await expect(
3133
page.getByRole('button', {
@@ -36,6 +38,8 @@ test.describe.serial('Language', () => {
3638
// switch to french
3739
await waitForLanguageSwitch(page, TestLanguage.French);
3840

41+
await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
42+
3943
await expect(
4044
header.getByRole('button').getByText('Français'),
4145
).toBeVisible();
@@ -47,6 +51,8 @@ test.describe.serial('Language', () => {
4751
await expect(header.getByRole('button').getByText('Deutsch')).toBeVisible();
4852

4953
await expect(page.getByLabel('Abmelden')).toBeVisible();
54+
55+
await expect(page.locator('html')).toHaveAttribute('lang', 'de');
5056
});
5157

5258
test('checks that backend uses the same language as the frontend', async ({
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const fallbackLng = 'en';

src/frontend/apps/impress/src/i18n/initI18n.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import i18next from 'i18next';
22
import LanguageDetector from 'i18next-browser-languagedetector';
33
import { initReactI18next } from 'react-i18next';
44

5+
import { fallbackLng } from './config';
56
import resources from './translations.json';
67

78
// Add an initialization guard
@@ -16,7 +17,7 @@ if (!isInitialized && !i18next.isInitialized) {
1617
.use(initReactI18next)
1718
.init({
1819
resources,
19-
fallbackLng: 'en',
20+
fallbackLng,
2021
debug: false,
2122
detection: {
2223
order: ['cookie', 'navigator'],
@@ -35,6 +36,17 @@ if (!isInitialized && !i18next.isInitialized) {
3536
nsSeparator: false,
3637
keySeparator: false,
3738
})
39+
.then(() => {
40+
if (typeof document !== 'undefined') {
41+
document.documentElement.setAttribute(
42+
'lang',
43+
i18next.language || fallbackLng,
44+
);
45+
i18next.on('languageChanged', (lang) => {
46+
document.documentElement.setAttribute('lang', lang);
47+
});
48+
}
49+
})
3850
.catch((e) => console.error('i18n initialization failed:', e));
3951
}
4052

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
1-
import { Head, Html, Main, NextScript } from 'next/document';
2-
3-
export default function RootLayout() {
4-
return (
5-
<Html>
6-
<Head />
7-
<body suppressHydrationWarning={process.env.NODE_ENV === 'development'}>
8-
<Main />
9-
<NextScript />
10-
</body>
11-
</Html>
12-
);
1+
import Document, {
2+
DocumentContext,
3+
Head,
4+
Html,
5+
Main,
6+
NextScript,
7+
} from 'next/document';
8+
9+
import { fallbackLng } from '../i18n/config';
10+
11+
class MyDocument extends Document<{ locale: string }> {
12+
static async getInitialProps(ctx: DocumentContext) {
13+
const initialProps = await Document.getInitialProps(ctx);
14+
return {
15+
...initialProps,
16+
locale: ctx.locale || fallbackLng,
17+
};
18+
}
19+
20+
render() {
21+
return (
22+
<Html lang={this.props.locale}>
23+
<Head />
24+
<body>
25+
<Main />
26+
<NextScript />
27+
</body>
28+
</Html>
29+
);
30+
}
1331
}
32+
33+
export default MyDocument;

0 commit comments

Comments
 (0)