diff --git a/web/app/components/NavLocaleLink.tsx b/web/app/components/NavLocaleLink.tsx index b3fb7357..320b7da4 100644 --- a/web/app/components/NavLocaleLink.tsx +++ b/web/app/components/NavLocaleLink.tsx @@ -1,24 +1,21 @@ import { NavLink, type NavLinkProps, useParams } from "@remix-run/react"; +import { forwardRef } from "react"; -// NavLinkProps を継承 +// NavLinkProps を継承しつつ、to を上書き type NavLocaleLinkProps = Omit & { to: string; }; -export function NavLocaleLink({ - to, - className, - children, - ...rest -}: NavLocaleLinkProps) { - const { locale } = useParams(); +export const NavLocaleLink = forwardRef( + function NavLocaleLink({ to, className, children, ...rest }, ref) { + const { locale } = useParams(); + const normalized = to.startsWith("/") ? to.slice(1) : to; + const path = `/${locale}/${normalized}`; - const normalized = to.startsWith("/") ? to.slice(1) : to; - const path = `/${locale}/${normalized}`; - - return ( - - {children} - - ); -} + return ( + + {children} + + ); + }, +); diff --git a/web/app/root.tsx b/web/app/root.tsx index c5636085..9481ee3a 100644 --- a/web/app/root.tsx +++ b/web/app/root.tsx @@ -162,7 +162,7 @@ function CommonLayout({ children, isSpecialLayout = true, }: { children: React.ReactNode; isSpecialLayout: boolean }) { - const { currentUser } = useLoaderData(); + const { currentUser, locale } = useLoaderData(); if (isSpecialLayout) { return <>{children}; @@ -170,7 +170,7 @@ function CommonLayout({ return ( <> -
+
{children}
diff --git a/web/app/routes/$locale+/user.$userName+/page+/$slug+/edit/_edit.tsx b/web/app/routes/$locale+/user.$userName+/page+/$slug+/edit/_edit.tsx index aaf849a1..0dfbd377 100644 --- a/web/app/routes/$locale+/user.$userName+/page+/$slug+/edit/_edit.tsx +++ b/web/app/routes/$locale+/user.$userName+/page+/$slug+/edit/_edit.tsx @@ -15,7 +15,10 @@ import { useCallback, useEffect } from "react"; import TextareaAutosize from "react-textarea-autosize"; import { useDebouncedCallback } from "use-debounce"; import { z } from "zod"; +import { getTranslateUserQueue } from "~/features/translate/translate-user-queue"; import { authenticator } from "~/utils/auth.server"; +import { createUserAITranslationInfo } from "../functions/mutations.server"; +import { fetchPageWithSourceTexts } from "../functions/queries.server"; import { EditHeader } from "./components/EditHeader"; import { TagInput } from "./components/TagInput"; import { Editor } from "./components/editor/Editor"; @@ -105,6 +108,45 @@ export async function action({ request, params }: ActionFunctionArgs) { if (tags) { await upsertTags(tags, page.id); } + + if (isPublishedBool) { + const geminiApiKey = process.env.GEMINI_API_KEY; + if (!geminiApiKey) { + throw new Error("GEMINI_API_KEY is not set"); + } + let locale = params.locale; + if (locale === "en") { + locale = "ja"; + } else { + locale = "en"; + } + const userAITranslationInfo = await createUserAITranslationInfo( + currentUser.id, + page.id, + "gemini-1.5-flash-latest", + locale, + ); + + const pageWithSourceTexts = await fetchPageWithSourceTexts(page.id); + if (!pageWithSourceTexts) { + throw new Error("Page not found"); + } + const numberedElements = pageWithSourceTexts.sourceTexts.map((item) => ({ + number: item.number, + text: item.text, + })); + const queue = getTranslateUserQueue(currentUser.id); + await queue.add(`translate-${currentUser.id}`, { + userAITranslationInfoId: userAITranslationInfo.id, + geminiApiKey: geminiApiKey, + aiModel: "gemini-1.5-flash-latest", + userId: currentUser.id, + pageId: page.id, + locale: locale, + title: title, + numberedElements: numberedElements, + }); + } return null; } diff --git a/web/app/routes/$locale+/user.$userName+/page+/$slug+/index.tsx b/web/app/routes/$locale+/user.$userName+/page+/$slug+/index.tsx index 7a1fdc72..67d9c738 100644 --- a/web/app/routes/$locale+/user.$userName+/page+/$slug+/index.tsx +++ b/web/app/routes/$locale+/user.$userName+/page+/$slug+/index.tsx @@ -22,6 +22,7 @@ import { import { actionSchema } from "./types"; import { getBestTranslation } from "./utils/getBestTranslation"; import { stripHtmlTags } from "./utils/stripHtmlTags"; + export const meta: MetaFunction = ({ data }) => { if (!data) { return [{ title: "Page Not Found" }]; diff --git a/web/app/routes/resources+/components/NewPageButton.tsx b/web/app/routes/resources+/components/NewPageButton.tsx index 1349b234..261bc00d 100644 --- a/web/app/routes/resources+/components/NewPageButton.tsx +++ b/web/app/routes/resources+/components/NewPageButton.tsx @@ -16,9 +16,10 @@ const generateSlug = (length = 8): string => { }; interface NewPageButtonProps { userName: string; + locale: string; } -export const NewPageButton = ({ userName }: NewPageButtonProps) => { +export const NewPageButton = ({ userName, locale }: NewPageButtonProps) => { const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(false); @@ -29,7 +30,7 @@ export const NewPageButton = ({ userName }: NewPageButtonProps) => { const handleNewPage = () => { setIsLoading(true); const newSlug = generateSlug(); - navigate(`/user/${userName}/page/${newSlug}/edit`); + navigate(`/${locale}/user/${userName}/page/${newSlug}/edit`); }; return ( diff --git a/web/app/routes/resources+/header.tsx b/web/app/routes/resources+/header.tsx index b4f862b1..8c94e894 100644 --- a/web/app/routes/resources+/header.tsx +++ b/web/app/routes/resources+/header.tsx @@ -11,6 +11,7 @@ import { NewPageButton } from "./components/NewPageButton"; interface HeaderProps { currentUser: SanitizedUser | null; + locale: string; } export async function action({ request }: ActionFunctionArgs) { @@ -34,7 +35,7 @@ export async function action({ request }: ActionFunctionArgs) { return data({ error: "Invalid intent" }, { status: 400 }); } -export function Header({ currentUser }: HeaderProps) { +export function Header({ currentUser, locale }: HeaderProps) { const rightExtra = ( <> {currentUser ? ( - + ) : ( )}