mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-11 19:42:16 +00:00
i18n
This commit is contained in:
7
apps/login/i18nConfig.js
Normal file
7
apps/login/i18nConfig.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const i18nConfig = {
|
||||
locales: ["en", "de", "it"],
|
||||
defaultLocale: "en",
|
||||
prefixDefault: false,
|
||||
};
|
||||
|
||||
module.exports = i18nConfig;
|
||||
0
apps/login/locales/en/loginname.json
Normal file
0
apps/login/locales/en/loginname.json
Normal 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"
|
||||
},
|
||||
|
||||
40
apps/login/src/app/i18n.js
Normal file
40
apps/login/src/app/i18n.js
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
26
apps/login/src/components/translations-provider.tsx
Normal file
26
apps/login/src/components/translations-provider.tsx
Normal 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>;
|
||||
}
|
||||
@@ -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
87
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user