⚡️(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 <c.gromoff@gmail.com>
This commit is contained in:
@@ -25,7 +25,9 @@ and this project adheres to
|
|||||||
- ♻️(frontend) redirect to doc after duplicate #1175
|
- ♻️(frontend) redirect to doc after duplicate #1175
|
||||||
- 🔧(project) change env.d system by using local files #1200
|
- 🔧(project) change env.d system by using local files #1200
|
||||||
- ⚡️(frontend) improve tree stability #1207
|
- ⚡️(frontend) improve tree stability #1207
|
||||||
- ⚡️(frontend) improve accessibility #1232
|
- ⚡️(frontend) improve accessibility
|
||||||
|
- #1232
|
||||||
|
- #1248
|
||||||
- 🛂(frontend) block drag n drop when not desktop #1239
|
- 🛂(frontend) block drag n drop when not desktop #1239
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ test.describe.serial('Language', () => {
|
|||||||
test('checks language switching', async ({ page }) => {
|
test('checks language switching', async ({ page }) => {
|
||||||
const header = page.locator('header').first();
|
const header = page.locator('header').first();
|
||||||
|
|
||||||
|
await expect(page.locator('html')).toHaveAttribute('lang', 'en-us');
|
||||||
|
|
||||||
// initial language should be english
|
// initial language should be english
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', {
|
page.getByRole('button', {
|
||||||
@@ -36,6 +38,8 @@ test.describe.serial('Language', () => {
|
|||||||
// switch to french
|
// switch to french
|
||||||
await waitForLanguageSwitch(page, TestLanguage.French);
|
await waitForLanguageSwitch(page, TestLanguage.French);
|
||||||
|
|
||||||
|
await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
header.getByRole('button').getByText('Français'),
|
header.getByRole('button').getByText('Français'),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
@@ -47,6 +51,8 @@ test.describe.serial('Language', () => {
|
|||||||
await expect(header.getByRole('button').getByText('Deutsch')).toBeVisible();
|
await expect(header.getByRole('button').getByText('Deutsch')).toBeVisible();
|
||||||
|
|
||||||
await expect(page.getByLabel('Abmelden')).toBeVisible();
|
await expect(page.getByLabel('Abmelden')).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.locator('html')).toHaveAttribute('lang', 'de');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('checks that backend uses the same language as the frontend', async ({
|
test('checks that backend uses the same language as the frontend', async ({
|
||||||
|
|||||||
1
src/frontend/apps/impress/src/i18n/config.ts
Normal file
1
src/frontend/apps/impress/src/i18n/config.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const fallbackLng = 'en';
|
||||||
@@ -2,6 +2,7 @@ import i18next from 'i18next';
|
|||||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
|
import { fallbackLng } from './config';
|
||||||
import resources from './translations.json';
|
import resources from './translations.json';
|
||||||
|
|
||||||
// Add an initialization guard
|
// Add an initialization guard
|
||||||
@@ -16,7 +17,7 @@ if (!isInitialized && !i18next.isInitialized) {
|
|||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources,
|
resources,
|
||||||
fallbackLng: 'en',
|
fallbackLng,
|
||||||
debug: false,
|
debug: false,
|
||||||
detection: {
|
detection: {
|
||||||
order: ['cookie', 'navigator'],
|
order: ['cookie', 'navigator'],
|
||||||
@@ -35,6 +36,17 @@ if (!isInitialized && !i18next.isInitialized) {
|
|||||||
nsSeparator: false,
|
nsSeparator: false,
|
||||||
keySeparator: false,
|
keySeparator: false,
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
'lang',
|
||||||
|
i18next.language || fallbackLng,
|
||||||
|
);
|
||||||
|
i18next.on('languageChanged', (lang) => {
|
||||||
|
document.documentElement.setAttribute('lang', lang);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch((e) => console.error('i18n initialization failed:', e));
|
.catch((e) => console.error('i18n initialization failed:', e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,33 @@
|
|||||||
import { Head, Html, Main, NextScript } from 'next/document';
|
import Document, {
|
||||||
|
DocumentContext,
|
||||||
|
Head,
|
||||||
|
Html,
|
||||||
|
Main,
|
||||||
|
NextScript,
|
||||||
|
} from 'next/document';
|
||||||
|
|
||||||
export default function RootLayout() {
|
import { fallbackLng } from '../i18n/config';
|
||||||
return (
|
|
||||||
<Html>
|
class MyDocument extends Document<{ locale: string }> {
|
||||||
<Head />
|
static async getInitialProps(ctx: DocumentContext) {
|
||||||
<body suppressHydrationWarning={process.env.NODE_ENV === 'development'}>
|
const initialProps = await Document.getInitialProps(ctx);
|
||||||
<Main />
|
return {
|
||||||
<NextScript />
|
...initialProps,
|
||||||
</body>
|
locale: ctx.locale || fallbackLng,
|
||||||
</Html>
|
};
|
||||||
);
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Html lang={this.props.locale}>
|
||||||
|
<Head />
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default MyDocument;
|
||||||
|
|||||||
Reference in New Issue
Block a user