diff --git a/.eslintrc.js b/.eslintrc.js
index bf8001b..a32ab9e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,5 +1,9 @@
module.exports = {
- extends: 'eslint-config-ns-ts',
+ extends: [
+ 'eslint-config-ns-ts',
+ // @see https://nextjs.org/docs/basic-features/eslint
+ 'plugin:@next/next/recommended'
+ ],
rules: {
/**
* This rule was disabled because of NextJS' Link API.
diff --git a/.storybook/i18n.js b/.storybook/i18n.js
new file mode 100644
index 0000000..d7c5533
--- /dev/null
+++ b/.storybook/i18n.js
@@ -0,0 +1,42 @@
+import { initReactI18next } from 'react-i18next'
+import i18n from 'i18next';
+
+import Backend from 'i18next-xhr-backend';
+import LanguageDetector from 'i18next-browser-languagedetector';
+
+
+/**
+ * Example
+ * - @see https://github.com/i18next/react-i18next/blob/7cfab9746b3ccc6f833cd9c892e7c3c804944a5e/example/react-typescript4.1/namespaces/src/i18n/config.ts#L13
+ */
+i18n
+.use(Backend)
+.use(LanguageDetector)
+.use(initReactI18next)
+.init({
+ lng: 'en',
+ fallbackLng: 'en',
+ // have a common namespace used around the full app
+ ns: ['common'],
+ defaultNS: 'common',
+ debug: true,
+ interpolation: {
+ escapeValue: false, // not needed for react!!
+ },
+ // resources: {
+ // en: {
+ // 'common': {
+ // "home": "Home GERMAN",
+ // "home_EN": "Home English"
+ // }
+ // },
+ // de: {
+ // 'common': {
+ // "home": "Home DEUTSCH",
+ // "home_EN": "Home ENGLISCH"
+ // }
+ // }
+ // },
+})
+
+export default i18n
\ No newline at end of file
diff --git a/.storybook/main.js b/.storybook/main.js
index 37266f1..3ad3336 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -13,6 +13,8 @@ module.exports = {
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
+ 'storybook-addon-next-router',
+ 'storybook-addon-i18next/register',
{
/**
* NOTE: fix Storybook issue with PostCSS@8
@@ -44,6 +46,24 @@ module.exports = {
...config.resolve?.alias,
'@': [path.resolve(__dirname, '../src/'), path.resolve(__dirname, '../')],
}
+
+ /**
+ * Fixes issue with `next-i18next` and is ready for webpack@5
+ * @see https://github.com/isaachinman/next-i18next/issues/1012#issuecomment-792697008
+ * @see https://github.com/storybookjs/storybook/issues/4082#issuecomment-758272734
+ * @see https://webpack.js.org/migrate/5/
+ *
+ * source: https://github.com/isaachinman/next-i18next/issues/1012#issuecomment-818042184
+ */
+ config.resolve.fallback = {
+ ...config.resolve?.fallback,
+ fs: false,
+ // tls: false,
+ // net: false,
+ // module: false,
+ // path: require.resolve('path-browserify'),
+ }
+
return config
},
}
diff --git a/.storybook/preview.js b/.storybook/preview.js
index 452da4c..5906c32 100644
--- a/.storybook/preview.js
+++ b/.storybook/preview.js
@@ -1,16 +1,11 @@
-import { withNextRouter } from 'storybook-addon-next-router'
+import React, { Suspense } from 'react';
+import { RouterContext } from "next/dist/next-server/lib/router-context";
+// import { I18nextProvider } from 'react-i18next';
+import { withI18next } from 'storybook-addon-i18next';
import '../src/styles/index.scss'
-// @see https://www.npmjs.com/package/storybook-addon-next-router
-export const decorators = [
- withNextRouter({
- path: '/', // defaults to `/`
- asPath: '/', // defaults to `/`
- query: {}, // defaults to `{}`
- push() {}, // defaults to using addon actions integration, can override any method in the router
- }),
-]
+import i18n from './i18n'
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
@@ -20,4 +15,18 @@ export const parameters = {
date: /Date$/,
},
},
+ // @see https://www.npmjs.com/package/storybook-addon-next-router
+ nextRouter: {
+ Provider: RouterContext.Provider,
+ path: '/', // defaults to `/`
+ asPath: '/', // defaults to `/`
+ query: {}, // defaults to `{}`
+ push() {}, // defaults to using addon actions integration, can override any method in the router
+ }
}
+
+export const decorators = [
+ // Story => ,
+ withI18next({ i18n, languages: { en: 'English', de: 'Deutsch' }}),
+ Story =>
+]
diff --git a/__mocks__/react-i18next.js b/__mocks__/react-i18next.js
new file mode 100644
index 0000000..6180d3f
--- /dev/null
+++ b/__mocks__/react-i18next.js
@@ -0,0 +1,80 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+/* eslint-disable react/display-name */
+/* eslint-disable no-confusing-arrow */
+
+/**
+ * Setup i18n in tests
+ *
+ * Docs
+ * @see https://react.i18next.com/misc/testing
+ *
+ * Inspired by
+ * @see https://github.com/i18next/react-i18next/blob/552ed79036c28f282afe7c6ccb525b82b76e71d5/example/test-jest/src/__mocks__/react-i18next.js
+ *
+ * Alternative examples using `i18n.use()...`
+ * @see https://github.com/i18next/react-i18next/blob/552ed79036c28f282afe7c6ccb525b82b76e71d5/example/test-jest/src/setupTests.js#L4-L23
+ * @see https://github.com/i18next/react-i18next/blob/552ed79036c28f282afe7c6ccb525b82b76e71d5/test/i18n.js
+ * @see https://github.com/isaachinman/next-i18next/issues/377#issuecomment-700516821
+ */
+
+const React = require('react')
+const reactI18next = require('react-i18next')
+
+const hasChildren = node =>
+ node && (node.children || (node.props && node.props.children))
+
+const getChildren = node =>
+ node && node.children ? node.children : node.props && node.props.children
+
+const renderNodes = reactNodes => {
+ if (typeof reactNodes === 'string') {
+ return reactNodes
+ }
+
+ return Object.keys(reactNodes).map((key, i) => {
+ const child = reactNodes[key]
+ const isElement = React.isValidElement(child)
+
+ if (typeof child === 'string') {
+ return child
+ }
+ if (hasChildren(child)) {
+ const inner = renderNodes(getChildren(child))
+ return React.cloneElement(child, { ...child.props, key: i }, inner)
+ }
+ if (typeof child === 'object' && !isElement) {
+ return Object.keys(child).reduce(
+ (str, childKey) => `${str}${child[childKey]}`,
+ '',
+ )
+ }
+
+ return child
+ })
+}
+
+/**
+ * @type any
+ */
+const useMock = [k => k, {}]
+useMock.t = k => k
+useMock.i18n = {}
+
+module.exports = {
+ // this mock makes sure any components using the translate HoC receive the t function as a prop
+ withTranslation: () => Component => props => (
+ k} {...props} />
+ ),
+ Trans: ({ children }) =>
+ Array.isArray(children) ? renderNodes(children) : renderNodes([children]),
+ Translation: ({ children }) => children(k => k, { i18n: {} }),
+ useTranslation: () => useMock,
+
+ // mock if needed
+ I18nextProvider: reactI18next.I18nextProvider,
+ initReactI18next: reactI18next.initReactI18next,
+ setDefaults: reactI18next.setDefaults,
+ getDefaults: reactI18next.getDefaults,
+ setI18n: reactI18next.setI18n,
+ getI18n: reactI18next.getI18n,
+}
diff --git a/cypress.json b/cypress.json
index 17ef242..ba9bc7b 100644
--- a/cypress.json
+++ b/cypress.json
@@ -1,3 +1,4 @@
{
- "baseUrl": "http://localhost:3000"
+ "baseUrl": "http://localhost:3000",
+ "video": false
}
diff --git a/jest.setup.ts b/jest.setup.ts
index b854473..7f17313 100644
--- a/jest.setup.ts
+++ b/jest.setup.ts
@@ -1,11 +1,8 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable import/no-extraneous-dependencies */
import 'jest-preset-ns/presets/react/jest-setup.js'
-
import { setConfig } from 'next/config'
import { PHASE_DEVELOPMENT_SERVER } from 'next/constants'
-import i18n from 'i18next'
-import { initReactI18next } from 'react-i18next'
import { NextRouter } from 'next/router'
// @ts-ignore
@@ -22,29 +19,6 @@ const { publicRuntimeConfig, serverRuntimeConfig } = NextConfig(
*/
setConfig({ publicRuntimeConfig, serverRuntimeConfig })
-/**
- * Setup i18n in tests
- *
- * @see https://github.com/i18next/react-i18next/blob/552ed79036c28f282afe7c6ccb525b82b76e71d5/example/test-jest/src/setupTests.js#L4-L23
- * @see https://github.com/isaachinman/next-i18next/issues/377#issuecomment-700516821
- */
-i18n.use(initReactI18next).init({
- lng: 'en',
- fallbackLng: 'en',
-
- // have a common namespace used around the full app
- ns: ['translations'],
- defaultNS: 'translations',
-
- // debug: true,
-
- interpolation: {
- escapeValue: false, // not needed for react!!
- },
-
- resources: { en: { translations: {} } },
-})
-
/**
* mockRouter mocks the initial router state of Next.js
* @see https://github.com/vercel/next.js/issues/7479#issuecomment-752418517
diff --git a/next-env.d.ts b/next-env.d.ts
index 3b89435..c6643fd 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,18 +1,3 @@
///
///
-
-type NextConfig = {
- publicRuntimeConfig: {
- NODE_ENV: string
- /**
- * version of the app
- */
- VERSION: string
- }
- serverRuntimeConfig: Record
-}
-
-declare module 'next/config' {
- export declare function setConfig(configValue: any): void
- export default function getConfig(): NextConfig
-}
+///
diff --git a/next.config.js b/next.config.js
index 7add2a9..01d11aa 100644
--- a/next.config.js
+++ b/next.config.js
@@ -13,15 +13,13 @@ const version = require('./version')
* – It should be availabe already: https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config-shared.ts#L12
*/
const nextConfig = {
- poweredByHeader: false,
- /**
- * Opt-in to webpack 5 support
- * @see https://github.com/vercel/next.js/issues/21679#issuecomment-771941447
- */
- future: {
- webpack5: true,
+ eslint: {
+ // We have manual checks in place to make sure we do not build dangerous
+ // code.
+ ignoreDuringBuilds: true,
},
i18n,
+ poweredByHeader: false,
publicRuntimeConfig: {
NODE_ENV: process.env.NODE_ENV,
VERSION: version,
diff --git a/package.json b/package.json
index dd3cd06..790dea3 100644
--- a/package.json
+++ b/package.json
@@ -22,46 +22,54 @@
"lint:css": "stylelint --cache './**/*.{ts,tsx,scss}'",
"prepare": "node scripts/setup && is-ci || husky install",
"start": "next start",
- "storybook": "start-storybook -p 6006",
+ "storybook": "start-storybook -p 6006 -s ./public",
"test": "jest",
"test:cypress": "start-server-and-test dev 3000 cypress:open",
- "test:cypress:ci": "start-server-and-test dev 3000 cypress:run"
+ "test:cypress:ci": "yarn build && start-server-and-test start 3000 cypress:run"
},
"dependencies": {
- "date-fns": "^2.21.1",
- "next": "^10.1.3",
+ "date-fns": "^2.23.0",
+ "i18next": "^20.3.5",
+ "i18next-browser-languagedetector": "^6.1.2",
+ "i18next-xhr-backend": "^3.2.2",
+ "next": "^11.0.1",
"next-compose-plugins": "^2.2.1",
- "next-i18next": "^8.2.0",
- "react": "^16.14.0",
+ "next-i18next": "^8.5.5",
+ "react": "^17.0.2",
"react-cookie": "^4.0.3",
- "react-dom": "^16.14.0"
+ "react-dom": "^17.0.2",
+ "react-i18next": "^11.11.4"
},
"devDependencies": {
- "@babel/core": "^7.13.16",
- "@commitlint/cli": "^12.1.1",
- "@commitlint/config-conventional": "^12.1.1",
+ "@babel/core": "^7.14.8",
+ "@commitlint/cli": "^13.1.0",
+ "@commitlint/config-conventional": "^13.1.0",
"@natterstefan/scripts": "^1.2.0",
+ "@next/eslint-plugin-next": "^11.0.1",
"@semantic-release/changelog": "^5.0.1",
"@semantic-release/commit-analyzer": "^8.0.1",
"@semantic-release/git": "^9.0.0",
- "@semantic-release/github": "^7.2.1",
- "@semantic-release/release-notes-generator": "^9.0.2",
- "@storybook/addon-actions": "^6.2.9",
- "@storybook/addon-essentials": "^6.2.9",
- "@storybook/addon-links": "^6.2.9",
+ "@semantic-release/github": "^7.2.3",
+ "@semantic-release/release-notes-generator": "^9.0.3",
+ "@storybook/addon-actions": "^6.3.6",
+ "@storybook/addon-essentials": "^6.3.6",
+ "@storybook/addon-links": "^6.3.6",
"@storybook/addon-postcss": "^2.0.0",
- "@storybook/addons": "^6.2.9",
- "@storybook/builder-webpack5": "^6.2.9",
- "@storybook/react": "^6.2.9",
- "@types/enzyme": "^3.10.8",
+ "@storybook/addons": "^6.3.6",
+ "@storybook/builder-webpack5": "^6.3.6",
+ "@storybook/manager-webpack5": "^6.3.6",
+ "@storybook/react": "^6.3.6",
+ "@types/enzyme": "^3.10.9",
+ "@types/jest": "^26.0.24",
"@types/node": "^14.14.41",
- "@types/react": "^17.0.3",
- "@types/react-dom": "^17.0.3",
+ "@types/react": "^17.0.15",
+ "@types/react-dom": "^17.0.9",
+ "@wojtekmaj/enzyme-adapter-react-17": "^0.6.3",
"all-contributors-cli": "^6.20.0",
- "autoprefixer": "^10.2.5",
+ "autoprefixer": "^10.3.1",
"babel-loader": "^8.2.2",
- "css-loader": "^5.2.4",
- "cypress": "^6.9.1",
+ "css-loader": "^6.2.0",
+ "cypress": "^8.1.0",
"enzyme": "^3.11.0",
"eslint": "^7.25.0",
"eslint-config-airbnb": "^18.2.1",
@@ -69,31 +77,33 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.11.2",
"eslint-plugin-import": "^2.22.1",
- "eslint-plugin-jest": "^24.3.5",
+ "eslint-plugin-jest": "^24.4.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
- "husky": "^6.0.0",
+ "husky": "^7.0.1",
"is-ci": "^3.0.0",
- "jest": "^26.6.3",
- "jest-preset-ns": "^0.2.0",
- "postcss": "^8.2.12",
+ "jest": "^27.0.6",
+ "jest-date-mock": "^1.0.8",
+ "jest-preset-ns": "^1.0.0",
+ "postcss": "^8.3.6",
"postcss-flexbugs-fixes": "^5.0.2",
- "postcss-import": "^14.0.1",
- "postcss-loader": "^5.2.0",
+ "postcss-import": "^14.0.2",
+ "postcss-loader": "^6.1.1",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.2.1",
- "sass": "^1.32.11",
- "sass-loader": "^11.0.1",
- "semantic-release": "^17.4.2",
- "start-server-and-test": "^1.12.1",
- "storybook-addon-next-router": "^2.0.4",
- "style-loader": "^2.0.0",
- "stylelint": "^13.13.0",
+ "sass": "^1.37.2",
+ "sass-loader": "^12.1.0",
+ "semantic-release": "^17.4.4",
+ "start-server-and-test": "^1.13.1",
+ "storybook-addon-i18next": "^1.3.0",
+ "storybook-addon-next-router": "^3.0.5",
+ "style-loader": "^3.2.1",
+ "stylelint": "^13.13.1",
"stylelint-config-recommended": "^5.0.0",
- "tailwindcss": "^2.1.2",
- "typescript": "^4.2.4",
- "webpack": "^5.35.1"
+ "tailwindcss": "^2.2.7",
+ "typescript": "^4.3.5",
+ "webpack": "^5.48.0"
}
}
diff --git a/src/components/navigation/Navigation.tsx b/src/components/navigation/Navigation.tsx
index 4159ae7..f7df587 100644
--- a/src/components/navigation/Navigation.tsx
+++ b/src/components/navigation/Navigation.tsx
@@ -12,7 +12,7 @@ const links = [
]
export const Navigation = () => {
- const { t } = useTranslation()
+ const { t } = useTranslation('common')
return (