- Added translations

- Added pluralization example
  - Added formatter example (with Luxon)
  - Used HTTP loader
- Added a suspense fallback page for app loading
- Added cypress eslint rules
This commit is contained in:
2022-07-08 15:05:05 +02:00
parent 7d9c7037bc
commit 0003df5fab
19 changed files with 527 additions and 21 deletions

View File

@@ -0,0 +1,27 @@
import { DateTime } from "luxon";
/**
* Luxon based date formatter for a specific language
* @param value date to format
* @param language language of the current user
* @returns formatted date
*/
export const FormattedDate = (value: Date, language: string | undefined) => {
if (!language) {
language = "en";
}
let format;
switch (language) {
case "nl":
format = "dd/MM/yyyy";
break;
case "en":
format = "yyyy-MM-dd";
break;
default:
format = "yyyy-MM-dd";
}
return DateTime.fromJSDate(value).setLocale(language).toFormat(format);
};

View File

@@ -0,0 +1,32 @@
import i18n, { InitOptions } from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import { initReactI18next } from "react-i18next";
import { FormattedDate } from "./formatters/formattedDate";
/**
* Initial i18n settings
* https://www.i18next.com/overview/configuration-options
*/
export const i18nSettings: InitOptions = {
debug: false,
fallbackLng: "en",
lng: "en",
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
// i18next-http-back-end options, for all options read: https://github.com/i18next/i18next-http-backend
backend: { loadPath: "/i18n/{{lng}}.json" },
};
i18n
// detect user language
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init(i18nSettings);
// format date based on language
i18n.services.formatter?.add("formattedDate", FormattedDate);
export default i18n;

View File

@@ -0,0 +1,7 @@
import { FunctionComponent } from "react";
type Props = {};
export const AppLoader: FunctionComponent<Props> = () => {
return <h1>Loading app...</h1>;
};

View File

@@ -1,13 +1,16 @@
import { render, screen } from "@testing-library/react";
import { WithTestTranslations } from "../../app/tests/mocks/i18n/WithTestTranslations";
import { WithRouter } from "../wrappers/WithRouter";
import { Navbar } from "./Navbar";
describe("Navbar container", () => {
it("renders a navigation section identified by the nav test-id", () => {
it.only("renders a navigation section identified by the nav test-id", () => {
render(
<WithRouter>
<Navbar />
</WithRouter>,
<WithTestTranslations>
<WithRouter>
<Navbar />
</WithRouter>
</WithTestTranslations>,
);
expect(screen.getAllByTestId("nav")?.length).toBeGreaterThan(0);

View File

@@ -1,24 +1,36 @@
import { DateTime } from "luxon";
import { FunctionComponent } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { Config } from "../config";
import "./Navbar.css";
type Props = {};
export const Navbar: FunctionComponent<Props> = () => {
const [translate, i18n] = useTranslation();
return (
<>
<h1>Our fancy header with navigation.</h1>
<p>App version: {JSON.stringify(Config.version)}</p>
<h1>{translate("navBar.intro")}</h1>
<p>
{/* trans can also be used to translate */}
<Trans i18nKey="navBar.version">App version:</Trans>
{JSON.stringify(Config.version)}
</p>
{/* This translation uses a formatter in the translation files */}
<p>{translate("navBar.currentDate", { date: DateTime.now().toJSDate() })}</p>
<nav data-testid="nav">
<Link to="/" data-testid="nav.home">
Home
{translate("nav.home")}
</Link>
<Link to="/about" data-testid="nav.about">
About
{translate("nav.about")}
</Link>
<Link to="/counter" data-testid="nav.counter">
Counter
{translate("nav.counter")}
</Link>
<button onClick={() => i18n.changeLanguage("en")}>en</button>
<button onClick={() => i18n.changeLanguage("nl")}>nl</button>
<hr />
</nav>
</>

View File

@@ -0,0 +1,11 @@
import { FunctionComponent, ReactNode, Suspense } from "react";
import { AppLoader } from "../loader/appLoader";
type Props = { children?: ReactNode };
/**
* Component which wraps children in a fallback loader
*/
export const Loader: FunctionComponent<Props> = ({ children }) => {
return <Suspense fallback={<AppLoader />}>{children}</Suspense>;
};