feat: setup next-intl with initial page props

This commit is contained in:
Nicolas Meienberger 2023-05-06 13:19:07 +02:00 committed by Nicolas Meienberger
parent c52684e08e
commit 1fca513c75
16 changed files with 219 additions and 48 deletions

3
crowdin.yml Normal file
View file

@ -0,0 +1,3 @@
files:
- source: /src/client/messages/en.json
translation: /src/client/messages/%locale%.json

2
global.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
type Messages = typeof import('./src/client/messages/en.json');
type IntlMessages = Messages;

View file

@ -53,9 +53,12 @@
"fs-extra": "^11.1.0",
"isomorphic-fetch": "^3.0.0",
"jsonwebtoken": "^9.0.0",
"lodash.merge": "^4.6.2",
"next": "13.2.4",
"next-intl": "^2.13.1",
"node-cron": "^3.0.1",
"node-fetch-commonjs": "^3.2.4",
"nookies": "^2.5.2",
"pg": "^8.10.0",
"qrcode.react": "^3.1.0",
"react": "18.2.0",
@ -95,6 +98,7 @@
"@types/isomorphic-fetch": "^0.0.36",
"@types/jest": "^29.5.0",
"@types/jsonwebtoken": "^9.0.0",
"@types/lodash.merge": "^4.6.7",
"@types/node": "18.15.3",
"@types/node-cron": "^3.0.2",
"@types/pg": "^8.6.6",

View file

@ -79,15 +79,24 @@ dependencies:
jsonwebtoken:
specifier: ^9.0.0
version: 9.0.0
lodash.merge:
specifier: ^4.6.2
version: 4.6.2
next:
specifier: 13.2.4
version: 13.2.4(@babel/core@7.21.3)(react-dom@18.2.0)(react@18.2.0)(sass@1.59.3)
next-intl:
specifier: ^2.13.1
version: 2.13.1(next@13.2.4)(react@18.2.0)
node-cron:
specifier: ^3.0.1
version: 3.0.2
node-fetch-commonjs:
specifier: ^3.2.4
version: 3.2.4
nookies:
specifier: ^2.5.2
version: 2.5.2
pg:
specifier: ^8.10.0
version: 8.10.0
@ -201,6 +210,9 @@ devDependencies:
'@types/jsonwebtoken':
specifier: ^9.0.0
version: 9.0.1
'@types/lodash.merge':
specifier: ^4.6.7
version: 4.6.7
'@types/node':
specifier: 18.15.3
version: 18.15.3
@ -1077,6 +1089,53 @@ packages:
- '@types/react'
dev: false
/@formatjs/ecma402-abstract@1.11.4:
resolution: {integrity: sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==}
dependencies:
'@formatjs/intl-localematcher': 0.2.25
tslib: 2.5.0
dev: false
/@formatjs/ecma402-abstract@1.15.0:
resolution: {integrity: sha512-7bAYAv0w4AIao9DNg0avfOLTCPE9woAgs6SpXuMq11IN3A+l+cq8ghczwqSZBM11myvPSJA7vLn72q0rJ0QK6Q==}
dependencies:
'@formatjs/intl-localematcher': 0.2.32
tslib: 2.5.0
dev: false
/@formatjs/fast-memoize@1.2.1:
resolution: {integrity: sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==}
dependencies:
tslib: 2.5.0
dev: false
/@formatjs/icu-messageformat-parser@2.1.0:
resolution: {integrity: sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==}
dependencies:
'@formatjs/ecma402-abstract': 1.11.4
'@formatjs/icu-skeleton-parser': 1.3.6
tslib: 2.5.0
dev: false
/@formatjs/icu-skeleton-parser@1.3.6:
resolution: {integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==}
dependencies:
'@formatjs/ecma402-abstract': 1.11.4
tslib: 2.5.0
dev: false
/@formatjs/intl-localematcher@0.2.25:
resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==}
dependencies:
tslib: 2.5.0
dev: false
/@formatjs/intl-localematcher@0.2.32:
resolution: {integrity: sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==}
dependencies:
tslib: 2.5.0
dev: false
/@hookform/resolvers@2.9.11(react-hook-form@7.43.7):
resolution: {integrity: sha512-bA3aZ79UgcHj7tFV7RlgThzwSSHZgvfbt2wprldRkYBcMopdMvHyO17Wwp/twcJasNFischFfS7oz8Katz8DdQ==}
peerDependencies:
@ -2500,6 +2559,16 @@ packages:
'@types/node': 18.15.3
dev: true
/@types/lodash.merge@4.6.7:
resolution: {integrity: sha512-OwxUJ9E50gw3LnAefSHJPHaBLGEKmQBQ7CZe/xflHkyy/wH2zVyEIAKReHvVrrn7zKdF58p16We9kMfh7v0RRQ==}
dependencies:
'@types/lodash': 4.14.194
dev: true
/@types/lodash@4.14.194:
resolution: {integrity: sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==}
dev: true
/@types/mdast@3.0.10:
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
dependencies:
@ -5714,6 +5783,15 @@ packages:
side-channel: 1.0.4
dev: true
/intl-messageformat@9.13.0:
resolution: {integrity: sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==}
dependencies:
'@formatjs/ecma402-abstract': 1.11.4
'@formatjs/fast-memoize': 1.2.1
'@formatjs/icu-messageformat-parser': 2.1.0
tslib: 2.5.0
dev: false
/invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
dependencies:
@ -6727,7 +6805,6 @@ packages:
/lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash.throttle@4.1.1:
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
@ -7397,6 +7474,20 @@ packages:
engines: {node: '>= 0.6'}
dev: false
/next-intl@2.13.1(next@13.2.4)(react@18.2.0):
resolution: {integrity: sha512-3XUZ7c123QHgQGcz5UUkTtakJdLETBlcHcdHop43iVToOpsezxvMZW6jxWwuHTRvkElfNPy1fhHwzBo/mhVVvQ==}
engines: {node: '>=10'}
peerDependencies:
next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@formatjs/intl-localematcher': 0.2.32
negotiator: 0.6.3
next: 13.2.4(@babel/core@7.21.3)(react-dom@18.2.0)(react@18.2.0)(sass@1.59.3)
react: 18.2.0
use-intl: 2.13.1(react@18.2.0)
dev: false
/next-router-mock@0.9.2(next@13.2.4)(react@18.2.0):
resolution: {integrity: sha512-rh6Mq1xhZ4Y0y9Z3seHZ04k4dAKnAyRcis7q3ZUF+Xp0uBeNqPC8Ydw5DldYncN3o1sYBqRyz25F/v/kfcg0/Q==}
peerDependencies:
@ -7524,6 +7615,13 @@ packages:
undefsafe: 2.0.5
dev: true
/nookies@2.5.2:
resolution: {integrity: sha512-x0TRSaosAEonNKyCrShoUaJ5rrT5KHRNZ5DwPCuizjgrnkpE5DRf3VL7AyyQin4htict92X1EQ7ejDbaHDVdYA==}
dependencies:
cookie: 0.4.2
set-cookie-parser: 2.5.1
dev: false
/nopt@1.0.10:
resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==}
hasBin: true
@ -8533,7 +8631,6 @@ packages:
/set-cookie-parser@2.5.1:
resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==}
dev: true
/setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@ -9316,6 +9413,17 @@ packages:
tslib: 2.5.0
dev: false
/use-intl@2.13.1(react@18.2.0):
resolution: {integrity: sha512-za8vb9UtKyFuDWbc+Iceqnz1KOAGwm9cTaBjW5af6e7ZcAdwADUwsz9M/8M9VDl5gKKQ/o+3TJcKdi+ieOKhfQ==}
engines: {node: '>=10'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@formatjs/ecma402-abstract': 1.15.0
intl-messageformat: 9.13.0
react: 18.2.0
dev: false
/use-isomorphic-layout-effect@1.1.2(@types/react@18.0.28)(react@18.2.0):
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
peerDependencies:

View file

@ -3,9 +3,11 @@ import { IncomingMessage } from 'http';
import { Session } from 'express-session';
import { GetServerSidePropsContext, GetServerSidePropsResult, PreviewData } from 'next';
import { ParsedUrlQuery } from 'querystring';
import { Locale } from '@/shared/internationalization/locales';
type SessionContent = {
userId?: number;
locale?: Locale;
};
declare module 'express-session' {

View file

@ -1,4 +1,6 @@
import nookies from 'nookies';
import { GetServerSideProps } from 'next';
import { getLocaleFromString } from '@/shared/internationalization/locales';
export const getAuthedPageProps: GetServerSideProps = async (ctx) => {
const { userId } = ctx.req.session;
@ -12,5 +14,22 @@ export const getAuthedPageProps: GetServerSideProps = async (ctx) => {
};
}
return { props: {} };
return {
props: {},
};
};
export const getMessagesPageProps: GetServerSideProps = async (ctx) => {
const cookies = nookies.get(ctx);
const { locale: sessionLocale } = ctx.req.session;
const { locale: cookieLocale } = cookies;
const browserLocale = ctx.req.headers['accept-language']?.split(',')[0];
const locale = sessionLocale || cookieLocale || browserLocale || 'en';
return {
props: {
messages: (await import(`../messages/${getLocaleFromString(locale)}.json`)).default,
},
};
};

View file

@ -2,10 +2,12 @@ import React, { useEffect } from 'react';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { NextIntlProvider } from 'next-intl';
import '../client/styles/global.css';
import '../client/styles/global.scss';
import 'react-tooltip/dist/react-tooltip.css';
import { Toaster } from 'react-hot-toast';
import { useLocale } from '@/client/hooks/useLocale';
import { useUIStore } from '../client/state/uiStore';
import { StatusProvider } from '../client/components/hoc/StatusProvider';
import { trpc } from '../client/utils/trpc';
@ -20,6 +22,7 @@ import { SystemStatus, useSystemStore } from '../client/state/systemStore';
function MyApp({ Component, pageProps }: AppProps) {
const { setDarkMode } = useUIStore();
const { setStatus, setVersion, pollStatus } = useSystemStore();
const { locale } = useLocale();
trpc.system.status.useQuery(undefined, { networkMode: 'online', refetchInterval: 2000, onSuccess: (d) => setStatus((d.status as SystemStatus) || 'RUNNING'), enabled: pollStatus });
const version = trpc.system.getVersion.useQuery(undefined, { networkMode: 'online' });
@ -46,13 +49,15 @@ function MyApp({ Component, pageProps }: AppProps) {
return (
<main className="h-100">
<Head>
<title>Tipi</title>
</Head>
<StatusProvider>
<Component {...pageProps} />
</StatusProvider>
<Toaster />
<NextIntlProvider locale={locale} messages={pageProps.messages}>
<Head>
<title>Tipi</title>
</Head>
<StatusProvider>
<Component {...pageProps} />
</StatusProvider>
<Toaster />
</NextIntlProvider>
<ReactQueryDevtools />
</main>
);

View file

@ -1,17 +1,19 @@
import { Context } from '@/server/context';
import { getAuthedPageProps } from '@/utils/page-helpers';
import { GetServerSidePropsContext } from 'next';
import { getAuthedPageProps, getMessagesPageProps } from '@/utils/page-helpers';
import merge from 'lodash.merge';
import { GetServerSideProps } from 'next';
export { AppDetailsPage as default } from '../../client/modules/Apps/pages/AppDetailsPage';
export const getServerSideProps = async (ctx: Context & GetServerSidePropsContext) => {
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const authedProps = await getAuthedPageProps(ctx);
const messagesProps = await getMessagesPageProps(ctx);
const { id } = ctx.query;
const appId = String(id);
return {
...authedProps,
props: { appId },
};
return merge(authedProps, messagesProps, {
props: {
appId,
},
});
};

View file

@ -1,13 +1,14 @@
import { getAuthedPageProps } from '@/utils/page-helpers';
import { getAuthedPageProps, getMessagesPageProps } from '@/utils/page-helpers';
import merge from 'lodash.merge';
import { GetServerSideProps } from 'next';
export { AppStorePage as default } from '../../client/modules/AppStore/pages/AppStorePage';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const authedProps = await getAuthedPageProps(ctx);
const messagesProps = await getMessagesPageProps(ctx);
return {
...authedProps,
return merge(authedProps, messagesProps, {
props: {},
};
});
};

View file

@ -1,17 +1,19 @@
import { Context } from '@/server/context';
import { getAuthedPageProps } from '@/utils/page-helpers';
import { GetServerSidePropsContext } from 'next';
import merge from 'lodash.merge';
import { getAuthedPageProps, getMessagesPageProps } from '@/utils/page-helpers';
import { GetServerSideProps } from 'next';
export { AppDetailsPage as default } from '../../client/modules/Apps/pages/AppDetailsPage';
export const getServerSideProps = async (ctx: Context & GetServerSidePropsContext) => {
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const authedProps = await getAuthedPageProps(ctx);
const messagesProps = await getMessagesPageProps(ctx);
const { id } = ctx.query;
const appId = String(id);
return {
...authedProps,
props: { appId },
};
return merge(authedProps, messagesProps, {
props: {
appId,
},
});
};

View file

@ -1,13 +1,14 @@
import { getAuthedPageProps } from '@/utils/page-helpers';
import { getAuthedPageProps, getMessagesPageProps } from '@/utils/page-helpers';
import merge from 'lodash.merge';
import { GetServerSideProps } from 'next';
export { AppsPage as default } from '../../client/modules/Apps/pages/AppsPage';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const authedProps = await getAuthedPageProps(ctx);
const messagesProps = await getMessagesPageProps(ctx);
return {
...authedProps,
return merge(authedProps, messagesProps, {
props: {},
};
});
};

View file

@ -1,13 +1,14 @@
import { getAuthedPageProps } from '@/utils/page-helpers';
import { getAuthedPageProps, getMessagesPageProps } from '@/utils/page-helpers';
import merge from 'lodash.merge';
import { GetServerSideProps } from 'next';
export { DashboardPage as default } from '../client/modules/Dashboard/pages/DashboardPage';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const authedProps = await getAuthedPageProps(ctx);
const messagesProps = await getMessagesPageProps(ctx);
return {
...authedProps,
return merge(authedProps, messagesProps, {
props: {},
};
});
};

View file

@ -1,9 +1,13 @@
import { getMessagesPageProps } from '@/utils/page-helpers';
import merge from 'lodash.merge';
import { GetServerSideProps } from 'next';
export { LoginPage as default } from '../client/modules/Auth/pages/LoginPage';
export const getServerSideProps: GetServerSideProps = async () => {
return {
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const messagesProps = await getMessagesPageProps(ctx);
return merge(messagesProps, {
props: {},
};
});
};

View file

@ -1,9 +1,13 @@
import { getMessagesPageProps } from '@/utils/page-helpers';
import merge from 'lodash.merge';
import { GetServerSideProps } from 'next';
export { RegisterPage as default } from '../client/modules/Auth/pages/RegisterPage';
export const getServerSideProps: GetServerSideProps = async () => {
return {
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const messagesProps = await getMessagesPageProps(ctx);
return merge(messagesProps, {
props: {},
};
});
};

View file

@ -1 +1,13 @@
import { getMessagesPageProps } from '@/utils/page-helpers';
import merge from 'lodash.merge';
import { GetServerSideProps } from 'next';
export { ResetPasswordPage as default } from '../client/modules/Auth/pages/ResetPasswordPage';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const messagesProps = await getMessagesPageProps(ctx);
return merge(messagesProps, {
props: {},
});
};

View file

@ -1,13 +1,14 @@
import { getAuthedPageProps } from '@/utils/page-helpers';
import { getAuthedPageProps, getMessagesPageProps } from '@/utils/page-helpers';
import merge from 'lodash.merge';
import { GetServerSideProps } from 'next';
export { SettingsPage as default } from '../client/modules/Settings/pages/SettingsPage';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const authedProps = await getAuthedPageProps(ctx);
const messagesProps = await getMessagesPageProps(ctx);
return {
...authedProps,
return merge(authedProps, messagesProps, {
props: {},
};
});
};