mirror of
https://github.com/Steffo99/todocolors.git
synced 2024-11-22 08:14:18 +00:00
Setup localization and localize root page
This commit is contained in:
parent
567818e95e
commit
f6f6cbf9df
58 changed files with 589 additions and 298 deletions
|
@ -19,11 +19,17 @@
|
|||
"@types/react-dom": "18.2.7",
|
||||
"classnames": "^2.3.2",
|
||||
"client-only": "^0.0.1",
|
||||
"i18next": "^23.4.2",
|
||||
"i18next-resources-to-backend": "^1.1.4",
|
||||
"negotiator": "^0.6.3",
|
||||
"next": "13.4.12",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"server-only": "^0.0.1",
|
||||
"typescript": "5.1.6",
|
||||
"use-local-storage": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/negotiator": "^0.6.1"
|
||||
}
|
||||
}
|
||||
|
|
32
todoblue/src/app/(i18n)/(locales)/(root)/en-US.json
Normal file
32
todoblue/src/app/(i18n)/(locales)/(root)/en-US.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"title": "Todocolors",
|
||||
"createBoardTitle": "Create a new board",
|
||||
"createPublicBoardTitle": "Public",
|
||||
"createPublicBoardDescription": "When creating a public board, you have to choose a code that others can use to access your board!",
|
||||
"createPublicBoardSmall": "If a board with the given code already exists, you'll be redirected to it.",
|
||||
"createPublicBoardCodeLeft": "Code",
|
||||
"createPublicBoardCodePlaceholder": "my-new-board",
|
||||
"createPublicBoardSubmitText": "Create public board",
|
||||
"createPrivateBoardTitle": "Private",
|
||||
"createPrivateBoardLoadingDescription": "Your connection is being checked to verify if it can support a private board.",
|
||||
"createPrivateBoardLoadingSmall": "Just a moment...",
|
||||
"createPrivateBoardUnavailableDescription": "Your connection does not support private boards.",
|
||||
"createPrivateBoardUnavailableSmall": "Are you using HTTPS?",
|
||||
"createPrivateBoardDescription": "When creating a private board, a random secure code will be assigned to it, which you can share privately with others to give them access.",
|
||||
"createPrivateBoardSmall": "Don't share this code with anyone who you don't want to give access to your board!",
|
||||
"createPrivateBoardSubmitText": "Create private board",
|
||||
"existingBoardTitle": "Use an existing board",
|
||||
"existingKnownBoardsTitle": "Access with code",
|
||||
"existingKnownBoardsDescription": "If you know the code of a board, you can access it by entering the code here.",
|
||||
"existingKnownBoardsSmall": "Entering an invalid code will create a new public board with the given code.",
|
||||
"existingKnownBoardsCodeLeft": "Code",
|
||||
"existingKnownBoardsCodePlaceholder": "your-new-board",
|
||||
"existingKnownBoardsSubmitText": "Access board with code",
|
||||
"existingStarredBoardsTitle": "Starred boards",
|
||||
"existingStarredBoardsLoadingDescription": "Your browser is loading the list of boards you've starred.",
|
||||
"existingStarredBoardsLoadingSmall": "Just a moment...",
|
||||
"existingStarredBoardsEmptyDescription": "You haven't starred any boards yet. Find or create a board, then come back here!",
|
||||
"existingStarredBoardsEmptySmall": "Once opened, you can star any board by pressing the second button from the top left.",
|
||||
"existingStarredBoardsDescription": "These are the codes for the boards your starred. Click on one of them to access the corresponding board.",
|
||||
"existingStarredBoardsSmall": "Once opened, you can unstar any board by pressing again the second button from the top left."
|
||||
}
|
3
todoblue/src/app/(i18n)/(locales)/index.ts
Normal file
3
todoblue/src/app/(i18n)/(locales)/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const AVAILABLE_LOCALES: string[] = [
|
||||
"en-US",
|
||||
]
|
41
todoblue/src/app/(i18n)/client.ts
Normal file
41
todoblue/src/app/(i18n)/client.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
"use client";
|
||||
import "client-only";
|
||||
import {createInstance, i18n} from "i18next"
|
||||
import resourcesToBackend from "i18next-resources-to-backend"
|
||||
import {useEffect, useState} from "react"
|
||||
|
||||
|
||||
async function init(lng: string, ns: string): Promise<i18n> {
|
||||
const instance = createInstance()
|
||||
await instance
|
||||
.use(resourcesToBackend((language: string, namespace: string) => import(`./(locales)/(${namespace})/${language}.json`)))
|
||||
.init({
|
||||
supportedLngs: ["en-US"],
|
||||
fallbackLng: "en-US",
|
||||
lng,
|
||||
fallbackNS: "common",
|
||||
defaultNS: "common",
|
||||
ns,
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
export function useTranslation(lng: string, ns: string) {
|
||||
const [instance, setInstance] = useState<i18n | undefined>(undefined);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
console.debug("[useTranslation] Initializing translation with:", lng, ":", ns)
|
||||
init(lng, ns).then((v: i18n) => {
|
||||
console.debug("[useTranslation] Initialized i18n:", v)
|
||||
return setInstance(v)
|
||||
})
|
||||
},
|
||||
[lng, ns]
|
||||
)
|
||||
|
||||
return {
|
||||
t: instance?.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns) ?? ((...args) => `${args}`),
|
||||
i18n: instance,
|
||||
}
|
||||
}
|
27
todoblue/src/app/(i18n)/server.ts
Normal file
27
todoblue/src/app/(i18n)/server.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import "server-only";
|
||||
import {createInstance, i18n} from "i18next"
|
||||
import resourcesToBackend from "i18next-resources-to-backend"
|
||||
|
||||
|
||||
async function init(lng: string, ns: string): Promise<i18n> {
|
||||
const instance = createInstance()
|
||||
await instance
|
||||
.use(resourcesToBackend((language: string, namespace: string) => import(`./(locales)/(${namespace})/${language}.json`)))
|
||||
.init({
|
||||
supportedLngs: ["en-US"],
|
||||
fallbackLng: "en-US",
|
||||
lng,
|
||||
fallbackNS: "common",
|
||||
defaultNS: "common",
|
||||
ns,
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
export async function useTranslation(lng: string, ns: string) {
|
||||
const instance = await init(lng, ns)
|
||||
return {
|
||||
t: instance.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns),
|
||||
i18n: instance
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {useBoardCreator} from "@/app/useBoardCreator"
|
||||
import {faKey} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import classNames from "classnames"
|
||||
import {default as React, useEffect, useState} from "react"
|
||||
|
||||
export function CreatePrivateBoardPanel() {
|
||||
const {createBoard} = useBoardCreator();
|
||||
const [canCreate, setCanCreate] = useState<boolean | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setCanCreate(window.isSecureContext)
|
||||
}, [])
|
||||
|
||||
let formContents;
|
||||
if(canCreate === null) {
|
||||
formContents = <MightCreateBoardFormContents/>
|
||||
}
|
||||
else if(!canCreate) {
|
||||
formContents = <CannotCreateBoardFormContents/>
|
||||
}
|
||||
else {
|
||||
formContents = <CanCreateBoardFormContents/>
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
className={classNames({
|
||||
"panel": true,
|
||||
"box": true,
|
||||
"form-flex": true,
|
||||
"fade": canCreate === null,
|
||||
"red": canCreate === false,
|
||||
})}
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
createBoard(crypto.randomUUID().toString());
|
||||
}}
|
||||
>
|
||||
<h3>
|
||||
<FontAwesomeIcon icon={faKey} size={"1x"}/>
|
||||
{" "}
|
||||
Privato
|
||||
</h3>
|
||||
{formContents}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function MightCreateBoardFormContents() {
|
||||
return <>
|
||||
<p>
|
||||
Sto verificando se è possibile creare un tabellone privato sul tuo browser...
|
||||
<br/>
|
||||
<small>Attendi un attimo...</small>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
|
||||
function CanCreateBoardFormContents() {
|
||||
return <>
|
||||
<p>
|
||||
Crea un nuovo tabellone privato utilizzando un codice segreto autogenerato!
|
||||
<br/>
|
||||
<small>Esso sarà accessibile solo da chi ne conosce il link.</small>
|
||||
</p>
|
||||
<label className={"float-bottom"}>
|
||||
<span/>
|
||||
<button>
|
||||
Crea
|
||||
</button>
|
||||
<span/>
|
||||
</label>
|
||||
</>
|
||||
}
|
||||
|
||||
function CannotCreateBoardFormContents() {
|
||||
return <>
|
||||
<p>
|
||||
Questa funzionalità non è disponibile al di fuori di contesti sicuri.
|
||||
<br/>
|
||||
<small>Assicurati di stare usando HTTPS!</small>
|
||||
</p>
|
||||
</>
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {useBoardCreator} from "@/app/useBoardCreator"
|
||||
import {useLowerKebabState} from "@/app/useKebabState"
|
||||
import {faGlobe} from "@fortawesome/free-solid-svg-icons"
|
||||
import {default as React} from "react"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
|
||||
|
||||
export function CreatePublicBoardPanel() {
|
||||
const [code, setCode] = useLowerKebabState("")
|
||||
const {createBoard} = useBoardCreator();
|
||||
|
||||
return (
|
||||
<form
|
||||
className={"panel box form-flex"}
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
createBoard(code);
|
||||
}}
|
||||
>
|
||||
<h3>
|
||||
<FontAwesomeIcon icon={faGlobe}/>
|
||||
{" "}
|
||||
Pubblico
|
||||
</h3>
|
||||
<p>
|
||||
Crea un nuovo tabellone pubblico, con un codice personalizzato!
|
||||
<br/>
|
||||
<small>Se un tabellone con quel codice esiste già, sarai reindirizzato ad esso.</small>
|
||||
</p>
|
||||
<label className={"float-bottom"}>
|
||||
<span>
|
||||
Codice
|
||||
</span>
|
||||
<input
|
||||
type={"text"}
|
||||
placeholder={"garasauto-planning-2023"}
|
||||
value={code}
|
||||
onChange={(
|
||||
e => setCode(e.target.value)
|
||||
)}
|
||||
/>
|
||||
<span/>
|
||||
</label>
|
||||
<label>
|
||||
<span/>
|
||||
<button
|
||||
onClick={_ => createBoard(code)}
|
||||
>
|
||||
Crea
|
||||
</button>
|
||||
<span/>
|
||||
</label>
|
||||
</form>
|
||||
)
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import style from "@/app/page.module.css"
|
||||
import {default as React} from "react"
|
||||
|
||||
|
||||
export function RootHeader() {
|
||||
return (
|
||||
<header className={style.pageHeader}>
|
||||
<h1>
|
||||
Todoblue
|
||||
</h1>
|
||||
</header>
|
||||
)
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import {CreatePrivateBoardPanel} from "@/app/CreatePrivateBoardPanel"
|
||||
import {CreatePublicBoardPanel} from "@/app/CreatePublicBoardPanel"
|
||||
import style from "@/app/page.module.css"
|
||||
import {StarredBoardsPanel} from "@/app/StarredBoardsPanel"
|
||||
import {default as React} from "react"
|
||||
|
||||
|
||||
export function RootMain() {
|
||||
return (
|
||||
<main className={style.pageMain}>
|
||||
<div className={"chapter-2"}>
|
||||
<h2>
|
||||
Crea un nuovo tabellone
|
||||
</h2>
|
||||
<CreatePublicBoardPanel/>
|
||||
<CreatePrivateBoardPanel/>
|
||||
</div>
|
||||
<div className={"chapter-1"}>
|
||||
<h2>
|
||||
Usa un tabellone già esistente
|
||||
</h2>
|
||||
<StarredBoardsPanel/>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {useManagedStarred} from "@/app/StarContext"
|
||||
import cn from "classnames"
|
||||
import Link from "next/link"
|
||||
import {useEffect, useState} from "react"
|
||||
|
||||
|
||||
export function StarredBoardsPanel() {
|
||||
const [isClient, setIsClient] = useState<true | null>(null);
|
||||
const {starred} = useManagedStarred()
|
||||
|
||||
useEffect(() => setIsClient(true), [])
|
||||
|
||||
let content;
|
||||
if(!isClient) {
|
||||
content = <>
|
||||
<p>
|
||||
Sto recuperando i dati salvati sul tuo browser...
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
else {
|
||||
content = <>
|
||||
<p>
|
||||
Puoi stellare un tabellone cliccando sulla stellina una volta che ci sei dentro.
|
||||
</p>
|
||||
{starred.length > 0 ?
|
||||
<ul>
|
||||
{starred.map(s => <li key={s}><Link href={`/board/${s}`}><code>{s}</code></Link></li>)}
|
||||
</ul>
|
||||
:
|
||||
<p className={"fade"}>
|
||||
Non hai ancora stellato nessun tabellone.
|
||||
</p>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn({
|
||||
"panel": true,
|
||||
"box": true,
|
||||
"fade": !isClient,
|
||||
})}>
|
||||
<h3>
|
||||
Tabelloni stellati
|
||||
</h3>
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
}
|
80
todoblue/src/app/[lang]/(page)/CreatePrivateBoardPanel.tsx
Normal file
80
todoblue/src/app/[lang]/(page)/CreatePrivateBoardPanel.tsx
Normal file
|
@ -0,0 +1,80 @@
|
|||
"use client";
|
||||
|
||||
import {useTranslation} from "@/app/(i18n)/client"
|
||||
import {faLock} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import classNames from "classnames"
|
||||
import {useRouter} from "next/navigation"
|
||||
import {default as React, SyntheticEvent, useCallback, useEffect, useState} from "react"
|
||||
|
||||
export function CreatePrivateBoardPanel({lng}: {lng: string}) {
|
||||
const {t} = useTranslation(lng, "root")
|
||||
const router = useRouter();
|
||||
const [canCreate, setCanCreate] = useState<boolean | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setCanCreate(window.isSecureContext)
|
||||
}, [])
|
||||
|
||||
const createBoardValidated = useCallback((e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
const code = crypto.randomUUID().toString();
|
||||
console.debug("[CreatePrivateBoardPanel] Creating private board...");
|
||||
router.push(`/board/${code}`);
|
||||
}, [router])
|
||||
|
||||
let formContents;
|
||||
if(canCreate === null) {
|
||||
formContents = <>
|
||||
<p>
|
||||
{t("createPrivateBoardLoadingDescription")}
|
||||
<br/>
|
||||
<small>{t("createPrivateBoardLoadingSmall")}</small>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
else if(!canCreate) {
|
||||
formContents = <>
|
||||
<p>
|
||||
{t("createPrivateBoardUnavailableDescription")}
|
||||
<br/>
|
||||
<small>{t("createPrivateBoardUnavailableSmall")}</small>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
else {
|
||||
formContents = <>
|
||||
<p>
|
||||
{t("createPrivateBoardDescription")}
|
||||
<br/>
|
||||
<small>{t("createPrivateBoardSmall")}</small>
|
||||
</p>
|
||||
<label className={"float-bottom"}>
|
||||
<span/>
|
||||
<button onClick={createBoardValidated}>
|
||||
{t("createPrivateBoardSubmitText")}
|
||||
</button>
|
||||
<span/>
|
||||
</label>
|
||||
</>
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
className={classNames({
|
||||
"panel": true,
|
||||
"box": true,
|
||||
"form-flex": true,
|
||||
"red": canCreate === false,
|
||||
})}
|
||||
onSubmit={createBoardValidated}
|
||||
>
|
||||
<h3>
|
||||
<FontAwesomeIcon icon={faLock} size={"1x"}/>
|
||||
{" "}
|
||||
{t("createPrivateBoardTitle")}
|
||||
</h3>
|
||||
{formContents}
|
||||
</form>
|
||||
)
|
||||
}
|
68
todoblue/src/app/[lang]/(page)/CreatePublicBoardPanel.tsx
Normal file
68
todoblue/src/app/[lang]/(page)/CreatePublicBoardPanel.tsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
"use client";
|
||||
|
||||
import {useTranslation} from "@/app/(i18n)/client"
|
||||
import {useLowerKebabState} from "@/app/[lang]/useKebabState"
|
||||
import {faGlobe} from "@fortawesome/free-solid-svg-icons"
|
||||
import cn from "classnames"
|
||||
import {useRouter} from "next/navigation"
|
||||
import {default as React, SyntheticEvent, useCallback} from "react"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
|
||||
|
||||
export function CreatePublicBoardPanel({lng}: {lng: string}) {
|
||||
const {t} = useTranslation(lng, "root")
|
||||
const [code, setCode] = useLowerKebabState("")
|
||||
const router = useRouter();
|
||||
|
||||
const codeIsValid = code.length >= 1
|
||||
|
||||
const createBoardValidated = useCallback((e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
if(!codeIsValid) {
|
||||
console.debug("[CreatePublicBoardPanel] Code is not valid, refusing to create board.");
|
||||
return
|
||||
}
|
||||
console.debug("[CreatePublicBoardPanel] Creating public board with code:", code);
|
||||
router.push(`/board/${code}`);
|
||||
}, [code, codeIsValid, router])
|
||||
|
||||
return (
|
||||
<form
|
||||
className={"panel box form-flex"}
|
||||
onSubmit={createBoardValidated}
|
||||
>
|
||||
<h3>
|
||||
<FontAwesomeIcon icon={faGlobe}/>
|
||||
{" "}
|
||||
{t("createPublicBoardTitle")}
|
||||
</h3>
|
||||
<p>
|
||||
{t("createPublicBoardDescription")}
|
||||
<br/>
|
||||
<small>{t("createPublicBoardSmall")}</small>
|
||||
</p>
|
||||
<label className={"float-bottom"}>
|
||||
<span>
|
||||
{t("createPublicBoardCodeLeft")}
|
||||
</span>
|
||||
<input
|
||||
type={"text"}
|
||||
placeholder={t("createPublicBoardCodePlaceholder")}
|
||||
value={code}
|
||||
onChange={e => setCode(e.target.value)}
|
||||
/>
|
||||
<span/>
|
||||
</label>
|
||||
<label>
|
||||
<span/>
|
||||
<button
|
||||
onClick={createBoardValidated}
|
||||
className={cn({"fade": !codeIsValid})}
|
||||
>
|
||||
{t("createPublicBoardSubmitText")}
|
||||
</button>
|
||||
<span/>
|
||||
</label>
|
||||
</form>
|
||||
)
|
||||
}
|
68
todoblue/src/app/[lang]/(page)/KnownBoardsPanel.tsx
Normal file
68
todoblue/src/app/[lang]/(page)/KnownBoardsPanel.tsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
"use client";
|
||||
|
||||
import {useTranslation} from "@/app/(i18n)/client"
|
||||
import {useLowerKebabState} from "@/app/[lang]/useKebabState"
|
||||
import {faKey} from "@fortawesome/free-solid-svg-icons"
|
||||
import cn from "classnames"
|
||||
import {useRouter} from "next/navigation"
|
||||
import {default as React, SyntheticEvent, useCallback} from "react"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
|
||||
|
||||
export function KnownBoardsPanel({lng}: {lng: string}) {
|
||||
const {t} = useTranslation(lng, "root")
|
||||
const [code, setCode] = useLowerKebabState("")
|
||||
const router = useRouter();
|
||||
|
||||
const codeIsValid = code.length >= 1
|
||||
|
||||
const moveToBoardValidated = useCallback((e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
if(!codeIsValid) {
|
||||
console.debug("[KnownBoardsPanel] Code is not valid, refusing to move to board.");
|
||||
return
|
||||
}
|
||||
console.debug("[KnownBoardsPanel] Moving to board with given code...");
|
||||
router.push(`/board/${code}`);
|
||||
}, [code, codeIsValid, router])
|
||||
|
||||
return (
|
||||
<form
|
||||
className={"panel box form-flex"}
|
||||
onSubmit={moveToBoardValidated}
|
||||
>
|
||||
<h3>
|
||||
<FontAwesomeIcon icon={faKey}/>
|
||||
{" "}
|
||||
{t("existingKnownBoardsTitle")}
|
||||
</h3>
|
||||
<p>
|
||||
{t("existingKnownBoardsDescription")}
|
||||
<br/>
|
||||
<small>{t("existingKnownBoardsSmall")}</small>
|
||||
</p>
|
||||
<label className={"float-bottom"}>
|
||||
<span>
|
||||
{t("existingKnownBoardsCodeLeft")}
|
||||
</span>
|
||||
<input
|
||||
type={"text"}
|
||||
placeholder={t("existingKnownBoardsCodePlaceholder")}
|
||||
value={code}
|
||||
onChange={e => setCode(e.target.value)}
|
||||
/>
|
||||
<span/>
|
||||
</label>
|
||||
<label>
|
||||
<span/>
|
||||
<button
|
||||
onClick={moveToBoardValidated}
|
||||
className={cn({"fade": !codeIsValid})}
|
||||
>
|
||||
{t("existingKnownBoardsSubmitText")}
|
||||
</button>
|
||||
<span/>
|
||||
</label>
|
||||
</form>
|
||||
)
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
import style from "@/app/page.module.css"
|
||||
import {useTranslation} from "@/app/(i18n)/server"
|
||||
import style from "@/app/[lang]/page.module.css"
|
||||
import {default as React} from "react"
|
||||
|
||||
|
||||
export function RootFooter() {
|
||||
export async function RootFooter({lng}: {lng: string}) {
|
||||
const {t} = await useTranslation(lng, "root")
|
||||
|
||||
return (
|
||||
<footer className={style.pageFooter}>
|
||||
<p>
|
16
todoblue/src/app/[lang]/(page)/RootHeader.tsx
Normal file
16
todoblue/src/app/[lang]/(page)/RootHeader.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import {useTranslation} from "@/app/(i18n)/server"
|
||||
import style from "@/app/[lang]/page.module.css"
|
||||
import {default as React} from "react"
|
||||
|
||||
|
||||
export async function RootHeader({lng}: {lng: string}) {
|
||||
const {t} = await useTranslation(lng, "root")
|
||||
|
||||
return (
|
||||
<header className={style.pageHeader}>
|
||||
<h1>
|
||||
{t("title")}
|
||||
</h1>
|
||||
</header>
|
||||
)
|
||||
}
|
31
todoblue/src/app/[lang]/(page)/RootMain.tsx
Normal file
31
todoblue/src/app/[lang]/(page)/RootMain.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import {useTranslation} from "@/app/(i18n)/server"
|
||||
import {CreatePrivateBoardPanel} from "@/app/[lang]/(page)/CreatePrivateBoardPanel"
|
||||
import {CreatePublicBoardPanel} from "@/app/[lang]/(page)/CreatePublicBoardPanel"
|
||||
import {KnownBoardsPanel} from "@/app/[lang]/(page)/KnownBoardsPanel"
|
||||
import style from "@/app/[lang]/page.module.css"
|
||||
import {StarredBoardsPanel} from "@/app/[lang]/(page)/StarredBoardsPanel"
|
||||
import {default as React} from "react"
|
||||
|
||||
|
||||
export async function RootMain({lng}: {lng: string}) {
|
||||
const {t} = await useTranslation(lng, "root")
|
||||
|
||||
return (
|
||||
<main className={style.pageMain}>
|
||||
<div className={"chapter-2"}>
|
||||
<h2>
|
||||
{t("createBoardTitle")}
|
||||
</h2>
|
||||
<CreatePublicBoardPanel lng={lng}/>
|
||||
<CreatePrivateBoardPanel lng={lng}/>
|
||||
</div>
|
||||
<div className={"chapter-2"}>
|
||||
<h2>
|
||||
{t("existingBoardTitle")}
|
||||
</h2>
|
||||
<KnownBoardsPanel lng={lng}/>
|
||||
<StarredBoardsPanel lng={lng}/>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
76
todoblue/src/app/[lang]/(page)/StarredBoardsPanel.tsx
Normal file
76
todoblue/src/app/[lang]/(page)/StarredBoardsPanel.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
"use client";
|
||||
|
||||
import {useTranslation} from "@/app/(i18n)/client"
|
||||
import {useManagedStarred} from "@/app/[lang]/StarContext"
|
||||
import {faStar} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import cn from "classnames"
|
||||
import Link from "next/link"
|
||||
import {useEffect, useState} from "react"
|
||||
|
||||
|
||||
export function StarredBoardsPanel({lng}: {lng: string}) {
|
||||
const {t} = useTranslation(lng, "root")
|
||||
const [isClient, setIsClient] = useState<true | null>(null);
|
||||
const {starred} = useManagedStarred()
|
||||
|
||||
useEffect(() => setIsClient(true), [])
|
||||
|
||||
let content;
|
||||
if(!isClient) {
|
||||
content = <>
|
||||
<p>
|
||||
{t("existingStarredBoardsLoadingDescription")}
|
||||
<br/>
|
||||
<small>
|
||||
{t("existingStarredBoardsLoadingSmall")}
|
||||
</small>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
if(starred.length === 0) {
|
||||
content = <>
|
||||
<p>
|
||||
{t("existingStarredBoardsEmptyDescription")}
|
||||
<br/>
|
||||
<small>
|
||||
{t("existingStarredBoardsEmptySmall")}
|
||||
</small>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
else {
|
||||
content = <>
|
||||
<p>
|
||||
{t("existingStarredBoardsDescription")}
|
||||
<br/>
|
||||
<small>
|
||||
{t("existingStarredBoardsSmall")}
|
||||
</small>
|
||||
</p>
|
||||
{starred.length > 0 ?
|
||||
<ul>
|
||||
{starred.map(s => <li key={s}><Link href={`/board/${s}`}><code>{s}</code></Link></li>)}
|
||||
</ul>
|
||||
:
|
||||
<p className={"fade"}>
|
||||
Non hai ancora stellato nessun tabellone.
|
||||
</p>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn({
|
||||
"panel": true,
|
||||
"box": true,
|
||||
})}>
|
||||
<h3>
|
||||
<FontAwesomeIcon icon={faStar}/>
|
||||
{" "}
|
||||
{t("existingStarredBoardsTitle")}
|
||||
</h3>
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -33,7 +33,7 @@ export function StarredManager({children}: {children: ReactNode}) {
|
|||
}
|
||||
else {
|
||||
const result = [...prev]
|
||||
delete result[result.indexOf(value)]
|
||||
result.splice(result.indexOf(value), 1)
|
||||
return result
|
||||
}
|
||||
})
|
|
@ -1,8 +1,8 @@
|
|||
import {useManagedStarred} from "@/app/StarContext"
|
||||
import {useManagedStarred} from "@/app/[lang]/StarContext"
|
||||
import {useRouter} from "next/navigation"
|
||||
import {ReactNode, useCallback} from "react"
|
||||
import style from "./BoardHeader.module.css"
|
||||
import {useManagedBoard} from "@/app/board/[board]/BoardManager"
|
||||
import {useManagedBoard} from "@/app/[lang]/board/[board]/BoardManager"
|
||||
import {faArrowDownWideShort, faHouse, faPencil, faObjectGroup, faTableColumns, faStar as faStarSolid} from "@fortawesome/free-solid-svg-icons"
|
||||
import {faStar as faStarRegular} from "@fortawesome/free-regular-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
|
@ -1,6 +1,6 @@
|
|||
import {BoardMainIcon} from "@/app/board/[board]/BoardMainIcon"
|
||||
import {BoardMainTaskGroups} from "@/app/board/[board]/BoardMainTaskGroups"
|
||||
import {useManagedBoard} from "@/app/board/[board]/BoardManager"
|
||||
import {BoardMainIcon} from "@/app/[lang]/board/[board]/BoardMainIcon"
|
||||
import {BoardMainTaskGroups} from "@/app/[lang]/board/[board]/BoardMainTaskGroups"
|
||||
import {useManagedBoard} from "@/app/[lang]/board/[board]/BoardManager"
|
||||
import {faLink, faLinkSlash, faGear} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import {useManagedBoard} from "@/app/board/[board]/BoardManager"
|
||||
import {TaskDisplay} from "@/app/board/[board]/TaskDisplay"
|
||||
import {TaskGroup} from "@/app/board/[board]/useBoardTaskArranger"
|
||||
import {useManagedBoard} from "@/app/[lang]/board/[board]/BoardManager"
|
||||
import {TaskDisplay} from "@/app/[lang]/board/[board]/TaskDisplay"
|
||||
import {TaskGroup} from "@/app/[lang]/board/[board]/useBoardTaskArranger"
|
||||
import cn from "classnames"
|
||||
import style from "./BoardMainTaskGroups.module.css"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {useBoard, UseBoardReturns} from "@/app/board/[board]/useBoard"
|
||||
import {useBoard, UseBoardReturns} from "@/app/[lang]/board/[board]/useBoard"
|
||||
import {createContext, ReactNode, useContext} from "react"
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {useManagedBoard} from "@/app/board/[board]/BoardManager"
|
||||
import {useManagedBoard} from "@/app/[lang]/board/[board]/BoardManager"
|
||||
import {FormEvent, useCallback} from "react"
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {useManagedBoard} from "@/app/board/[board]/BoardManager"
|
||||
import {useManagedBoard} from "@/app/[lang]/board/[board]/BoardManager"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import cn from "classnames"
|
||||
import {FormEvent, useCallback} from "react"
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import {TaskIconEl} from "@/app/board/[board]/TaskIconEl"
|
||||
import {TaskWithId} from "@/app/board/[board]/Types"
|
||||
import {useManagedBoard} from "@/app/board/[board]/BoardManager"
|
||||
import {TaskIconEl} from "@/app/[lang]/board/[board]/TaskIconEl"
|
||||
import {TaskWithId} from "@/app/[lang]/board/[board]/Types"
|
||||
import {useManagedBoard} from "@/app/[lang]/board/[board]/BoardManager"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import {useCallback, useState, MouseEvent} from "react"
|
||||
import {faTrashCanArrowUp} from "@fortawesome/free-solid-svg-icons"
|
|
@ -1,4 +1,4 @@
|
|||
import {TaskIcon, TaskStatus} from "@/app/board/[board]/Types"
|
||||
import {TaskIcon, TaskStatus} from "@/app/[lang]/board/[board]/Types"
|
||||
import {SizeProp} from "@fortawesome/fontawesome-svg-core"
|
||||
import {
|
||||
faUser as faUserSolid,
|
|
@ -1,6 +1,6 @@
|
|||
import {TaskIconEl} from "@/app/board/[board]/TaskIconEl"
|
||||
import {TaskIcon, TaskImportance, TaskPriority, TaskStatus, TaskWithId} from "@/app/board/[board]/Types"
|
||||
import {TaskGroupTitleComponent, TaskGroupComparer, TaskGroup, TaskCategorizer} from "@/app/board/[board]/useBoardTaskArranger"
|
||||
import {TaskIconEl} from "@/app/[lang]/board/[board]/TaskIconEl"
|
||||
import {TaskIcon, TaskImportance, TaskPriority, TaskStatus, TaskWithId} from "@/app/[lang]/board/[board]/Types"
|
||||
import {TaskGroupTitleComponent, TaskGroupComparer, TaskGroup, TaskCategorizer} from "@/app/[lang]/board/[board]/useBoardTaskArranger"
|
||||
import {ReactNode} from "react"
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import {TaskWithId} from "@/app/board/[board]/Types"
|
||||
import {TaskSortingFunction} from "@/app/board/[board]/useBoardTaskArranger"
|
||||
import {TaskWithId} from "@/app/[lang]/board/[board]/Types"
|
||||
import {TaskSortingFunction} from "@/app/[lang]/board/[board]/useBoardTaskArranger"
|
||||
|
||||
/**
|
||||
* **Mapping** from {@link TaskImportance} to a {@link number}.
|
|
@ -1,9 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import {BoardMain} from "@/app/board/[board]/BoardMain"
|
||||
import {BoardManager} from "@/app/board/[board]/BoardManager"
|
||||
import {BoardHeader} from "@/app/board/[board]/BoardHeader"
|
||||
import {BoardTaskEditor} from "@/app/board/[board]/BoardTaskEditor"
|
||||
import {BoardMain} from "@/app/[lang]/board/[board]/BoardMain"
|
||||
import {BoardManager} from "@/app/[lang]/board/[board]/BoardManager"
|
||||
import {BoardHeader} from "@/app/[lang]/board/[board]/BoardHeader"
|
||||
import {BoardTaskEditor} from "@/app/[lang]/board/[board]/BoardTaskEditor"
|
||||
import style from "./page.module.css"
|
||||
|
||||
export default function Page({params: {board}}: {params: {board: string}}) {
|
|
@ -1,13 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import {TASK_GROUPERS} from "@/app/board/[board]/doTaskGrouping"
|
||||
import {TASK_SORTERS} from "@/app/board/[board]/doTaskSorting"
|
||||
import {BoardAction, Task} from "@/app/board/[board]/Types"
|
||||
import {useBoardTaskEditor} from "@/app/board/[board]/useBoardTaskEditor"
|
||||
import {useBoardWs} from "@/app/board/[board]/useBoardWs"
|
||||
import {TaskGroup, useBoardTaskArranger} from "@/app/board/[board]/useBoardTaskArranger"
|
||||
import {useBoardTitleEditor} from "@/app/board/[board]/useBoardTitleEditor"
|
||||
import {useCycleState} from "@/app/useCycleState"
|
||||
import {TASK_GROUPERS} from "@/app/[lang]/board/[board]/doTaskGrouping"
|
||||
import {TASK_SORTERS} from "@/app/[lang]/board/[board]/doTaskSorting"
|
||||
import {BoardAction, Task} from "@/app/[lang]/board/[board]/Types"
|
||||
import {useBoardTaskEditor} from "@/app/[lang]/board/[board]/useBoardTaskEditor"
|
||||
import {useBoardWs} from "@/app/[lang]/board/[board]/useBoardWs"
|
||||
import {TaskGroup, useBoardTaskArranger} from "@/app/[lang]/board/[board]/useBoardTaskArranger"
|
||||
import {useBoardTitleEditor} from "@/app/[lang]/board/[board]/useBoardTitleEditor"
|
||||
import {useCycleState} from "@/app/[lang]/useCycleState"
|
||||
import {Dispatch, SetStateAction, useState} from "react"
|
||||
|
||||
export interface UseBoardReturns {
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import {BoardAction, Task, TaskBoardAction, TitleBoardAction} from "@/app/board/[board]/Types"
|
||||
import {BoardAction, Task, TaskBoardAction, TitleBoardAction} from "@/app/[lang]/board/[board]/Types"
|
||||
import {Reducer, useReducer} from "react"
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import {Task, TaskWithId} from "@/app/board/[board]/Types"
|
||||
import {Task, TaskWithId} from "@/app/[lang]/board/[board]/Types"
|
||||
import {ReactNode, useMemo} from "react"
|
||||
|
||||
export type TaskGroup = {
|
|
@ -1,4 +1,4 @@
|
|||
import {Task, TaskIcon, TaskImportance, TaskPriority} from "@/app/board/[board]/Types"
|
||||
import {Task, TaskIcon, TaskImportance, TaskPriority} from "@/app/[lang]/board/[board]/Types"
|
||||
import {useCallback, useMemo, useState} from "react"
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import {BoardAction} from "@/app/board/[board]/Types"
|
||||
import {BoardAction} from "@/app/[lang]/board/[board]/Types"
|
||||
import {useCallback, useState} from "react"
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import {BoardAction} from "@/app/board/[board]/Types"
|
||||
import {useBoardState} from "@/app/board/[board]/useBoardState"
|
||||
import {useWsBaseURL} from "@/app/useWsBaseURL"
|
||||
import {BoardAction} from "@/app/[lang]/board/[board]/Types"
|
||||
import {useBoardState} from "@/app/[lang]/board/[board]/useBoardState"
|
||||
import {useWsBaseURL} from "@/app/[lang]/useWsBaseURL"
|
||||
import {useCallback, useMemo} from "react"
|
||||
import {useWs, WebSocketHandlerParams} from "@/app/useWs"
|
||||
import {useWs, WebSocketHandlerParams} from "@/app/[lang]/useWs"
|
||||
|
||||
|
||||
export function useBoardWs(name: string) {
|
|
@ -1,8 +1,8 @@
|
|||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
import "./layout.css";
|
||||
import {AppBody} from "@/app/AppBody"
|
||||
import {StarredManager} from "@/app/StarContext"
|
||||
import {AppBody} from "@/app/[lang]/AppBody"
|
||||
import {StarredManager} from "@/app/[lang]/StarContext"
|
||||
import type {Metadata as NextMetadata} from "next"
|
||||
import {default as React, ReactNode} from "react"
|
||||
|
17
todoblue/src/app/[lang]/page.tsx
Normal file
17
todoblue/src/app/[lang]/page.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {RootFooter} from "@/app/[lang]/(page)/RootFooter"
|
||||
import {RootHeader} from "@/app/[lang]/(page)/RootHeader"
|
||||
import {RootMain} from "@/app/[lang]/(page)/RootMain"
|
||||
import {default as React} from "react";
|
||||
import style from "./page.module.css"
|
||||
|
||||
|
||||
export default async function page({params: {lng}}: {params: {lng: string}}) {
|
||||
return (
|
||||
<div className={style.pageRoot}>
|
||||
<RootHeader lng={lng}/>
|
||||
<RootMain lng={lng}/>
|
||||
<RootFooter lng={lng}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,8 +2,16 @@
|
|||
|
||||
import {useCallback, useState} from "react"
|
||||
|
||||
|
||||
/**
|
||||
* **Regex** identifying the characters to be replaced with dashes in {@link useAnyKebabState}, {@link useLowerKebabState}, and {@link useUpperKebabState}.
|
||||
*/
|
||||
const KEBABIFIER = /[^a-zA-Z0-9-]/g
|
||||
|
||||
/**
|
||||
* **Hook** similar to {@link useState}, but replace non-alphanumeric characters with dashes.
|
||||
* @param initial
|
||||
*/
|
||||
export function useAnyKebabState(initial: string): [string, (inputString: string) => void] {
|
||||
const [state, setInnerState] = useState<string>(initial);
|
||||
|
||||
|
@ -15,6 +23,10 @@ export function useAnyKebabState(initial: string): [string, (inputString: string
|
|||
return [state, setState]
|
||||
}
|
||||
|
||||
/**
|
||||
* **Hook** similar to {@link useState}, but replaces non-alphanumeric characters with dashes and converts to lowercase the whole string.
|
||||
* @param initial
|
||||
*/
|
||||
export function useLowerKebabState(initial: string): [string, (inputString: string) => void] {
|
||||
const [state, setInnerState] = useState<string>(initial);
|
||||
|
||||
|
@ -26,6 +38,10 @@ export function useLowerKebabState(initial: string): [string, (inputString: stri
|
|||
return [state, setState]
|
||||
}
|
||||
|
||||
/**
|
||||
* **Hook** similar to {@link useState}, but replaces non-alphanumeric characters with dashes and converts to uppercase the whole string.
|
||||
* @param initial
|
||||
*/
|
||||
export function useUpperKebabState(initial: string): [string, (inputString: string) => void] {
|
||||
const [state, setInnerState] = useState<string>(initial);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {useHttpBaseURL} from "@/app/useHttpBaseURL"
|
||||
import {useHttpBaseURL} from "@/app/[lang]/useHttpBaseURL"
|
||||
|
||||
|
||||
/**
|
|
@ -1,17 +0,0 @@
|
|||
import {RootFooter} from "@/app/RootFooter"
|
||||
import {RootHeader} from "@/app/RootHeader"
|
||||
import {RootMain} from "@/app/RootMain"
|
||||
import {default as React} from "react";
|
||||
import style from "./page.module.css"
|
||||
|
||||
|
||||
export default function page() {
|
||||
return (
|
||||
<div className={style.pageRoot}>
|
||||
<RootHeader/>
|
||||
<RootMain/>
|
||||
<RootFooter/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
23
todoblue/src/middleware.ts
Normal file
23
todoblue/src/middleware.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import {AVAILABLE_LOCALES} from "@/app/(i18n)/(locales)"
|
||||
import Negotiator from 'negotiator'
|
||||
import {NextRequest, NextResponse} from "next/server"
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
// https://nextjs.org/docs/app/building-your-application/routing/internationalization
|
||||
const pathname = request.nextUrl.pathname
|
||||
const pathnameIsMissingLocale = AVAILABLE_LOCALES.every(
|
||||
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
|
||||
)
|
||||
if(!pathnameIsMissingLocale) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
const negotiator = new Negotiator(request as any)
|
||||
const bestLocale = negotiator.language(AVAILABLE_LOCALES)
|
||||
return NextResponse.rewrite(`${request.nextUrl.protocol}//${request.nextUrl.host}/${bestLocale}${pathname}`)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/((?!api|_next|manifest.json|logo-wbg-[0-9]*.png|favicon.ico).*)',
|
||||
]
|
||||
}
|
|
@ -2,6 +2,13 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/runtime@^7.21.5", "@babel/runtime@^7.22.5":
|
||||
version "7.22.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682"
|
||||
integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@fortawesome/fontawesome-common-types@6.4.0":
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz#88da2b70d6ca18aaa6ed3687832e11f39e80624b"
|
||||
|
@ -97,6 +104,11 @@
|
|||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@types/negotiator@^0.6.1":
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/negotiator/-/negotiator-0.6.1.tgz#4c75543f6ef87f427f4705e731a933595b7397f5"
|
||||
integrity sha512-c4mvXFByghezQ/eVGN5HvH/jI63vm3B7FiE81BUzDAWmuiohRecCO6ddU60dfq29oKUMiQujsoB2h0JQC7JHKA==
|
||||
|
||||
"@types/node@20.4.5":
|
||||
version "20.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.5.tgz#9dc0a5cb1ccce4f7a731660935ab70b9c00a5d69"
|
||||
|
@ -174,6 +186,20 @@ graceful-fs@^4.1.2:
|
|||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
||||
i18next-resources-to-backend@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/i18next-resources-to-backend/-/i18next-resources-to-backend-1.1.4.tgz#d139ca0cacc270dcc90b7926e192f4cd5aa4db60"
|
||||
integrity sha512-hMyr9AOmIea17AOaVe1srNxK/l3mbk81P7Uf3fdcjlw3ehZy3UNTd0OP3EEi6yu4J02kf9jzhCcjokz6AFlEOg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.21.5"
|
||||
|
||||
i18next@^23.4.2:
|
||||
version "23.4.2"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.4.2.tgz#e68108be82287114e027afc5402bb7830d7f45c9"
|
||||
integrity sha512-hkVPHKFLtn9iewdqHDiU+MGVIBk+bVFn5usw7CIeCn/SBcVKGTItGdjNPm2B8Lnz42CeHUlnSOTgsr5vbITjhA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.22.5"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
|
@ -191,6 +217,11 @@ nanoid@^3.3.4:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||
|
||||
negotiator@^0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||
|
||||
next@13.4.12:
|
||||
version "13.4.12"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-13.4.12.tgz#809b21ea0aabbe88ced53252c88c4a5bd5af95df"
|
||||
|
@ -263,6 +294,11 @@ react@18.2.0:
|
|||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
regenerator-runtime@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
|
||||
integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
|
||||
|
||||
scheduler@^0.23.0:
|
||||
version "0.23.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
|
||||
|
|
Loading…
Reference in a new issue