Skip to content

Commit 2263026

Browse files
authored
Replace Link with LocaleLink for localization support (#452)
2 parents b11e527 + 38310a5 commit 2263026

File tree

17 files changed

+127
-58
lines changed

17 files changed

+127
-58
lines changed

web/app/components/BaseHeaderLayout.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Link } from "@remix-run/react";
2-
import { Form, NavLink } from "@remix-run/react";
1+
import { Form } from "@remix-run/react";
32
import { LogOutIcon, SettingsIcon } from "lucide-react";
43
import type { ReactNode } from "react";
4+
import { LocaleLink } from "~/components/LocaleLink";
55
import { ModeToggle } from "~/components/ModeToggle";
6+
import { NavLocaleLink } from "~/components/NavLocaleLink";
67
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
78
import {
89
DropdownMenu,
@@ -30,14 +31,14 @@ export function BaseHeaderLayout({
3031
<header className="z-10 w-full">
3132
<div className="max-w-7xl mx-auto py-2 md:py-4 px-2 md:px-6 lg:px-8 flex justify-between items-center">
3233
<div className="flex items-center gap-4">
33-
<Link to="/home" className="flex items-center">
34+
<LocaleLink to="/home" className="flex items-center">
3435
<img
3536
src="/logo.svg"
3637
alt="Evame"
3738
className="h-8 w-20 dark:invert"
3839
aria-label="Evame Logo"
3940
/>
40-
</Link>
41+
</LocaleLink>
4142
{leftExtra}
4243
</div>
4344
<div className="flex items-center gap-4">
@@ -57,7 +58,7 @@ export function BaseHeaderLayout({
5758
</DropdownMenuTrigger>
5859
<DropdownMenuContent className="m-2 p-0 rounded-xl min-w-40">
5960
<DropdownMenuItem asChild>
60-
<NavLink
61+
<NavLocaleLink
6162
to={`/user/${currentUser.userName}`}
6263
className={({ isPending }) =>
6364
isPending
@@ -71,11 +72,11 @@ export function BaseHeaderLayout({
7172
@{currentUser.userName}
7273
</span>
7374
</div>
74-
</NavLink>
75+
</NavLocaleLink>
7576
</DropdownMenuItem>
7677
<DropdownMenuSeparator className="my-0" />
7778
<DropdownMenuItem asChild>
78-
<NavLink
79+
<NavLocaleLink
7980
to={`/user/${currentUser.userName}/page-management`}
8081
className={({ isPending }) =>
8182
isPending
@@ -85,7 +86,7 @@ export function BaseHeaderLayout({
8586
>
8687
<SettingsIcon className="w-4 h-4" />
8788
Page Management
88-
</NavLink>
89+
</NavLocaleLink>
8990
</DropdownMenuItem>
9091
<DropdownMenuItem asChild>
9192
<ModeToggle />

web/app/components/LocaleLink.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// LocaleLink.tsx
2+
import { Link, useMatches } from "@remix-run/react";
3+
4+
function useRootData() {
5+
const matches = useMatches();
6+
const rootMatch = matches.find((match) => match.id === "root");
7+
return rootMatch?.data as { locale: string } | undefined;
8+
}
9+
10+
export function LocaleLink({
11+
to,
12+
children,
13+
className,
14+
}: {
15+
to: string;
16+
children: React.ReactNode;
17+
className?: string;
18+
}) {
19+
const rootData = useRootData();
20+
const locale = rootData?.locale ?? "en";
21+
22+
const path = `/${locale}${to}`;
23+
24+
return (
25+
<Link to={path} className={className}>
26+
{children}
27+
</Link>
28+
);
29+
}

web/app/components/NavLocaleLink.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { NavLink, type NavLinkProps, useMatches } from "@remix-run/react";
2+
3+
function useRootData() {
4+
const matches = useMatches();
5+
const rootMatch = matches.find((match) => match.id === "root");
6+
return rootMatch?.data as { locale?: string } | undefined;
7+
}
8+
9+
// NavLinkProps を継承
10+
type NavLocaleLinkProps = Omit<NavLinkProps, "to"> & {
11+
to: string;
12+
};
13+
14+
export function NavLocaleLink({
15+
to,
16+
className,
17+
children,
18+
...rest
19+
}: NavLocaleLinkProps) {
20+
const rootData = useRootData();
21+
const locale = rootData?.locale ?? "en";
22+
23+
// 先頭のスラッシュを削除したうえで "/:locale" を付与
24+
const normalized = to.startsWith("/") ? to.slice(1) : to;
25+
const path = `/${locale}/${normalized}`;
26+
27+
return (
28+
<NavLink
29+
to={path}
30+
// className が「文字列」or「コールバック」どちらでもOK
31+
className={className}
32+
{...rest}
33+
>
34+
{children}
35+
</NavLink>
36+
);
37+
}

web/app/components/PageCard.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
// PageCard.tsx
2-
import { Link } from "@remix-run/react";
31
import { Lock } from "lucide-react";
2+
import { LocaleLink } from "~/components/LocaleLink";
43
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
54
import {
65
Card,
@@ -12,7 +11,6 @@ import {
1211
import { PageActionsDropdown } from "~/routes/$locale+/user.$userName+/components/PageActionsDropdown";
1312
import { LikeButton } from "~/routes/resources+/like-button";
1413
import type { PageCardType } from "~/routes/types";
15-
1614
type PageCardProps = {
1715
pageCard: PageCardType;
1816
pageLink: string;
@@ -43,17 +41,17 @@ export function PageCard({
4341
</div>
4442
)}
4543
<CardHeader>
46-
<Link to={pageLink} className="block">
44+
<LocaleLink to={pageLink} className="block">
4745
<CardTitle className="flex items-center pr-3 break-all overflow-wrap-anywhere">
4846
{!pageCard.isPublished && <Lock className="h-4 w-4 mr-2" />}
4947
{pageCard.title}
5048
</CardTitle>
5149
<CardDescription>{pageCard.createdAt}</CardDescription>
52-
</Link>
50+
</LocaleLink>
5351
</CardHeader>
5452
<CardContent>
5553
<div className="flex justify-between items-center">
56-
<Link to={userLink} className="flex items-center">
54+
<LocaleLink to={userLink} className="flex items-center">
5755
<Avatar className="w-6 h-6 mr-2">
5856
<AvatarImage
5957
src={pageCard.user.icon}
@@ -66,7 +64,7 @@ export function PageCard({
6664
<span className="text-sm text-gray-600">
6765
{pageCard.user.displayName}
6866
</span>
69-
</Link>
67+
</LocaleLink>
7068

7169
<LikeButton
7270
liked={pageCard.likePages.length > 0}

web/app/routes/$locale+/auth/login/route.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
55
import { Form, useActionData, useLoaderData } from "@remix-run/react";
66
import { useLocation } from "@remix-run/react";
77
import { useNavigation } from "@remix-run/react";
8-
import { Link } from "@remix-run/react";
98
import { CheckCircle } from "lucide-react";
109
import { z } from "zod";
10+
import { LocaleLink } from "~/components/LocaleLink";
1111
import { Button } from "~/components/ui/button";
1212
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
1313
import { Input } from "~/components/ui/input";
@@ -16,7 +16,6 @@ import { Separator } from "~/components/ui/separator";
1616
import { sessionStorage } from "~/utils/session.server";
1717
import { authenticator } from "../../../../utils/auth.server";
1818
import { GoogleForm } from "../../../resources+/google-form";
19-
2019
const loginSchema = z.object({
2120
email: z.string().email("Please enter a valid email address"),
2221
});
@@ -130,13 +129,13 @@ export default function LoginPage() {
130129
</Form>
131130
<div className="text-center text-sm text-gray-500 my-2">
132131
Login means you agree to our{" "}
133-
<Link to="/terms" className="underline">
132+
<LocaleLink to="/terms" className="underline">
134133
Terms of Service
135-
</Link>{" "}
134+
</LocaleLink>{" "}
136135
and{" "}
137-
<Link to="/privacy" className="underline">
136+
<LocaleLink to="/privacy" className="underline">
138137
Privacy Policy
139-
</Link>
138+
</LocaleLink>
140139
</div>
141140
</CardContent>
142141
</Card>

web/app/routes/$locale+/privacy.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Link } from "@remix-run/react";
1+
import { LocaleLink } from "~/components/LocaleLink";
22

33
export default function PrivacyPolicyPage() {
44
return (
@@ -99,9 +99,9 @@ export default function PrivacyPolicyPage() {
9999
</section>
100100

101101
<div className="mt-8">
102-
<Link to="/" className="text-blue-600 hover:underline">
102+
<LocaleLink to="/" className="text-blue-600 hover:underline">
103103
Return to Home
104-
</Link>
104+
</LocaleLink>
105105
</div>
106106
</main>
107107
</div>

web/app/routes/$locale+/search/route.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { getZodConstraint, parseWithZod } from "@conform-to/zod";
33
import type { ActionFunctionArgs } from "@remix-run/node";
44
import type { LoaderFunctionArgs } from "@remix-run/node";
55
import { useActionData, useNavigation } from "@remix-run/react";
6-
import { Link } from "@remix-run/react";
76
import { Form } from "@remix-run/react";
87
import type { MetaFunction } from "@remix-run/react";
98
import { z } from "zod";
9+
import { LocaleLink } from "~/components/LocaleLink";
1010
import { Button } from "~/components/ui/button";
1111
import { Input } from "~/components/ui/input";
1212
import { authenticator } from "~/utils/auth.server";
@@ -79,12 +79,12 @@ export default function Search() {
7979
key={result.id}
8080
className="hover:bg-gray-300 dark:hover:bg-gray-700 transition duration-150 rounded-lg"
8181
>
82-
<Link
82+
<LocaleLink
8383
to={`/user/${result.user.userName}/page/${encodeURIComponent(result.slug)}`}
8484
className="block p-2 text-inherit no-underline"
8585
>
8686
<h3 className="font-bold">{result.title}</h3>
87-
</Link>
87+
</LocaleLink>
8888
</li>
8989
))}
9090
</ul>

web/app/routes/$locale+/terms/route.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Link } from "@remix-run/react";
1+
import { LocaleLink } from "~/components/LocaleLink";
22

33
export default function TermsPage() {
44
return (
@@ -127,9 +127,9 @@ export default function TermsPage() {
127127
</section>
128128

129129
<div className="mt-8">
130-
<Link to="/" className="text-blue-600 hover:underline">
130+
<LocaleLink to="/" className="text-blue-600 hover:underline">
131131
Return to Home
132-
</Link>
132+
</LocaleLink>
133133
</div>
134134
</main>
135135
</div>

web/app/routes/$locale+/user.$userName+/components/PageActionsDropdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { NavLink } from "@remix-run/react";
21
import { MoreVertical } from "lucide-react";
2+
import { NavLocaleLink } from "~/components/NavLocaleLink";
33
import { Button } from "~/components/ui/button";
44
import {
55
DropdownMenu,
@@ -36,7 +36,7 @@ export function PageActionsDropdown({
3636
</DropdownMenuTrigger>
3737
<DropdownMenuContent align="end">
3838
<DropdownMenuItem asChild>
39-
<NavLink to={editPath}>Edit</NavLink>
39+
<NavLocaleLink to={editPath}>Edit</NavLocaleLink>
4040
</DropdownMenuItem>
4141
<DropdownMenuItem onSelect={onTogglePublic}>
4242
{isPublished ? "Make Private" : "Make Public"}

web/app/routes/$locale+/user.$userName+/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useSearchParams } from "@remix-run/react";
88
import Linkify from "linkify-react";
99
import { Settings } from "lucide-react";
1010
import { useState } from "react";
11+
import { LocaleLink } from "~/components/LocaleLink";
1112
import { PageCard } from "~/components/PageCard";
1213
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
1314
import { Button } from "~/components/ui/button";
@@ -160,11 +161,13 @@ export default function UserPage() {
160161
<CardTitle className="text-2xl font-bold flex justify-between items-center">
161162
<div>{sanitizedUserWithPages.displayName}</div>
162163
{isOwner && (
163-
<Link to={`/user/${sanitizedUserWithPages.userName}/edit`}>
164+
<LocaleLink
165+
to={`/user/${sanitizedUserWithPages.userName}/edit`}
166+
>
164167
<Button variant="ghost">
165168
<Settings className="w-6 h-6" />
166169
</Button>
167-
</Link>
170+
</LocaleLink>
168171
)}
169172
</CardTitle>
170173
</CardHeader>

web/app/routes/$locale+/user.$userName+/page+/$slug+/components/ContentWithTranslations.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { UserAITranslationInfo } from "@prisma/client";
2-
import { Link } from "@remix-run/react";
32
import { Hash, Loader2 } from "lucide-react";
43
import { useHydrated } from "remix-utils/use-hydrated";
4+
import { LocaleLink } from "~/components/LocaleLink";
55
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
66
import type {
77
PageWithTranslations,
@@ -65,8 +65,8 @@ export function ContentWithTranslations({
6565
</div>
6666

6767
<div className="flex items-center not-prose">
68-
<Link
69-
to={`/${pageWithTranslations.user.userName}`}
68+
<LocaleLink
69+
to={`/user/${pageWithTranslations.user.userName}`}
7070
className="flex items-center mr-2 !no-underline hover:text-gray-700"
7171
>
7272
<Avatar className="w-12 h-12 flex-shrink-0 mr-3 ">
@@ -86,7 +86,7 @@ export function ContentWithTranslations({
8686
{pageWithTranslations.page.createdAt}
8787
</span>
8888
</div>
89-
</Link>
89+
</LocaleLink>
9090
</div>
9191
<TranslateActionSection
9292
pageId={pageWithTranslations.page.id}

web/app/routes/$locale+/user.$userName+/page+/$slug+/components/sourceTextAndTranslationSection/SourceTextAndTranslationSection.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { NavLink } from "@remix-run/react";
21
import { Lock } from "lucide-react";
32
import { SquarePen } from "lucide-react";
43
import type { ReactNode } from "react";
4+
import { NavLocaleLink } from "~/components/NavLocaleLink";
55
import type { SourceTextWithTranslations } from "../../types";
66
import { TranslationSection } from "./TranslationSection";
77
interface SourceTextAndTranslationSectionProps {
@@ -44,14 +44,14 @@ export function SourceTextAndTranslationSection({
4444
</span>
4545
{isOwner && slug && (
4646
<div className="ml-auto">
47-
<NavLink
47+
<NavLocaleLink
4848
to={`/user/${currentUserName}/page/${slug}/edit`}
4949
className={({ isPending }) =>
5050
isPending ? "opacity-50" : "opacity-100"
5151
}
5252
>
5353
<SquarePen className="w-5 h-5" />
54-
</NavLink>
54+
</NavLocaleLink>
5555
</div>
5656
)}
5757
</div>

web/app/routes/$locale+/user.$userName+/page+/$slug+/components/sourceTextAndTranslationSection/TranslationSection.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Link } from "@remix-run/react";
21
import { Languages, Plus } from "lucide-react";
32
import { useState } from "react";
43
import { useHydrated } from "remix-utils/use-hydrated";
4+
import { LocaleLink } from "~/components/LocaleLink";
55
import { VoteButtons } from "~/routes/resources+/vote-buttons";
66
import type { SourceTextWithTranslations } from "../../types";
77
import { sanitizeAndParseText } from "../../utils/sanitize-and-parse-text.client";
@@ -46,14 +46,14 @@ export function TranslationSection({
4646
{isSelected && (
4747
<>
4848
<div className="flex items-center justify-end">
49-
<Link
49+
<LocaleLink
5050
to={`/user/${bestTranslationWithVote?.user.userName}`}
5151
className="!no-underline mr-2"
5252
>
5353
<p className="text-sm text-gray-500 text-right flex justify-end items-center">
5454
by: {bestTranslationWithVote?.user.displayName}
5555
</p>
56-
</Link>
56+
</LocaleLink>
5757
<VoteButtons translationWithVote={bestTranslationWithVote} />
5858
</div>
5959
<AddAndVoteTranslations

0 commit comments

Comments
 (0)