This commit is contained in:
peintnermax
2024-10-08 09:31:56 +02:00
parent 9b251a3f4f
commit a5af0cae4d
28 changed files with 216 additions and 31 deletions

7
apps/login/i18nConfig.js Normal file
View File

@@ -0,0 +1,7 @@
const i18nConfig = {
locales: ["en", "de", "it"],
defaultLocale: "en",
prefixDefault: false,
};
module.exports = i18nConfig;

View File

View File

@@ -42,14 +42,18 @@
"@zitadel/proto": "workspace:*",
"clsx": "1.2.1",
"copy-to-clipboard": "^3.3.3",
"i18next": "^23.15.2",
"i18next-resources-to-backend": "^1.2.1",
"moment": "^2.29.4",
"next": "14.2.10",
"next-i18n-router": "^5.5.1",
"next-themes": "^0.2.1",
"nice-grpc": "2.0.1",
"qrcode.react": "^3.1.0",
"react": "^18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "7.39.5",
"react-i18next": "^15.0.2",
"swr": "^2.2.0",
"tinycolor2": "1.4.2"
},

View File

@@ -0,0 +1,40 @@
import { createInstance } from "i18next";
import { initReactI18next } from "react-i18next/initReactI18next";
import resourcesToBackend from "i18next-resources-to-backend";
import i18nConfig from "i18nConfig";
export default async function initTranslations(
locale,
namespaces,
i18nInstance,
resources
) {
i18nInstance = i18nInstance || createInstance();
i18nInstance.use(initReactI18next);
if (!resources) {
i18nInstance.use(
resourcesToBackend((language, namespace) =>
import(`/locales/${language}/${namespace}.json`)
)
);
}
await i18nInstance.init({
lng: locale,
resources,
fallbackLng: i18nConfig.defaultLocale,
supportedLngs: i18nConfig.locales,
defaultNS: namespaces[0],
fallbackNS: namespaces[0],
ns: namespaces,
preload: resources ? [] : i18nConfig.locales,
});
return {
i18n: i18nInstance,
resources: i18nInstance.services.resourceStore.data,
t: i18nInstance.t,
};
}

View File

@@ -4,9 +4,13 @@ import { AddressBar } from "@/components/address-bar";
import { GlobalNav } from "@/components/global-nav";
import { Theme } from "@/components/theme";
import { ThemeProvider } from "@/components/theme-provider";
import { TranslationsProvider } from "@/components/translations-provider";
import { Analytics } from "@vercel/analytics/react";
import i18nConfig from "i18nConfig";
import { dir } from "i18next";
import { Lato } from "next/font/google";
import { ReactNode } from "react";
import initTranslations from "./i18n";
const lato = Lato({
weight: ["400", "700", "900"],
@@ -15,11 +19,20 @@ const lato = Lato({
export const revalidate = 60; // revalidate every minute
export function generateStaticParams() {
return i18nConfig.locales.map((locale) => ({ locale }));
}
export default async function RootLayout({
children,
params: { locale },
}: {
children: ReactNode;
params: { locale: string };
}) {
const i18nNamespaces = ["common", "footer"];
const { t, resources } = await initTranslations(locale, i18nNamespaces);
// later only shown with dev mode enabled
const showNav = process.env.DEBUG === "true";
@@ -27,45 +40,56 @@ export default async function RootLayout({
domain = domain ? domain.replace("https://", "") : "acme.com";
return (
<html lang="en" className={`${lato.className}`} suppressHydrationWarning>
<html
lang={locale}
dir={dir(locale)}
className={`${lato.className}`}
suppressHydrationWarning
>
<head />
<body>
<ThemeProvider>
<div
className={`h-screen overflow-y-scroll bg-background-light-600 dark:bg-background-dark-600 ${
showNav
? "bg-[url('/grid-light.svg')] dark:bg-[url('/grid-dark.svg')]"
: ""
}`}
<TranslationsProvider
namespaces={i18nNamespaces}
locale={locale}
resources={resources}
>
{showNav ? (
<GlobalNav />
) : (
<div className="absolute bottom-0 right-0 flex flex-row p-4">
<Theme />
</div>
)}
<div
className={`${
showNav ? "lg:pl-72" : ""
} pb-4 flex flex-col justify-center h-full`}
className={`h-screen overflow-y-scroll bg-background-light-600 dark:bg-background-dark-600 ${
showNav
? "bg-[url('/grid-light.svg')] dark:bg-[url('/grid-dark.svg')]"
: ""
}`}
>
<div className="mx-auto max-w-[440px] space-y-8 pt-20 lg:py-8 w-full">
{showNav && (
<div className="rounded-lg bg-vc-border-gradient dark:bg-dark-vc-border-gradient p-px shadow-lg shadow-black/5 dark:shadow-black/20">
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500">
<AddressBar domain={domain} />
</div>
</div>
)}
{showNav ? (
<GlobalNav />
) : (
<div className="absolute bottom-0 right-0 flex flex-row p-4">
<Theme />
</div>
)}
{children}
<div
className={`${
showNav ? "lg:pl-72" : ""
} pb-4 flex flex-col justify-center h-full`}
>
<div className="mx-auto max-w-[440px] space-y-8 pt-20 lg:py-8 w-full">
{showNav && (
<div className="rounded-lg bg-vc-border-gradient dark:bg-dark-vc-border-gradient p-px shadow-lg shadow-black/5 dark:shadow-black/20">
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500">
<AddressBar domain={domain} />
</div>
</div>
)}
{children}
</div>
</div>
</div>
</div>
<Analytics />
<Analytics />
</TranslationsProvider>
</ThemeProvider>
</body>
</html>

View File

@@ -0,0 +1,26 @@
"use client";
import { createInstance } from "i18next";
import React from "react";
import { I18nextProvider } from "react-i18next";
import initTranslations from "../app/i18n";
type Props = {
locale: string;
children: React.ReactNode;
namespaces: string[];
resources: Record<string, Record<string, string>>;
};
export function TranslationsProvider({
children,
locale,
namespaces,
resources,
}: Props) {
const i18n = createInstance();
initTranslations(locale, namespaces, i18n, resources);
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
}

View File

@@ -32,6 +32,7 @@ export function middleware(request: NextRequest) {
responseHeaders.set("Access-Control-Allow-Headers", "*");
request.nextUrl.href = `${INSTANCE}${request.nextUrl.pathname}${request.nextUrl.search}`;
return NextResponse.rewrite(request.nextUrl, {
request: {
headers: requestHeaders,

87
pnpm-lock.yaml generated
View File

@@ -77,12 +77,21 @@ importers:
copy-to-clipboard:
specifier: ^3.3.3
version: 3.3.3
i18next:
specifier: ^23.15.2
version: 23.15.2
i18next-resources-to-backend:
specifier: ^1.2.1
version: 1.2.1
moment:
specifier: ^2.29.4
version: 2.30.1
next:
specifier: 14.2.10
version: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1)
next-i18n-router:
specifier: ^5.5.1
version: 5.5.1
next-themes:
specifier: ^0.2.1
version: 0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -101,6 +110,9 @@ importers:
react-hook-form:
specifier: 7.39.5
version: 7.39.5(react@18.3.1)
react-i18next:
specifier: ^15.0.2
version: 15.0.2(i18next@23.15.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
swr:
specifier: ^2.2.0
version: 2.2.5(react@18.3.1)
@@ -839,6 +851,9 @@ packages:
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'}
'@formatjs/intl-localematcher@0.5.4':
resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==}
'@grpc/grpc-js@1.11.1':
resolution: {integrity: sha512-gyt/WayZrVPH2w/UTLansS7F9Nwld472JxxaETamrM8HNlsa+jSLNyKAZmhxI2Me4c3mQHFiS1wWHDY1g1Kthw==}
engines: {node: '>=12.10.0'}
@@ -2141,6 +2156,7 @@ packages:
eslint@8.57.1:
resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
espree@9.6.1:
@@ -2491,6 +2507,9 @@ packages:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
html-parse-stringify@3.0.1:
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
@@ -2522,6 +2541,12 @@ packages:
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
engines: {node: '>=16.17.0'}
i18next-resources-to-backend@1.2.1:
resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==}
i18next@23.15.2:
resolution: {integrity: sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==}
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@@ -3102,6 +3127,13 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
next-i18n-router@5.5.1:
resolution: {integrity: sha512-uJGYUAQS33LbRT3Jx+kurR/E79iPQo1jWZUYmc+614UkPt58k2XYyGloSvHR74b21i4K/d6eksdBj6T2WojjdA==}
next-themes@0.2.1:
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
peerDependencies:
@@ -3603,6 +3635,19 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17 || ^18
react-i18next@15.0.2:
resolution: {integrity: sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==}
peerDependencies:
i18next: '>= 23.2.3'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -4375,6 +4420,10 @@ packages:
jsdom:
optional: true
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
@@ -5065,6 +5114,10 @@ snapshots:
'@fastify/busboy@2.1.1': {}
'@formatjs/intl-localematcher@0.5.4':
dependencies:
tslib: 2.7.0
'@grpc/grpc-js@1.11.1':
dependencies:
'@grpc/proto-loader': 0.7.13
@@ -5136,14 +5189,14 @@ snapshots:
'@manypkg/find-root@1.1.0':
dependencies:
'@babel/runtime': 7.25.6
'@babel/runtime': 7.25.7
'@types/node': 12.20.55
find-up: 4.1.0
fs-extra: 8.1.0
'@manypkg/get-packages@1.1.3':
dependencies:
'@babel/runtime': 7.25.6
'@babel/runtime': 7.25.7
'@changesets/types': 4.1.0
'@manypkg/find-root': 1.1.0
fs-extra: 8.1.0
@@ -6988,6 +7041,10 @@ snapshots:
dependencies:
whatwg-encoding: 3.1.1
html-parse-stringify@3.0.1:
dependencies:
void-elements: 3.1.0
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.1
@@ -7023,6 +7080,14 @@ snapshots:
human-signals@5.0.0: {}
i18next-resources-to-backend@1.2.1:
dependencies:
'@babel/runtime': 7.25.7
i18next@23.15.2:
dependencies:
'@babel/runtime': 7.25.7
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
@@ -7579,6 +7644,13 @@ snapshots:
natural-compare@1.4.0: {}
negotiator@0.6.3: {}
next-i18n-router@5.5.1:
dependencies:
'@formatjs/intl-localematcher': 0.5.4
negotiator: 0.6.3
next-themes@0.2.1(next@14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
next: 14.2.10(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.1)
@@ -7990,6 +8062,15 @@ snapshots:
dependencies:
react: 18.3.1
react-i18next@15.0.2(i18next@23.15.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.25.7
html-parse-stringify: 3.0.1
i18next: 23.15.2
react: 18.3.1
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
react-is@16.13.1: {}
react-is@17.0.2: {}
@@ -8834,6 +8915,8 @@ snapshots:
- supports-color
- terser
void-elements@3.1.0: {}
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0