implement language switcher

This commit is contained in:
peintnermax
2024-10-08 15:14:29 +02:00
parent fc03ecdc60
commit e6a5431124
8 changed files with 175 additions and 24 deletions

View File

@@ -1,5 +1,5 @@
const i18nConfig = {
locales: ["en"],
locales: ["en", "de", "it"],
defaultLocale: "en",
prefixDefault: false,
};

View File

@@ -0,0 +1,4 @@
{
"title": "Willkommen zurück!",
"description": "Geben Sie Ihre Anmeldedaten ein."
}

View File

@@ -0,0 +1,4 @@
{
"title": "Welcome back!",
"description": "Enter your login data."
}

View File

@@ -33,7 +33,7 @@
"*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@headlessui/react": "^1.7.18",
"@headlessui/react": "^2.1.9",
"@heroicons/react": "2.1.3",
"@tailwindcss/forms": "0.5.7",
"@vercel/analytics": "^1.2.2",

View File

@@ -2,6 +2,7 @@ import "@/styles/globals.scss";
import { AddressBar } from "@/components/address-bar";
import { GlobalNav } from "@/components/global-nav";
import { LanguageSwitcher } from "@/components/language-switcher";
import { Theme } from "@/components/theme";
import { ThemeProvider } from "@/components/theme-provider";
import { TranslationsProvider } from "@/components/translations-provider";
@@ -65,7 +66,8 @@ export default async function RootLayout({
{showNav ? (
<GlobalNav />
) : (
<div className="absolute bottom-0 right-0 flex flex-row p-4">
<div className="absolute bottom-0 right-0 flex flex-row p-4 items-center space-x-4">
<LanguageSwitcher locale={locale} />
<Theme />
</div>
)}

View File

@@ -1,6 +1,12 @@
"use client";
import { Listbox, Transition } from "@headlessui/react";
import {
Listbox,
ListboxButton,
ListboxOption,
ListboxOptions,
Transition,
} from "@headlessui/react";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
import { usePathname, useRouter } from "next/navigation";
import { Fragment, useState } from "react";
@@ -39,7 +45,7 @@ type Props = {
locale: string;
};
export default function LanguageSwitcher({ locale }: Props) {
export function LanguageSwitcher({ locale }: Props) {
const { i18n } = useTranslation();
const currentLocale = locale || i18n.language || i18nConfig.defaultLocale;
@@ -77,10 +83,10 @@ export default function LanguageSwitcher({ locale }: Props) {
};
return (
<div className="mb-4 w-32">
<div className="w-32">
<Listbox value={selected} onChange={handleChange}>
<div className="relative mt-1">
<Listbox.Button className="relative w-full cursor-default rounded-lg border border-divider-light bg-white py-2 pl-3 pr-10 text-left focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm">
<div className="relative">
<ListboxButton className="relative w-full cursor-default rounded-lg border border-divider-light bg-background-light-500 dark:bg-background-dark-500 py-2 pl-3 pr-10 text-left focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm">
<span className="block truncate">{selected.name}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon
@@ -88,20 +94,25 @@ export default function LanguageSwitcher({ locale }: Props) {
aria-hidden="true"
/>
</span>
</Listbox.Button>
</ListboxButton>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm">
<ListboxOptions
anchor="bottom"
className="absolute mt-1 max-h-60 w-[var(--button-width)] w-full overflow-auto rounded-md text-text-light-500 dark:text-text-dark-500 bg-background-light-500 dark:bg-background-dark-500 py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm"
>
{LANGS.map((lang, index) => (
<Listbox.Option
<ListboxOption
key={lang.code}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 ${
active ? "bg-amber-100 text-amber-900" : "text-gray-900"
active
? "bg-background-light-300 dark:bg-background-dark-300"
: ""
}`
}
value={lang}
@@ -116,15 +127,15 @@ export default function LanguageSwitcher({ locale }: Props) {
{lang.name}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-primary-light-500 dark:text-primary-dark-500">
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
</ListboxOption>
))}
</Listbox.Options>
</ListboxOptions>
</Transition>
</div>
</Listbox>

View File

@@ -21,7 +21,7 @@ export function Theme() {
return (
<div
className={`relative grid grid-cols-2 rounded-full border border-divider-light dark:border-divider-dark p-1`}
className={`h-fit relative grid grid-cols-2 rounded-full border border-divider-light dark:border-divider-dark p-1`}
>
<button
className={`h-8 w-8 rounded-full flex flex-row items-center justify-center hover:opacity-100 transition-all ${

146
pnpm-lock.yaml generated
View File

@@ -51,8 +51,8 @@ importers:
apps/login:
dependencies:
'@headlessui/react':
specifier: ^1.7.18
version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^2.1.9
version: 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@heroicons/react':
specifier: 2.1.3
version: 2.1.3(react@18.3.1)
@@ -851,6 +851,27 @@ packages:
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'}
'@floating-ui/core@1.6.8':
resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==}
'@floating-ui/dom@1.6.11':
resolution: {integrity: sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==}
'@floating-ui/react-dom@2.1.2':
resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@floating-ui/react@0.26.24':
resolution: {integrity: sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@floating-ui/utils@0.2.8':
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
'@formatjs/intl-localematcher@0.5.4':
resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==}
@@ -869,12 +890,12 @@ packages:
'@hapi/topo@5.1.0':
resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
'@headlessui/react@1.7.19':
resolution: {integrity: sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==}
'@headlessui/react@2.1.9':
resolution: {integrity: sha512-ckWw7vlKtnoa1fL2X0fx1a3t/Li9MIKDVXn3SgG65YlxvDAsNrY39PPCxVM7sQRA7go2fJsuHSSauKFNaJHH7A==}
engines: {node: '>=10'}
peerDependencies:
react: ^16 || ^17 || ^18
react-dom: ^16 || ^17 || ^18
react: ^18
react-dom: ^18
'@heroicons/react@2.1.3':
resolution: {integrity: sha512-fEcPfo4oN345SoqdlCDdSa4ivjaKbk0jTd+oubcgNxnNgAfzysfwWfQUr+51wigiWHQQRiZNd1Ao0M5Y3M2EGg==}
@@ -1039,6 +1060,37 @@ packages:
'@protobufjs/utf8@1.1.0':
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
'@react-aria/focus@3.18.3':
resolution: {integrity: sha512-WKUElg+5zS0D3xlVn8MntNnkzJql2J6MuzAMP8Sv5WTgFDse/XGR842dsxPTIyKKdrWVCRegCuwa4m3n/GzgJw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
'@react-aria/interactions@3.22.3':
resolution: {integrity: sha512-RRUb/aG+P0IKTIWikY/SylB6bIbLZeztnZY2vbe7RAG5MgVaCgn5HQ45SI15GlTmhsFG8CnF6slJsUFJiNHpbQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
'@react-aria/ssr@3.9.6':
resolution: {integrity: sha512-iLo82l82ilMiVGy342SELjshuWottlb5+VefO3jOQqQRNYnJBFpUSadswDPbRimSgJUZuFwIEYs6AabkP038fA==}
engines: {node: '>= 12'}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
'@react-aria/utils@3.25.3':
resolution: {integrity: sha512-PR5H/2vaD8fSq0H/UB9inNbc8KDcVmW6fYAfSWkkn+OAdhTTMVKqXXrZuZBWyFfSD5Ze7VN6acr4hrOQm2bmrA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
'@react-stately/utils@3.10.4':
resolution: {integrity: sha512-gBEQEIMRh5f60KCm7QKQ2WfvhB2gLUr9b72sqUdIZ2EG+xuPgaIlCBeSicvjmjBvYZwOjoOEnmIkcx2GHp/HWw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
'@react-types/shared@3.25.0':
resolution: {integrity: sha512-OZSyhzU6vTdW3eV/mz5i6hQwQUhkRs7xwY2d1aqPvTdMe0+2cY7Fwp45PAiwYLEj73i9ro2FxF9qC4DvHGSCgQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
'@rollup/rollup-android-arm-eabi@4.21.3':
resolution: {integrity: sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==}
cpu: [arm]
@@ -1706,6 +1758,10 @@ packages:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
engines: {node: '>=6'}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@@ -4054,6 +4110,9 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
tailwindcss@3.4.12:
resolution: {integrity: sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==}
engines: {node: '>=14.0.0'}
@@ -5119,6 +5178,31 @@ snapshots:
'@fastify/busboy@2.1.1': {}
'@floating-ui/core@1.6.8':
dependencies:
'@floating-ui/utils': 0.2.8
'@floating-ui/dom@1.6.11':
dependencies:
'@floating-ui/core': 1.6.8
'@floating-ui/utils': 0.2.8
'@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/dom': 1.6.11
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@floating-ui/react@0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@floating-ui/utils': 0.2.8
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tabbable: 6.2.0
'@floating-ui/utils@0.2.8': {}
'@formatjs/intl-localematcher@0.5.4':
dependencies:
tslib: 2.7.0
@@ -5141,10 +5225,12 @@ snapshots:
dependencies:
'@hapi/hoek': 9.3.0
'@headlessui/react@1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@headlessui/react@2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/react': 0.26.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@react-aria/focus': 3.18.3(react@18.3.1)
'@react-aria/interactions': 3.22.3(react@18.3.1)
'@tanstack/react-virtual': 3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
client-only: 0.0.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -5296,6 +5382,46 @@ snapshots:
'@protobufjs/utf8@1.1.0': {}
'@react-aria/focus@3.18.3(react@18.3.1)':
dependencies:
'@react-aria/interactions': 3.22.3(react@18.3.1)
'@react-aria/utils': 3.25.3(react@18.3.1)
'@react-types/shared': 3.25.0(react@18.3.1)
'@swc/helpers': 0.5.5
clsx: 2.1.1
react: 18.3.1
'@react-aria/interactions@3.22.3(react@18.3.1)':
dependencies:
'@react-aria/ssr': 3.9.6(react@18.3.1)
'@react-aria/utils': 3.25.3(react@18.3.1)
'@react-types/shared': 3.25.0(react@18.3.1)
'@swc/helpers': 0.5.5
react: 18.3.1
'@react-aria/ssr@3.9.6(react@18.3.1)':
dependencies:
'@swc/helpers': 0.5.5
react: 18.3.1
'@react-aria/utils@3.25.3(react@18.3.1)':
dependencies:
'@react-aria/ssr': 3.9.6(react@18.3.1)
'@react-stately/utils': 3.10.4(react@18.3.1)
'@react-types/shared': 3.25.0(react@18.3.1)
'@swc/helpers': 0.5.5
clsx: 2.1.1
react: 18.3.1
'@react-stately/utils@3.10.4(react@18.3.1)':
dependencies:
'@swc/helpers': 0.5.5
react: 18.3.1
'@react-types/shared@3.25.0(react@18.3.1)':
dependencies:
react: 18.3.1
'@rollup/rollup-android-arm-eabi@4.21.3':
optional: true
@@ -5972,6 +6098,8 @@ snapshots:
clsx@1.2.1: {}
clsx@2.1.1: {}
color-convert@1.9.3:
dependencies:
color-name: 1.1.3
@@ -8542,6 +8670,8 @@ snapshots:
symbol-tree@3.2.4: {}
tabbable@6.2.0: {}
tailwindcss@3.4.12:
dependencies:
'@alloc/quick-lru': 5.2.0