i18n by next-intl
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
export default function DocsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
||||
<div className="inline-block max-w-lg text-center justify-center">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { title } from "@/components/primitives";
|
||||
|
||||
export default function DocsPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className={title()}>Docs</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,15 @@
|
||||
import {
|
||||
Navbar as NextUINavbar,
|
||||
NavbarContent,
|
||||
NavbarMenu,
|
||||
NavbarMenuToggle,
|
||||
NavbarBrand,
|
||||
NavbarItem,
|
||||
NavbarMenuItem,
|
||||
Kbd,
|
||||
Link,
|
||||
Link as NextUiLink,
|
||||
Input,
|
||||
link as linkStyles,
|
||||
} from "@nextui-org/react";
|
||||
|
||||
import { Link } from "@/src/navigation";
|
||||
import { siteConfig } from "@/config/site";
|
||||
import NextLink from "next/link";
|
||||
|
||||
import { ThemeSwitch } from "@/components/theme-switch";
|
||||
import { GithubIcon, SearchIcon } from "@/components/icons";
|
||||
|
||||
@@ -46,7 +41,7 @@ export const Navbar = () => {
|
||||
<NextUINavbar maxWidth="xl" position="sticky" className="bg-inherit">
|
||||
<NavbarContent className="basis-1/5 sm:basis-full" justify="start">
|
||||
<NavbarBrand as="li" className="gap-3 max-w-fit">
|
||||
<NextLink className="flex justify-start items-center gap-1" href="/">
|
||||
<Link className="flex justify-start items-center gap-1" href="/">
|
||||
<Image
|
||||
src="/favicon/android-chrome-192x192.png"
|
||||
width={32}
|
||||
@@ -54,17 +49,17 @@ export const Navbar = () => {
|
||||
alt="Logo"
|
||||
/>
|
||||
<p className="font-bold text-inherit">Test Case Manager</p>
|
||||
</NextLink>
|
||||
</Link>
|
||||
</NavbarBrand>
|
||||
<ul className="hidden lg:flex gap-4 justify-start ml-2">
|
||||
{siteConfig.navItems.map((item) => (
|
||||
<NavbarItem key={item.href}>
|
||||
<NextLink
|
||||
<Link
|
||||
className="data-[active=true]:text-primary data-[active=true]:font-medium"
|
||||
href={item.href}
|
||||
>
|
||||
{item.label}
|
||||
</NextLink>
|
||||
</Link>
|
||||
</NavbarItem>
|
||||
))}
|
||||
</ul>
|
||||
@@ -75,62 +70,29 @@ export const Navbar = () => {
|
||||
justify="end"
|
||||
>
|
||||
<NavbarItem className="hidden sm:flex gap-2">
|
||||
{/* <Link isExternal href={siteConfig.links.twitter} aria-label="Twitter">
|
||||
<TwitterIcon className="text-default-500" />
|
||||
</Link> */}
|
||||
{/* <Link isExternal href={siteConfig.links.discord} aria-label="Discord">
|
||||
<DiscordIcon className="text-default-500" />
|
||||
</Link> */}
|
||||
<Link isExternal href={siteConfig.links.github} aria-label="Github">
|
||||
<NextUiLink
|
||||
isExternal
|
||||
href={siteConfig.links.github}
|
||||
aria-label="Github"
|
||||
>
|
||||
<GithubIcon className="text-default-500" />
|
||||
</Link>
|
||||
</NextUiLink>
|
||||
<ThemeSwitch />
|
||||
</NavbarItem>
|
||||
<NavbarItem className="hidden lg:flex">{searchInput}</NavbarItem>
|
||||
{/* <NavbarItem className="hidden md:flex">
|
||||
<Button
|
||||
isExternal
|
||||
as={Link}
|
||||
className="text-sm font-normal text-default-600 bg-default-100"
|
||||
href={siteConfig.links.sponsor}
|
||||
startContent={<HeartFilledIcon className="text-danger" />}
|
||||
variant="flat"
|
||||
>
|
||||
Sponsor
|
||||
</Button>
|
||||
</NavbarItem> */}
|
||||
</NavbarContent>
|
||||
|
||||
<NavbarContent className="sm:hidden basis-1 pl-4" justify="end">
|
||||
<Link isExternal href={siteConfig.links.github} aria-label="Github">
|
||||
<NextUiLink
|
||||
isExternal
|
||||
href={siteConfig.links.github}
|
||||
aria-label="Github"
|
||||
>
|
||||
<GithubIcon className="text-default-500" />
|
||||
</Link>
|
||||
</NextUiLink>
|
||||
<ThemeSwitch />
|
||||
<NavbarMenuToggle />
|
||||
</NavbarContent>
|
||||
|
||||
<NavbarMenu>
|
||||
{searchInput}
|
||||
<div className="mx-4 mt-2 flex flex-col gap-2">
|
||||
{siteConfig.navMenuItems.map((item, index) => (
|
||||
<NavbarMenuItem key={`${item}-${index}`}>
|
||||
<Link
|
||||
color={
|
||||
index === 2
|
||||
? "primary"
|
||||
: index === siteConfig.navMenuItems.length - 1
|
||||
? "danger"
|
||||
: "foreground"
|
||||
}
|
||||
href="#"
|
||||
size="lg"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</NavbarMenuItem>
|
||||
))}
|
||||
</div>
|
||||
</NavbarMenu>
|
||||
</NextUINavbar>
|
||||
);
|
||||
};
|
||||
|
||||
9
frontend/messages/en.json
Normal file
9
frontend/messages/en.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Index": {
|
||||
"title": "Hello world!"
|
||||
},
|
||||
"ProjectPage": {
|
||||
"projects": "Projects",
|
||||
"newProject": "New Project"
|
||||
}
|
||||
}
|
||||
9
frontend/messages/ja.json
Normal file
9
frontend/messages/ja.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Index": {
|
||||
"title": "はろー"
|
||||
},
|
||||
"ProjectPage": {
|
||||
"projects": "プロジェクト",
|
||||
"newProject": "プロジェクトの作成"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
const createNextIntlPlugin = require("next-intl/plugin");
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
typescript: {
|
||||
@@ -7,6 +10,6 @@ const nextConfig = {
|
||||
// !! WARN !!
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = nextConfig
|
||||
module.exports = withNextIntl(nextConfig);
|
||||
|
||||
104
frontend/package-lock.json
generated
104
frontend/package-lock.json
generated
@@ -24,6 +24,7 @@
|
||||
"intl-messageformat": "^10.5.0",
|
||||
"lucide-react": "^0.372.0",
|
||||
"next": "14.0.2",
|
||||
"next-intl": "^3.12.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss": "8.4.31",
|
||||
"react": "18.2.0",
|
||||
@@ -5204,6 +5205,14 @@
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "14.0.2",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-14.0.2.tgz",
|
||||
@@ -5249,6 +5258,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.12.0.tgz",
|
||||
"integrity": "sha512-N3DHT6ce6K4VHVA3y2p3U7wfBx4c31qEgQSTFCFJuNnE7XYzy49O576ewEz7/k2YaB/U5bfxaWWaMMkskofwoQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/amannn"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.2.32",
|
||||
"negotiator": "^0.6.3",
|
||||
"use-intl": "^3.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.32",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
|
||||
"integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next-themes": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",
|
||||
@@ -6683,6 +6720,73 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.12.0.tgz",
|
||||
"integrity": "sha512-tTJBSaxpVF1ZHqJ5+1JOaBsPmvBPscXHR0soMNQFWIURZspOueLaMXx1XHNdBv9KZGwepBju5aWXkJ0PM6ztXg==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "^1.11.4",
|
||||
"intl-messageformat": "^9.3.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl/node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
|
||||
"integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "0.2.25",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl/node_modules/@formatjs/fast-memoize": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
|
||||
"integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl/node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz",
|
||||
"integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"@formatjs/icu-skeleton-parser": "1.3.6",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl/node_modules/@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz",
|
||||
"integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.11.4",
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl/node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.25",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
|
||||
"integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl/node_modules/intl-messageformat": {
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz",
|
||||
"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.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-isomorphic-layout-effect": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"intl-messageformat": "^10.5.0",
|
||||
"lucide-react": "^0.372.0",
|
||||
"next": "14.0.2",
|
||||
"next-intl": "^3.12.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss": "8.4.31",
|
||||
"react": "18.2.0",
|
||||
|
||||
@@ -25,11 +25,13 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
params: {locale}
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
params: {locale: string};
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<html lang={locale} suppressHydrationWarning>
|
||||
<head />
|
||||
<body
|
||||
className={clsx(
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Link, button as buttonStyles } from "@nextui-org/react";
|
||||
import { button as buttonStyles } from "@nextui-org/react";
|
||||
import { title, subtitle } from "@/components/primitives";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Link } from "@/src/navigation";
|
||||
|
||||
export default function Home() {
|
||||
const t = useTranslations("Index");
|
||||
|
||||
return (
|
||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
||||
<div className="inline-block max-w-lg text-center justify-center">
|
||||
@@ -13,6 +17,8 @@ export default function Home() {
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<h1>{t("title")}</h1>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Link
|
||||
href="/projects"
|
||||
@@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
// "use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@nextui-org/react";
|
||||
import { Plus } from "lucide-react";
|
||||
@@ -11,8 +11,10 @@ import {
|
||||
updateProject,
|
||||
deleteProject,
|
||||
} from "./projectsControl";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function ProjectsPage() {
|
||||
const t = useTranslations("ProjectsPage");
|
||||
const [projects, setProjects] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -78,7 +80,7 @@ export default function ProjectsPage() {
|
||||
return (
|
||||
<div className="container mx-auto max-w-3xl pt-16 px-6 flex-grow">
|
||||
<div className="w-full p-3 flex items-center justify-between">
|
||||
<h3 className="font-bold">Projects</h3>
|
||||
<h3 className="font-bold">{t("project.project")}</h3>
|
||||
<div>
|
||||
<Button
|
||||
startContent={<Plus size={16} />}
|
||||
@@ -86,7 +88,7 @@ export default function ProjectsPage() {
|
||||
color="primary"
|
||||
onClick={openDialogForCreate}
|
||||
>
|
||||
New Project
|
||||
{t("newProject")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Image, Button, Tooltip, Card, CardBody } from "@nextui-org/react";
|
||||
import { AttachmentType } from "../../../../../../../types/case";
|
||||
import { AttachmentType } from "../../../../../../../../types/case";
|
||||
import { Trash, ArrowDownToLine } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Textarea, Button, Tooltip } from "@nextui-org/react";
|
||||
import { StepType } from "../../../../../../../types/case";
|
||||
import { StepType } from "../../../../../../../../types/case";
|
||||
import { Plus, Trash } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
12
frontend/src/i18n.ts
Normal file
12
frontend/src/i18n.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
|
||||
const locales = ["en", "ja"];
|
||||
|
||||
export default getRequestConfig(async ({ locale }) => {
|
||||
if (!locales.includes(locale as any)) notFound();
|
||||
|
||||
return {
|
||||
messages: (await import(`../messages/${locale}.json`)).default,
|
||||
};
|
||||
});
|
||||
12
frontend/src/middleware.ts
Normal file
12
frontend/src/middleware.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import createMiddleware from "next-intl/middleware";
|
||||
import { locales, localePrefix } from "./navigation";
|
||||
|
||||
export default createMiddleware({
|
||||
defaultLocale: "en",
|
||||
localePrefix,
|
||||
locales,
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ["/", "/(ja|en)/:path*"],
|
||||
};
|
||||
7
frontend/src/navigation.ts
Normal file
7
frontend/src/navigation.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createSharedPathnamesNavigation } from "next-intl/navigation";
|
||||
|
||||
export const locales = ["en", "ja"] as const;
|
||||
export const localePrefix = "always";
|
||||
|
||||
export const { Link, redirect, usePathname, useRouter } =
|
||||
createSharedPathnamesNavigation({ locales, localePrefix });
|
||||
Reference in New Issue
Block a user