Skip to content

Commit

Permalink
New language handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonas-C committed Apr 23, 2024
1 parent 0c153a4 commit acc3f2d
Show file tree
Hide file tree
Showing 9 changed files with 36 additions and 165 deletions.
107 changes: 15 additions & 92 deletions src/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import "./style/index.css";
//@ts-ignore
import queryString from "query-string";
import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
import { ReactNode } from "react";
import { useDeviceSelectors } from "react-device-detect";
import { createRoot, hydrateRoot } from "react-dom/client";
import { HelmetProvider } from "react-helmet-async";
import { I18nextProvider, useTranslation } from "react-i18next";
import { I18nextProvider } from "react-i18next";
import { BrowserRouter, MemoryRouter } from "react-router-dom";
import { ApolloProvider, useApolloClient } from "@apollo/client";
import { ApolloProvider } from "@apollo/client";
import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import "@fontsource/source-code-pro/400-italic.css";
Expand All @@ -32,14 +32,13 @@ import "@fontsource/source-serif-pro/700.css";
import "@fontsource/source-serif-pro/index.css";
import { ErrorReporter } from "@ndla/error-reporter";
import { i18nInstance } from "@ndla/ui";
import { getCookie, setCookie } from "@ndla/util";
import App from "./App";
import { VersionHashProvider } from "./components/VersionHashContext";
import { EmotionCacheKey, STORED_LANGUAGE_COOKIE_KEY } from "./constants";
import { getLocaleInfoFromPath, initializeI18n, isValidLocale, supportedLanguages } from "./i18n";
import { EmotionCacheKey } from "./constants";
import { getLocaleInfoFromPath, initializeI18n, isValidLocale } from "./i18n";
import { NDLAWindow } from "./interfaces";
import { UserAgentProvider } from "./UserAgentContext";
import { createApolloClient, createApolloLinks } from "./util/apiHelpers";
import { createApolloClient } from "./util/apiHelpers";

declare global {
interface Window extends NDLAWindow {}
Expand All @@ -49,10 +48,10 @@ const {
DATA: { config, serverPath, serverQuery },
} = window;

const { basepath, abbreviation } = getLocaleInfoFromPath(serverPath ?? "");
const { basepath } = getLocaleInfoFromPath(serverPath ?? "");

const paths = window.location.pathname.split("/");
const basename = isValidLocale(paths[1] ?? "") ? `${paths[1]}` : undefined;
const lang = isValidLocale(paths[1] ?? "") ? `${paths[1]}` : undefined;

const { versionHash } = queryString.parse(window.location.search);

Expand All @@ -62,17 +61,7 @@ const locationFromServer = {
search: serverQueryString ? `?${serverQueryString}` : "",
};

const maybeStoredLanguage = getCookie(STORED_LANGUAGE_COOKIE_KEY, document.cookie);
// Set storedLanguage to a sane value if non-existent
if (maybeStoredLanguage === null || maybeStoredLanguage === undefined) {
setCookie({
cookieName: STORED_LANGUAGE_COOKIE_KEY,
cookieValue: abbreviation,
lax: true,
});
}
const storedLanguage = getCookie(STORED_LANGUAGE_COOKIE_KEY, document.cookie)!;
const i18n = initializeI18n(i18nInstance, storedLanguage);
const i18n = initializeI18n(i18nInstance, lang ?? config.defaultLocale);

window.errorReporter = ErrorReporter.getInstance({
logglyApiKey: config.logglyApiKey,
Expand All @@ -81,7 +70,7 @@ window.errorReporter = ErrorReporter.getInstance({
ignoreUrls: [],
});

const client = createApolloClient(storedLanguage, versionHash);
const client = createApolloClient(lang, versionHash);
const cache = createCache({ key: EmotionCacheKey });

// Use memory router if running under google translate
Expand All @@ -97,82 +86,16 @@ const RouterComponent = ({ children, base }: RCProps) =>
isGoogleUrl ? (
<MemoryRouter initialEntries={[locationFromServer]}>{children}</MemoryRouter>
) : (
<BrowserRouter key={base} basename={base}>
{children}
</BrowserRouter>
<BrowserRouter basename={base}>{children}</BrowserRouter>
);

const constructNewPath = (newLocale?: string) => {
const regex = new RegExp(`\\/(${supportedLanguages.join("|")})($|\\/)`, "");
const path = window.location.pathname.replace(regex, "");
const fullPath = path.startsWith("/") ? path : `/${path}`;
const localePrefix = newLocale ? `/${newLocale}` : "";
return `${localePrefix}${fullPath}${window.location.search}`;
};

const useReactPath = () => {
const [path, setPath] = useState("");
const listenToPopstate = () => {
const winPath = window.location.pathname;
setPath(winPath);
};
useEffect(() => {
window.addEventListener("popstate", listenToPopstate);
window.addEventListener("pushstate", listenToPopstate);
return () => {
window.removeEventListener("popstate", listenToPopstate);
window.removeEventListener("pushstate", listenToPopstate);
};
}, []);
return path;
};

const LanguageWrapper = ({ basename }: { basename?: string }) => {
const { i18n } = useTranslation();
const [base, setBase] = useState(basename ?? "");
const firstRender = useRef(true);
const client = useApolloClient();
const windowPath = useReactPath();
const UserAgentWrapper = ({ basename }: { basename?: string }) => {
const [selectors] = useDeviceSelectors(window.navigator.userAgent);

i18n.on("languageChanged", (lang) => {
setCookie({
cookieName: STORED_LANGUAGE_COOKIE_KEY,
cookieValue: lang,
lax: true,
});
client.resetStore();
client.setLink(createApolloLinks(lang, versionHash));
document.documentElement.lang = lang;
});

// handle path changes when the language is changed
useLayoutEffect(() => {
if (firstRender.current) {
firstRender.current = false;
} else {
window.history.replaceState("", "", constructNewPath(i18n.language));
setBase(i18n.language);
}
}, [i18n.language]);

// handle initial redirect if URL has wrong or missing locale prefix.
// only relevant when disableSSR=true
useLayoutEffect(() => {
const storedLanguage = getCookie(STORED_LANGUAGE_COOKIE_KEY, document.cookie)!;
if (storedLanguage === config.defaultLocale && !base) return;
if (isValidLocale(storedLanguage) && storedLanguage === base) {
setBase(storedLanguage);
}
if (window.location.pathname.includes("/login/success")) return;
setBase(storedLanguage);
window.history.replaceState("", "", constructNewPath(storedLanguage));
}, [base, windowPath]);

return (
<UserAgentProvider value={selectors}>
<RouterComponent key={base} base={base}>
<App base={base} />
<RouterComponent base={basename ?? ""}>
<App base={basename} />
</RouterComponent>
</UserAgentProvider>
);
Expand All @@ -194,7 +117,7 @@ renderOrHydrate(
<ApolloProvider client={client}>
<CacheProvider value={cache}>
<VersionHashProvider value={versionHash}>
<LanguageWrapper basename={basename} />
<UserAgentWrapper basename={lang} />
</VersionHashProvider>
</CacheProvider>
</ApolloProvider>
Expand Down
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const TOOLBOX_STUDENT_SUBJECT_ID = "urn:subject:1:54b1727c-2d91-4512-901c

export const SKIP_TO_CONTENT_ID = "SkipToContentId";
export const SUPPORTED_LANGUAGES = ["nb", "nn", "en", "se"];
export const STORED_LANGUAGE_COOKIE_KEY = "language";
export const STORED_RESOURCE_VIEW_SETTINGS = "STORED_RESOURCE_VIEW_SETTINGS";

export const PROGRAMME_PATH = "/utdanning";
Expand Down
17 changes: 3 additions & 14 deletions src/containers/ErrorPage/ErrorEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ import { BrowserRouter } from "react-router-dom";
import { ErrorReporter } from "@ndla/error-reporter";
import { MissingRouterContext } from "@ndla/safelink";
import { i18nInstance } from "@ndla/ui";
import { getCookie, setCookie } from "@ndla/util";
import ErrorPage from "./ErrorPage";
import Scripts from "../../components/Scripts/Scripts";
import { STORED_LANGUAGE_COOKIE_KEY } from "../../constants";
import { getLocaleInfoFromPath, initializeI18n } from "../../i18n";
import { getLocaleInfoFromPath, initializeI18n, isValidLocale } from "../../i18n";

const { config, serverPath } = window.DATA;

Expand All @@ -33,18 +31,9 @@ window.errorReporter = ErrorReporter.getInstance({

const { abbreviation } = getLocaleInfoFromPath(serverPath ?? "");

const maybeStoredLanguage = getCookie(STORED_LANGUAGE_COOKIE_KEY, document.cookie);
// Set storedLanguage to a sane value if non-existent
if (maybeStoredLanguage === null || maybeStoredLanguage === undefined) {
setCookie({
cookieName: STORED_LANGUAGE_COOKIE_KEY,
cookieValue: abbreviation,
lax: true,
});
}
const storedLanguage = getCookie(STORED_LANGUAGE_COOKIE_KEY, document.cookie)!;
const lang = isValidLocale(abbreviation) ? abbreviation : undefined;

const i18n = initializeI18n(i18nInstance, storedLanguage);
const i18n = initializeI18n(i18nInstance, lang ?? config.defaultLocale);

const renderOrHydrate = (container: HTMLElement, children: ReactNode) => {
if (config.disableSSR) {
Expand Down
9 changes: 7 additions & 2 deletions src/containers/Masthead/MastheadContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*
*/

import { useContext } from "react";
import { useCallback, useContext } from "react";

import { useTranslation } from "react-i18next";
import { gql } from "@apollo/client";
Expand All @@ -26,6 +26,7 @@ import { GQLMastHeadQuery, GQLMastHeadQueryVariables } from "../../graphqlTypes"
import { supportedLanguages } from "../../i18n";
import { useIsNdlaFilm, useUrnIds } from "../../routeHelpers";
import { useGraphQuery } from "../../util/runQueries";
import { constructNewPath } from "../../util/urlHelper";
import ErrorBoundary from "../ErrorPage/ErrorBoundary";

const FeideLoginLabel = styled.span`
Expand Down Expand Up @@ -91,6 +92,10 @@ const MastheadContainer = () => {
number: alert.number,
}));

const onChangeLanguage = useCallback((lang: string) => {
window.location.href = constructNewPath(window.location.pathname, lang);
}, []);

return (
<ErrorBoundary>
<Masthead
Expand All @@ -109,7 +114,7 @@ const MastheadContainer = () => {
<ButtonWrapper>
<MastheadSearch subject={data?.subject} />
<LanguageSelectWrapper>
<LanguageSelector inverted={ndlaFilm} locales={supportedLanguages} onSelect={i18n.changeLanguage} />
<LanguageSelector inverted={ndlaFilm} locales={supportedLanguages} onSelect={onChangeLanguage} />
</LanguageSelectWrapper>
{config.feideEnabled && (
<FeideLoginButton>
Expand Down
8 changes: 1 addition & 7 deletions src/lti/LtiProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import { useState } from "react";
import { Helmet } from "react-helmet-async";
import { useTranslation } from "react-i18next";
import { useApolloClient } from "@apollo/client";
import { setCookie } from "@ndla/util";

import { useLtiData } from "../components/LtiContext";
import { RESOURCE_TYPE_LEARNING_PATH, STORED_LANGUAGE_COOKIE_KEY } from "../constants";
import { RESOURCE_TYPE_LEARNING_PATH } from "../constants";
import ErrorBoundary from "../containers/ErrorPage/ErrorBoundary";
import ErrorPage from "../containers/ErrorPage/ErrorPage";
import SearchInnerPage from "../containers/SearchPage/SearchInnerPage";
Expand Down Expand Up @@ -60,11 +59,6 @@ const LtiProvider = ({ locale: propsLocale }: Props) => {
i18n.on("languageChanged", (lang) => {
client.resetStore();
client.setLink(createApolloLinks(lang));
setCookie({
cookieName: STORED_LANGUAGE_COOKIE_KEY,
cookieValue: lang,
lax: true,
});
document.documentElement.lang = lang;
});

Expand Down
10 changes: 3 additions & 7 deletions src/lti/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ import "@fontsource/source-code-pro/700.css";
import "@fontsource/source-serif-pro/index.css";
import "@fontsource/source-serif-pro/400-italic.css";
import "@fontsource/source-serif-pro/700.css";
import { getCookie } from "@ndla/util";
import "../style/index.css";
import { LtiIframePage } from "./LtiIframePage";
import LtiProvider from "./LtiProvider";
import { LtiContextProvider } from "../components/LtiContext";
import Scripts from "../components/Scripts/Scripts";
import { STORED_LANGUAGE_COOKIE_KEY } from "../constants";
import { initializeI18n, isValidLocale } from "../i18n";
import { initializeI18n } from "../i18n";
import { createApolloClient } from "../util/apiHelpers";

const {
Expand All @@ -48,10 +46,8 @@ window.errorReporter = ErrorReporter.getInstance({
ignoreUrls: [],
});

const storedLanguage = getCookie(STORED_LANGUAGE_COOKIE_KEY, document.cookie);
const language = isValidLocale(storedLanguage) ? storedLanguage : config.defaultLocale;
const client = createApolloClient(language);
const i18n = initializeI18n(i18nInstance, language);
const client = createApolloClient(config.defaultLocale);
const i18n = initializeI18n(i18nInstance, config.defaultLocale);

const root = createRoot(document.getElementById("root")!);
root.render(
Expand Down
22 changes: 2 additions & 20 deletions src/server/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { oembedArticleRoute } from "./routes/oembedArticleRoute";
import { podcastFeedRoute } from "./routes/podcastFeedRoute";
import { sendResponse } from "./serverHelpers";
import config from "../config";
import { FILM_PAGE_PATH, STORED_LANGUAGE_COOKIE_KEY, UKR_PAGE_PATH } from "../constants";
import { FILM_PAGE_PATH, UKR_PAGE_PATH } from "../constants";
import { getLocaleInfoFromPath } from "../i18n";
import { routes } from "../routeHelpers";
import { privateRoutes } from "../routes";
Expand Down Expand Up @@ -50,7 +50,6 @@ router.get("/film", (_, res) => {
});

router.get("/ukr", (_req, res) => {
res.cookie(STORED_LANGUAGE_COOKIE_KEY, "en");
res.redirect(`/en${UKR_PAGE_PATH}`);
});

Expand All @@ -60,23 +59,12 @@ router.get("/oembed", async (req, res) => {
sendResponse(res, data, status);
});

const getLang = (paramLang?: string, cookieLang?: string | null): string | undefined => {
if (paramLang) {
return paramLang;
}
if (!paramLang && cookieLang && cookieLang !== config.defaultLocale) {
return cookieLang;
}
return undefined;
};

router.get("/:lang?/login", async (req, res) => {
const feideCookie = getCookie("feide_auth", req.headers.cookie ?? "") ?? "";
const feideToken = feideCookie ? JSON.parse(feideCookie) : undefined;
const state = typeof req.query.state === "string" ? req.query.state : "";
res.setHeader("Cache-Control", "private");
const lang = getLang(req.params.lang, getCookie(STORED_LANGUAGE_COOKIE_KEY, req.headers.cookie ?? ""));
const redirect = constructNewPath(state, lang);
const redirect = constructNewPath(state, req.params.lang);

if (feideToken && isAccessTokenValid(feideToken)) {
return res.redirect(state);
Expand Down Expand Up @@ -105,12 +93,6 @@ router.get("/login/success", async (req, res) => {
encode: String,
domain: `.${config.feideDomain}`,
});
const languageCookie = getCookie(STORED_LANGUAGE_COOKIE_KEY, req.headers.cookie ?? "");
//workaround to ensure language cookie is set before redirecting to state path
if (!languageCookie) {
const { basename } = getLocaleInfoFromPath(state);
res.cookie(STORED_LANGUAGE_COOKIE_KEY, basename.length ? basename : config.defaultLocale);
}
return res.redirect(state);
});

Expand Down
Loading

0 comments on commit acc3f2d

Please sign in to comment.