Skip to content

Commit

Permalink
タイトルも翻訳できるように (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
ttizze authored Aug 12, 2024
2 parents b6789fe + 78073b2 commit 786db35
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { JSDOM } from "jsdom";

export function extractNumberedElements(
content: string,
title: string,
): Array<{ number: number; text: string }> {
const doc = new JSDOM(content);
const numberedElements: Array<{ number: number; text: string }> = [];
const numberedElements: Array<{ number: number; text: string }> = [
{ number: 0, text: title },
];
// <br>のみを改行とする
doc.window.document.body.innerHTML = doc.window.document.body.innerHTML
.replace(/\n/g, "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ import type { SourceTextWithTranslations } from "../types";
import { Translation } from "./Translation";

interface ContentWithTranslationsProps {
title: string;
content: string;
sourceTextWithTranslations: SourceTextWithTranslations[];
userId: number | null;
}

export const ContentWithTranslations = memo(function ContentWithTranslations({
title,
content,
sourceTextWithTranslations,
userId,
}: ContentWithTranslationsProps) {
const titleTranslation = useMemo(() => {
return sourceTextWithTranslations.find((info) => info.number === 0);
}, [sourceTextWithTranslations]);

const parsedContent = useMemo(() => {
if (typeof window === "undefined") {
return null;
Expand Down Expand Up @@ -63,5 +69,19 @@ export const ContentWithTranslations = memo(function ContentWithTranslations({
return <div>Loading...</div>;
}

return <>{parsedContent}</>;
return (
<>
<h1>
{title}
{titleTranslation && (
<Translation
translationsWithVotes={titleTranslation.translationsWithVotes}
userId={userId}
sourceTextId={titleTranslation.sourceTextId}
/>
)}
</h1>
{parsedContent}
</>
);
});
16 changes: 12 additions & 4 deletions web/app/routes/$userName+/page+/$slug+/components/Translation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,22 @@ export function Translation({

const alternativeTranslationsWithVotes = useMemo(
() =>
translationsWithVotes.filter((t) => t.id !== bestTranslationWithVote.id),
bestTranslationWithVote
? translationsWithVotes.filter(
(t) => t.id !== bestTranslationWithVote.id,
)
: [],
[translationsWithVotes, bestTranslationWithVote],
);

const sanitizedAndParsedText = useMemo(() => {
const sanitized = sanitizeAndParseText(bestTranslationWithVote.text);
return sanitized;
}, [bestTranslationWithVote.text]);
if (!bestTranslationWithVote) return null;
return sanitizeAndParseText(bestTranslationWithVote.text);
}, [bestTranslationWithVote]);

if (!bestTranslationWithVote || !sanitizedAndParsedText) {
return null;
}

return (
<div className="group relative">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function UserAITranslationStatus({
return (
<Card className="mt-6">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Translation Status</CardTitle>
<CardTitle>Your AI Translation Status</CardTitle>
<Button
variant="ghost"
size="sm"
Expand Down
7 changes: 3 additions & 4 deletions web/app/routes/$userName+/page+/$slug+/edit/_edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import { ArrowDownToLine } from "lucide-react";
import { z } from "zod";
import { Button } from "~/components/ui/button";
import { authenticator } from "~/utils/auth.server";
import { addNumbersToContent } from "../utils/addNumbersToContent";
import { extractNumberedElements } from "../utils/extractNumberedElements";
import { Header } from "./components/Header";
import { createOrUpdateSourceTexts } from "./functions/mutations.server";
import { getOrCreatePage } from "./functions/mutations.server";
import { getPageBySlug } from "./functions/queries.server";
import { addNumbersToContent } from "./utils/addNumbersToContent";
import { extractNumberedElements } from "./utils/extractNumberedElements";

const schema = z.object({
title: z.string().min(1, "Required"),
Expand Down Expand Up @@ -67,9 +67,8 @@ export const action: ActionFunction = async ({ request, params }) => {

const { title, pageContent } = submission.value;
const numberedContent = addNumbersToContent(pageContent);
console.log(numberedContent);
const page = await getOrCreatePage(safeUser.id, slug, title, numberedContent);
const numberedElements = extractNumberedElements(numberedContent);
const numberedElements = extractNumberedElements(numberedContent, title);
await createOrUpdateSourceTexts(numberedElements, page.id);

return redirect(`/${userName}/page/${slug}`);
Expand Down
48 changes: 36 additions & 12 deletions web/app/routes/$userName+/page+/$slug+/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { Languages } from "lucide-react";
import { useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { LoadingSpinner } from "~/components/LoadingSpinner";
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
import { Button } from "~/components/ui/button";
import { extractNumberedElements } from "~/features/prepare-html-for-translate/utils/extractNumberedElements";
import { AIModelSelector } from "~/features/translate/components/AIModelSelector";
import { getTranslateUserQueue } from "~/features/translate/translate-user-queue";
import { GeminiApiKeyDialog } from "~/routes/resources+/gemini-api-key-dialog";
Expand All @@ -31,7 +31,7 @@ import {
getDbUser,
} from "./functions/queries.server";
import { actionSchema } from "./types";

import { extractNumberedElements } from "./utils/extractNumberedElements";
export const loader = async ({ params, request }: LoaderFunctionArgs) => {
const { slug } = params;

Expand Down Expand Up @@ -77,10 +77,11 @@ export const action = async ({ request }: ActionFunctionArgs) => {
return {
intent: null,
lastResult: submission.reply({ formErrors: ["User not authenticated"] }),
slug: null,
};
}
if (submission.status !== "success") {
return { intent: null, lastResult: submission.reply() };
return { intent: null, lastResult: submission.reply(), slug: null };
}
const { intent } = submission.value;
switch (intent) {
Expand All @@ -90,22 +91,31 @@ export const action = async ({ request }: ActionFunctionArgs) => {
submission.value.isUpvote,
safeUserId,
);
return { intent, lastResult: submission.reply({ resetForm: true }) };
return {
intent,
lastResult: submission.reply({ resetForm: true }),
slug: null,
};
case "add":
handleAddTranslationAction(
submission.value.sourceTextId,
submission.value.text,
safeUserId,
targetLanguage,
);
return { intent, lastResult: submission.reply({ resetForm: true }) };
return {
intent,
lastResult: submission.reply({ resetForm: true }),
slug: null,
};
case "translate": {
const dbUser = await getDbUser(safeUser.id);
if (!dbUser?.geminiApiKey) {
return {
lastResult: submission.reply({
formErrors: ["Gemini API key is not set"],
}),
intent: null,
slug: null,
};
}
Expand All @@ -115,6 +125,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
lastResult: submission.reply({
formErrors: ["Page not found"],
}),
intent: null,
slug: null,
};
}
Expand All @@ -125,7 +136,10 @@ export const action = async ({ request }: ActionFunctionArgs) => {
targetLanguage,
);

const numberedElements = extractNumberedElements(page.content);
const numberedElements = extractNumberedElements(
page.content,
page.title,
);
const queue = getTranslateUserQueue(safeUser.id);
const job = await queue.add(`translate-${safeUser.id}`, {
userAITranslationInfoId: userAITranslationInfo.id,
Expand All @@ -138,7 +152,11 @@ export const action = async ({ request }: ActionFunctionArgs) => {
numberedContent: page.content,
numberedElements,
});
return { intent, lastResult: submission.reply({ resetForm: true }) };
return {
intent,
lastResult: submission.reply({ resetForm: true }),
slug: page.slug,
};
}
default:
throw new Error("Invalid Intent");
Expand Down Expand Up @@ -205,17 +223,23 @@ export default function ReaderView() {
)}
</div>
</Form>
{actionData?.slug && (
<Alert className="bg-blue-50 border-blue-200 text-blue-800 animate-in fade-in duration-300">
<AlertTitle className="text-center">
Translation Job Started
</AlertTitle>
<AlertDescription className="text-center">
<strong className="font-semibold ">{actionData.slug}</strong>
</AlertDescription>
</Alert>
)}
<UserAITranslationStatus
userAITranslationInfo={userAITranslationInfo}
/>
</div>
<article className="prose dark:prose-invert lg:prose-xl">
<h1>
{pageData.title}
<div>{pageData.translationTitle}</div>
</h1>
<hr />
<ContentWithTranslations
title={pageData.title}
content={pageData.content}
sourceTextWithTranslations={pageData.sourceTextWithTranslations}
userId={safeUser?.id ?? null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { TranslationWithVote } from "../types";

export function getBestTranslation(
translationsWithVotes: TranslationWithVote[],
): TranslationWithVote {
): TranslationWithVote | null {
if (translationsWithVotes.length === 0) {
return null;
}
const upvotedTranslations = translationsWithVotes.filter(
(t) => t.userVote?.isUpvote,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { JSDOM } from "jsdom";

export function extractNumberedElements(
content: string,
title: string,
): Array<{ number: number; text: string }> {
const doc = new JSDOM(content);
const numberedElements: Array<{ number: number; text: string }> = [];
const numberedElements: Array<{ number: number; text: string }> = [
{ number: 0, text: title },
];
// <br>のみを改行とする
doc.window.document.body.innerHTML = doc.window.document.body.innerHTML
.replace(/\n/g, "")
Expand Down
91 changes: 47 additions & 44 deletions web/app/routes/resources+/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,50 +42,36 @@ export function Footer({ safeUser }: FooterProps) {
return (
<footer className="bottom-0 left-0 right-0 border-t border-gray-200 dark:border-gray-700">
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center">
<div className="flex items-center">
<Link to="/">
<img
src="/title-logo-dark.png"
alt="::COMPANY_NAME::"
className="w-32"
/>
</Link>
</div>
<Link to={`/${safeUser?.userName}`}>
<Button variant="outline">{safeUser?.userName}</Button>
</Link>

<div className="flex items-center space-x-4 text-sm text-gray-6000 dark:text-gray-300">
<Link
to="/privacy"
className="hover:text-gray-900 dark:hover:text-white"
>
Privacy Policy
</Link>
<Link
to="/terms"
className="hover:text-gray-900 dark:hover:text-white"
>
Terms of Service
</Link>
<p>© {currentYear} EveEve</p>
</div>

<div>
{safeUser ? (
<Form method="post" action="/resources/footer">
<Button
type="submit"
name="intent"
value="logout"
variant="outline"
>
<LogOut className="w-4 h-4 mr-2" />
Log out
</Button>
</Form>
) : (
<div className="flex flex-col space-y-4">
<div className="flex justify-between items-center">
<div className="flex items-center space-x-4">
<Link to="/">
<img
src="/title-logo-dark.png"
alt="::COMPANY_NAME::"
className="w-32"
/>
</Link>
{safeUser && (
<>
<Link to={`/${safeUser.userName}`}>
<Button variant="outline">{safeUser.userName}</Button>
</Link>
<Form method="post" action="/resources/footer">
<Button
type="submit"
name="intent"
value="logout"
variant="outline"
>
<LogOut className="w-4 h-4 mr-2" />
Log out
</Button>
</Form>
</>
)}
</div>
{!safeUser && (
<Form method="post" action="/resources/footer">
<Button
type="submit"
Expand All @@ -99,6 +85,23 @@ export function Footer({ safeUser }: FooterProps) {
</Form>
)}
</div>
<div className="flex justify-between items-center text-sm text-gray-600 dark:text-gray-300 mt-8">
<div className="flex space-x-4">
<Link
to="/privacy"
className="hover:text-gray-900 dark:hover:text-white"
>
Privacy Policy
</Link>
<Link
to="/terms"
className="hover:text-gray-900 dark:hover:text-white"
>
Terms of Service
</Link>
</div>
<p>© {currentYear} EveEve</p>
</div>
</div>
</div>
</footer>
Expand Down
5 changes: 4 additions & 1 deletion web/app/routes/translator/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ export async function action({ request }: ActionFunctionArgs) {
targetLanguage,
);

const numberedElements = extractNumberedElements(numberedContent);
const numberedElements = extractNumberedElements(
numberedContent,
title,
);
await createOrUpdateSourceTexts(numberedElements, page.id);
// ファイルの翻訳ジョブをキューに追加
await queue.add(`translate-${safeUser.id}`, {
Expand Down

0 comments on commit 786db35

Please sign in to comment.