Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a poc for new language handling #1731

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
158 changes: 139 additions & 19 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@
*/

import { Component, ErrorInfo, ReactNode } from 'react';
import { Route, Routes } from 'react-router-dom';
import { Outlet, Route, Routes } from 'react-router-dom';
import { SnackbarProvider } from '@ndla/ui';
import { AlertsProvider } from './components/AlertsContext';
import AuthenticationContext from './components/AuthenticationContext';
import { BaseNameProvider } from './components/BaseNameContext';
import AboutPage from './containers/AboutPage/AboutPage';
import AccessDenied from './containers/AccessDeniedPage/AccessDeniedPage';
import AllSubjectsPage from './containers/AllSubjectsPage/AllSubjectsPage';
Expand Down Expand Up @@ -53,6 +52,7 @@ import SearchPage from './containers/SearchPage/SearchPage';
import SharedFolderPage from './containers/SharedFolderPage/SharedFolderPage';
import SubjectRouting from './containers/SubjectPage/SubjectRouting';
import WelcomePage from './containers/WelcomePage/WelcomePage';
import { LanguagePath } from './LanguagePath';
import handleError from './util/handleError';

interface State {
Expand All @@ -66,8 +66,8 @@ const resourceRoutes = (
</>
);

class App extends Component<AppProps, State> {
constructor(props: AppProps) {
class App extends Component<{}, State> {
constructor(props: {}) {
super(props);
this.state = {
hasError: false,
Expand All @@ -87,18 +87,26 @@ class App extends Component<AppProps, State> {
return <ErrorPage />;
}

return <AppRoutes base={this.props.base} />;
return <AppRoutes />;
}
}

const AppRoutes = ({ base }: AppProps) => {
const AppRoutes = () => {
return (
<AlertsProvider>
<BaseNameProvider value={base}>
<AuthenticationContext>
<SnackbarProvider>
<Routes>
<Route path="/" element={<Layout />}>
<AuthenticationContext>
<SnackbarProvider>
<Routes>
<Route path="/" element={<Layout includeLanguageSwitcher />}>
<Route
path=":lang"
element={
<>
<LanguagePath />
<Outlet />
</>
}
>
<Route index element={<WelcomePage />} />
<Route path="subjects" element={<AllSubjectsPage />} />
<Route path="search" element={<SearchPage />} />
Expand Down Expand Up @@ -219,16 +227,128 @@ const AppRoutes = ({ base }: AppProps) => {
<Route path="*" element={<NotFound />} />
<Route path="p/:articleId" element={<PlainArticlePage />} />
</Route>
</Routes>
</SnackbarProvider>
</AuthenticationContext>
</BaseNameProvider>
<Route index element={<WelcomePage />} />
<Route path="subjects" element={<AllSubjectsPage />} />
<Route path="search" element={<SearchPage />} />
<Route path="utdanning/:programme" element={<ProgrammePage />}>
<Route path=":grade" element={null} />
</Route>
<Route path="podkast">
<Route index element={<PodcastSeriesListPage />} />
<Route path=":id" element={<PodcastSeriesPage />} />
</Route>
<Route path="article/:articleId" element={<PlainArticlePage />} />
<Route
path="learningpaths/:learningpathId"
element={<PlainLearningpathPage />}
>
<Route path="steps/:stepId" element={null} />
</Route>
<Route path="subject:subjectId/topic:topicId/resource:resourceId">
{resourceRoutes}
</Route>
<Route path="subject:subjectId/topic:topic1/topic:topicId/resource:resourceId">
{resourceRoutes}
</Route>
<Route path="subject:subjectId/topic:topic1/topic:topic2/topic:topicId/resource:resourceId">
{resourceRoutes}
</Route>
<Route path="subject:subjectId/topic:topic1/topic:topic2/topic:topic3/topic:topicId/resource:resourceId">
{resourceRoutes}
</Route>
<Route path="subject:subjectId/topic:topic1/topic:topic2/topic:topic3/topic:topic4/topic:topicId/resource:resourceId">
{resourceRoutes}
</Route>
<Route path="subject:subjectId" element={<SubjectRouting />}>
<Route path="topic:topicId" element={null} />
<Route path="topic:topic1" element={null}>
<Route path="topic:topicId" element={null} />
<Route path="topic:topic2" element={null}>
<Route path="topic:topicId" element={null} />
<Route path="topic:topic3" element={null}>
<Route path="topic:topicId" element={null} />
<Route path="topic:topic4" element={null}>
<Route path="topic:topicId" element={null} />
</Route>
</Route>
</Route>
</Route>
</Route>
<Route path="video/:videoId" element={<VideoPage />} />
<Route path="image/:imageId" element={<ImagePage />} />
<Route path="concept/:conceptId" element={<ConceptPage />} />
<Route path="audio/:audioId" element={<AudioPage />} />
<Route path="h5p/:h5pId" element={<H5pPage />} />
<Route
path="minndla"
element={<PrivateRoute element={<MyNdlaLayout />} />}
>
<Route index element={<MyNdlaPage />} />
<Route path="folders">
<Route index element={<FoldersPage />} />
<Route path="preview/:folderId">
<Route index element={<PreviewFoldersPage />} />
<Route
path=":subfolderId"
element={<PreviewFoldersPage />}
/>
<Route
path=":subfolderId/:resourceId"
element={<PreviewFoldersPage />}
/>
</Route>
<Route path=":folderId" element={<FoldersPage />} />
</Route>
<Route path="arena">
<Route index element={<ArenaPage />} />
<Route path="category/new" element={<NewCategoryPage />} />
<Route path="category/:categoryId">
<Route index element={<TopicPage />} />
<Route path="edit" element={<CategoryEditPage />} />
<Route path="topic/new" element={<NewTopicPage />} />
</Route>
<Route path="topic/:topicId" element={<PostsPage />} />
<Route
path="notifications"
element={<ArenaNotificationPage />}
/>
<Route path="user/:username" element={<ArenaUserPage />} />
</Route>
<Route path="admin">
<Route index element={<ArenaAdminPage />} />
<Route path="users" element={<ArenaUserListPage />} />
<Route path="flags">
<Route index element={<ArenaFlagPage />} />
<Route path=":postId" element={<ArenaSingleFlagPage />} />
</Route>
</Route>
<Route path="tags">
<Route index element={<TagsPage />} />
<Route path=":tag" element={<TagsPage />} />
</Route>
<Route path="subjects" element={<FavoriteSubjectsPage />} />
<Route path="profile" element={<MyProfilePage />} />
</Route>
<Route path="about/:slug" element={<AboutPage />} />

<Route path="folder/:folderId">
<Route index element={<SharedFolderPage />} />
<Route path=":subfolderId" element={<SharedFolderPage />} />
<Route
path=":subfolderId/:resourceId"
element={<SharedFolderPage />}
/>
</Route>
<Route path="404" element={<NotFound />} />
<Route path="403" element={<AccessDenied />} />
<Route path="*" element={<NotFound />} />
<Route path="p/:articleId" element={<PlainArticlePage />} />
</Route>
</Routes>
</SnackbarProvider>
</AuthenticationContext>
</AlertsProvider>
);
};

interface AppProps {
base?: string;
}

export default App;
40 changes: 40 additions & 0 deletions src/LanguagePath.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2024-present, NDLA.
*
* This source code is licensed under the GPLv3 license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { useApolloClient } from '@apollo/client';
import { useVersionHash } from './components/VersionHashContext';
import config from './config';
import { LocaleType } from './interfaces';
import { createApolloLinks } from './util/apiHelpers';

export const LanguagePath = () => {
const { i18n } = useTranslation();
const { lang } = useParams();
const client = useApolloClient();
const versionHash = useVersionHash();

useEffect(() => {
if (
(!lang && i18n.language !== config.defaultLocale) ||
(lang && i18n.language !== lang)
) {
i18n.changeLanguage(lang as LocaleType);
}
}, [i18n, lang]);

i18n.on('languageChanged', (lang) => {
client.resetStore();
client.setLink(createApolloLinks(lang, versionHash));
document.documentElement.lang = lang;
});

return null;
};
42 changes: 42 additions & 0 deletions src/__tests__/toLanguagePath-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2024-present, NDLA.
*
* This source code is licensed under the GPLv3 license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import { toLanguagePath } from '../toLanguagePath';

describe('toLanguagePath', () => {
it('should return path if language is nb', () => {
expect(toLanguagePath('/path', 'nb')).toEqual('/path');
});

it('should return path with language if path does not start with /', () => {
expect(toLanguagePath('path', 'nn')).toEqual('/nn/path');
});

it('should return path with language if path starts with /', () => {
expect(toLanguagePath('/path', 'nn')).toEqual('/nn/path');
});

it('should return path with language if path starts with / and language is nb', () => {
expect(toLanguagePath('/path', 'nb')).toEqual('/path');
});
it('should return path with language if path starts with /nn/ and language is nn', () => {
expect(toLanguagePath('/nn/path', 'nn')).toEqual('/nn/path');
});
it('should return path with language if path starts with /nn/ and language is en', () => {
expect(toLanguagePath('/nn/path', 'en')).toEqual('/en/path');
});
it('should return path without language if path starts with /nn/ and language is nb', () => {
expect(toLanguagePath('/nn/path', 'nb')).toEqual('/path');
});
it('languages without translations should redirect to en', () => {
expect(toLanguagePath('/path', 'uk')).toEqual('/en/path');
});
it('languages without translations not starting with / should redirect to en', () => {
expect(toLanguagePath('path', 'uk')).toEqual('/en/path');
});
});
Loading
Loading