feat(i18n) : add personal languages and compact weapon exports
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { Localized, Text, ExportText, StatText, MetricText, SectionTitle, ConditionLabel } from "@/components/i18n/text";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowUpRightIcon, ChevronLeftIcon } from "lucide-react";
|
||||
@@ -54,7 +55,6 @@ import {
|
||||
} from "@/lib/guides/talents";
|
||||
import { parseYouTubeUrl } from "@/lib/guides/youtube";
|
||||
import { normalizeExtraRichText } from "@/lib/rich-text";
|
||||
import { exportText, exportStatLabel, type Locale } from "@/lib/guides/export-text";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function hasSection(guide: PublicGuide, slug: string): boolean {
|
||||
@@ -84,14 +84,6 @@ export function publicSections(guide: PublicGuide) {
|
||||
return guide.sections.filter((section) => hasSection(guide, section.slug));
|
||||
}
|
||||
|
||||
function publicSectionTitle(slug: string, fallback: string): string {
|
||||
if (slug === "weapon") return "อาวุธ";
|
||||
if (slug === "artifact") return "อาร์ติแฟกต์และค่าสถานะ";
|
||||
if (slug === "constellations") return "กลุ่มดาว";
|
||||
if (slug === "team") return "คำนวณทีม";
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function StructuredGuideShell({
|
||||
guide,
|
||||
section,
|
||||
@@ -109,13 +101,13 @@ export function StructuredGuideShell({
|
||||
<SiteHeader editHref={`/admin/${guide.characterKey}`} guideId={guide.id} />
|
||||
<div className="sticky top-0 z-40 bg-background/95 supports-backdrop-filter:backdrop-blur">
|
||||
<div className="mx-auto flex min-h-16 w-full max-w-7xl items-center gap-3 px-4 sm:px-6">
|
||||
<Link
|
||||
<Localized attributes={{ "aria-label": "กลับหน้ารวม" }}><Link
|
||||
href="/"
|
||||
aria-label="กลับหน้ารวม"
|
||||
className="grid size-10 place-items-center rounded-lg hover:bg-muted"
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
</Link>
|
||||
</Link></Localized>
|
||||
<div className="relative size-11 overflow-hidden rounded-lg border">
|
||||
<Image
|
||||
src={guide.coverUrl}
|
||||
@@ -130,7 +122,7 @@ export function StructuredGuideShell({
|
||||
{guide.name}
|
||||
</p>
|
||||
<p className="truncate font-heading text-2xl font-semibold">
|
||||
{currentPageTitle}
|
||||
<SectionTitle slug={section ?? "overview"} fallback={currentPageTitle} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -155,8 +147,7 @@ export function StructuredGuideShell({
|
||||
height={20}
|
||||
className="size-5 object-contain"
|
||||
/>
|
||||
Overview
|
||||
</Link>
|
||||
<Text message="Overview" /></Link>
|
||||
{sections.map((item) => (
|
||||
<Link
|
||||
key={item.id}
|
||||
@@ -178,7 +169,7 @@ export function StructuredGuideShell({
|
||||
className="size-5 object-contain"
|
||||
/>
|
||||
) : null}
|
||||
{item.title}
|
||||
<SectionTitle slug={item.slug} fallback={item.title} />
|
||||
</Link>
|
||||
))}
|
||||
</ScrollableNav>
|
||||
@@ -210,20 +201,20 @@ function GuideOverview({
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="mx-auto flex max-w-3xl flex-col gap-3 text-center">
|
||||
<p className="text-sm font-medium text-primary">ภาพรวมแนวทาง</p>
|
||||
<p className="text-sm font-medium text-primary"><Text message="ภาพรวมแนวทาง" /></p>
|
||||
<h1 className="font-heading text-4xl font-bold">{guide.name}</h1>
|
||||
<p className="text-lg font-semibold">{guide.overview}</p>
|
||||
</div>
|
||||
<div className="grid items-start gap-6 lg:grid-cols-2">
|
||||
<div className="order-1 relative aspect-square w-full overflow-hidden rounded-xl border bg-muted lg:col-start-1 lg:row-start-1">
|
||||
<Image
|
||||
<Localized attributes={{ "alt": { message: "ไกด์ {name}", params: { name: guide.name } } }}><Image
|
||||
src={guide.coverUrl}
|
||||
alt={`ไกด์ ${guide.name}`}
|
||||
fill
|
||||
sizes="(min-width: 1280px) 600px, (min-width: 1024px) calc(50vw - 44px), calc(100vw - 32px)"
|
||||
priority
|
||||
className="object-cover"
|
||||
/>
|
||||
/></Localized>
|
||||
</div>
|
||||
<div className="order-3 lg:col-start-2 lg:row-start-1">
|
||||
<GuideNavigation guide={guide} sections={sections} />
|
||||
@@ -268,7 +259,7 @@ function GuideOverview({
|
||||
{youtubeVideos.map((youtubeVideo, index) => (
|
||||
<section key={`${youtubeVideo.videoId}-${index}`} className="flex flex-col items-center gap-3">
|
||||
<div className="aspect-video w-full overflow-hidden rounded-xl border bg-muted">
|
||||
<iframe
|
||||
<Localized attributes={{ "title": { message: "วิดีโอไกด์ {name} {index}", params: { name: guide.name, index: index + 1 } } }}><iframe
|
||||
src={youtubeVideo.embedUrl}
|
||||
title={`วิดีโอไกด์ ${guide.name} ${index + 1}`}
|
||||
loading="lazy"
|
||||
@@ -276,7 +267,7 @@ function GuideOverview({
|
||||
referrerPolicy="strict-origin-when-cross-origin"
|
||||
allowFullScreen
|
||||
className="size-full"
|
||||
/>
|
||||
/></Localized>
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
@@ -297,7 +288,7 @@ function GuideNavigation({
|
||||
sections: ReturnType<typeof publicSections>;
|
||||
}) {
|
||||
return sections.length ? (
|
||||
<nav aria-label={`เมนูคู่มือ ${guide.name}`} className="flex flex-col gap-3">
|
||||
<Localized attributes={{ "aria-label": { message: "เมนูคู่มือ {name}", params: { name: guide.name } } }}><nav aria-label={`เมนูคู่มือ ${guide.name}`} className="flex flex-col gap-3">
|
||||
{sections.map((item) => (
|
||||
<Link
|
||||
key={item.id}
|
||||
@@ -316,8 +307,8 @@ function GuideNavigation({
|
||||
className="size-8 object-contain"
|
||||
/>
|
||||
) : null}
|
||||
<CardTitle>{publicSectionTitle(item.slug, item.title)}</CardTitle>
|
||||
<CardDescription>เปิดดูคำแนะนำฉบับเต็ม</CardDescription>
|
||||
<CardTitle><SectionTitle slug={item.slug} fallback={item.title} /></CardTitle>
|
||||
<CardDescription><Text message="เปิดดูคำแนะนำฉบับเต็ม" /></CardDescription>
|
||||
<CardAction>
|
||||
<ArrowUpRightIcon aria-hidden="true" className="size-5 text-primary" />
|
||||
</CardAction>
|
||||
@@ -325,12 +316,12 @@ function GuideNavigation({
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</nav></Localized>
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>กำลังจัดทำเนื้อหา</CardTitle>
|
||||
<CardDescription>กลับมาตรวจสอบอีกครั้งหลังผู้ดูแลเพิ่มข้อมูล</CardDescription>
|
||||
<CardTitle><Text message="กำลังจัดทำเนื้อหา" /></CardTitle>
|
||||
<CardDescription><Text message="กลับมาตรวจสอบอีกครั้งหลังผู้ดูแลเพิ่มข้อมูล" /></CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
@@ -352,10 +343,8 @@ function GuideSection({
|
||||
return extra ? <ExtraSection guide={guide} extra={extra} /> : null;
|
||||
}
|
||||
|
||||
function metric(value: string | null, locale?: string) {
|
||||
return value === null
|
||||
? null
|
||||
: `${Number(value).toLocaleString(locale, { maximumFractionDigits: 2 })}%`;
|
||||
function metric(value: string | null) {
|
||||
return value === null ? null : <MetricText value={value} />;
|
||||
}
|
||||
|
||||
function lunarisItemUrl(
|
||||
@@ -409,8 +398,7 @@ function WeaponSectionContent({
|
||||
<header className="relative z-10 flex items-center justify-center text-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<p className="text-sm font-medium text-primary">
|
||||
อาวุธที่แนะนำ
|
||||
</p>
|
||||
<Text message="อาวุธที่แนะนำ" /></p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Image
|
||||
src="/icon/nav/Weapon.png"
|
||||
@@ -425,8 +413,7 @@ function WeaponSectionContent({
|
||||
)}
|
||||
/>
|
||||
<h1 className="whitespace-nowrap font-heading text-4xl font-semibold">
|
||||
Weapons
|
||||
</h1>
|
||||
<Text message="Weapons" /></h1>
|
||||
</div>
|
||||
{note ? (
|
||||
<p className="max-w-3xl whitespace-pre-wrap text-muted-foreground">
|
||||
@@ -438,14 +425,14 @@ function WeaponSectionContent({
|
||||
<WeaponDisplayModeProvider>
|
||||
<ConditionSwitcher
|
||||
label="เลือกเงื่อนไขอาวุธ"
|
||||
labels={weaponGroups.map((group, index) => group.name || `Condition ${index + 1}`)}
|
||||
labels={weaponGroups.map((group) => group.name || "")}
|
||||
>
|
||||
{weaponGroups.map((group, groupIndex, groups) => (
|
||||
<div key={group.id} className="relative z-10 flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
{groups.length > 1 || conditionNumber ? (
|
||||
<h2 className="font-heading text-2xl font-semibold">
|
||||
{group.name || `Condition ${conditionNumber ?? groupIndex + 1}`}
|
||||
<ConditionLabel name={group.name} index={conditionNumber ?? groupIndex + 1} />
|
||||
</h2>
|
||||
) : <span />}
|
||||
{showExportActions ? (
|
||||
@@ -473,7 +460,7 @@ function WeaponSectionContent({
|
||||
: "md:grid-cols-[minmax(0,3fr)_minmax(0,1fr)]",
|
||||
)}
|
||||
>
|
||||
<a
|
||||
<Localized attributes={{ "aria-label": { message: "เปิดข้อมูลอาวุธ {name} บน Lunaris", params: { name: row.weapon.name } } }}><a
|
||||
href={lunarisItemUrl("weapon", row.weapon.key)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@@ -510,7 +497,7 @@ function WeaponSectionContent({
|
||||
: "text-base sm:text-lg",
|
||||
)}
|
||||
>
|
||||
{index + 1}. {row.weapon.name} R{row.refinement}
|
||||
{index + 1}. {row.weapon.name} <Text message="R" />{row.refinement}
|
||||
</p>
|
||||
{row.note ? (
|
||||
<p
|
||||
@@ -523,7 +510,7 @@ function WeaponSectionContent({
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</a>
|
||||
</a></Localized>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-around bg-white py-5 text-black",
|
||||
@@ -557,8 +544,7 @@ function WeaponSectionContent({
|
||||
: "text-sm sm:text-base",
|
||||
)}
|
||||
>
|
||||
ดาเมจทั้งทีม
|
||||
</p>
|
||||
<Text message="ดาเมจทั้งทีม" /></p>
|
||||
</div>
|
||||
) : null}
|
||||
{row.personalPercent ? (
|
||||
@@ -588,8 +574,7 @@ function WeaponSectionContent({
|
||||
: "text-sm sm:text-base",
|
||||
)}
|
||||
>
|
||||
ดาเมจส่วนตัว
|
||||
</p>
|
||||
<Text message="ดาเมจส่วนตัว" /></p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -617,8 +602,8 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
|
||||
<ArtifactImageExport
|
||||
fileName={`${guide.name}-Artifact.png`}
|
||||
hasNote={Boolean(guide.artifactProfile?.note)}
|
||||
recommendedConditionLabels={(guide.artifactProfile?.recommendedConditions ?? []).map((group, index) => group.name || `Condition ${index + 1}`)}
|
||||
targetConditionLabels={targetConditions.map((group, index) => group.name || `Condition ${index + 1}`)}
|
||||
recommendedConditionLabels={(guide.artifactProfile?.recommendedConditions ?? []).map((group) => group.name || "")}
|
||||
targetConditionLabels={targetConditions.map((group) => group.name || "")}
|
||||
>
|
||||
<ArtifactSectionContent guide={guide} exporting />
|
||||
</ArtifactImageExport>
|
||||
@@ -640,31 +625,31 @@ function ArtifactSectionContent({
|
||||
const recommendedConditions = profile?.recommendedConditions ?? [];
|
||||
const recommendedCards = recommendedConditions.map((condition, conditionIndex) => (
|
||||
<div key={conditionIndex} data-artifact-export-section="best-stats" data-recommended-condition={conditionIndex} className="flex flex-col gap-5">
|
||||
{exporting && recommendedConditions.length > 1 ? <p className="text-sm font-medium">{condition.name || exportText("condition", { index: conditionIndex + 1 })}</p> : null}
|
||||
{exporting && recommendedConditions.length > 1 ? <p className="text-sm font-medium"><ConditionLabel name={condition.name ?? null} index={conditionIndex + 1} /></p> : null}
|
||||
{condition.note ? <p data-export-note className="whitespace-pre-wrap text-sm text-muted-foreground">{condition.note}</p> : null}
|
||||
{profile ? (
|
||||
<Card data-artifact-export-section="best-stats" className="artifact-export-main-stats overflow-hidden pt-0">
|
||||
<CardHeader className="bg-primary py-4 text-center text-primary-foreground">
|
||||
<CardTitle className="text-2xl">{exportText("mainStats", {}, exporting ? "en" : "th")}</CardTitle>
|
||||
<CardTitle className="text-2xl"><ExportText message="mainStats" /></CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid grid-cols-3 p-0">
|
||||
<ArtifactStat
|
||||
iconSrc="/icon/Icon_Sands_of_Eon.webp"
|
||||
label={exportText("sands", {}, exporting ? "en" : "th")}
|
||||
label={<ExportText message="sands" />}
|
||||
values={condition.sands}
|
||||
exporting={exporting}
|
||||
elementIconUrls={guide.elementIconUrls}
|
||||
/>
|
||||
<ArtifactStat
|
||||
iconSrc="/icon/Icon_Goblet_of_Eonothem.webp"
|
||||
label={exportText("goblet", {}, exporting ? "en" : "th")}
|
||||
label={<ExportText message="goblet" />}
|
||||
values={condition.goblet}
|
||||
exporting={exporting}
|
||||
elementIconUrls={guide.elementIconUrls}
|
||||
/>
|
||||
<ArtifactStat
|
||||
iconSrc="/icon/Icon_Circlet_of_Logos.webp"
|
||||
label={exportText("circlet", {}, exporting ? "en" : "th")}
|
||||
label={<ExportText message="circlet" />}
|
||||
values={condition.circlet}
|
||||
exporting={exporting}
|
||||
elementIconUrls={guide.elementIconUrls}
|
||||
@@ -675,7 +660,7 @@ function ArtifactSectionContent({
|
||||
{condition.substats.length ? (
|
||||
<Card data-artifact-export-section="best-stats" className="artifact-export-substats">
|
||||
<CardContent className="flex flex-col items-center gap-2 py-5 text-center text-sm">
|
||||
<strong>{exportText("substats", {}, exporting ? "en" : "th")}</strong>
|
||||
<strong><ExportText message="substats" /></strong>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
{condition.substats.map((stat, index) => (
|
||||
<ArtifactStatValue
|
||||
@@ -710,7 +695,7 @@ function ArtifactSectionContent({
|
||||
<header className="relative z-10 flex items-center justify-center text-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<p className="text-sm font-medium text-primary">
|
||||
{exportText("artifactRecommendation", {}, exporting ? "en" : "th")}
|
||||
<ExportText message="artifactRecommendation" />
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Image
|
||||
@@ -726,7 +711,7 @@ function ArtifactSectionContent({
|
||||
)}
|
||||
/>
|
||||
<h1 className="whitespace-nowrap font-heading text-4xl font-semibold">
|
||||
{exportText("artifactTitle", {}, exporting ? "en" : "th")}
|
||||
<ExportText message="artifactTitle" />
|
||||
</h1>
|
||||
</div>
|
||||
{action}
|
||||
@@ -741,7 +726,7 @@ function ArtifactSectionContent({
|
||||
{" "}
|
||||
<div className="artifact-export-left flex flex-col gap-5">
|
||||
{exporting ? recommendedCards : (
|
||||
<ConditionSwitcher label="เลือกเงื่อนไขค่าสถานะแนะนำ" labels={recommendedConditions.map((condition, index) => condition.name || `Condition ${index + 1}`)}>
|
||||
<ConditionSwitcher label="เลือกเงื่อนไขค่าสถานะแนะนำ" labels={recommendedConditions.map((condition) => condition.name || "")}>
|
||||
{recommendedCards}
|
||||
</ConditionSwitcher>
|
||||
)}
|
||||
@@ -751,9 +736,9 @@ function ArtifactSectionContent({
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
{group.name === "Late Game"
|
||||
? exportText("lateGame", {}, exporting ? "en" : "th")
|
||||
? <ExportText message="lateGame" />
|
||||
: group.name === "Early Game"
|
||||
? exportText("earlyGame", {}, exporting ? "en" : "th")
|
||||
? <ExportText message="earlyGame" />
|
||||
: group.name}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -787,7 +772,7 @@ function ArtifactSectionContent({
|
||||
) : null}
|
||||
</div>
|
||||
<p className="text-2xl font-semibold text-primary">
|
||||
{Number(row.percent) === 0 ? "-%" : metric(row.percent, exporting ? "en-US" : undefined)}
|
||||
{Number(row.percent) === 0 ? "-%" : metric(row.percent)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
@@ -806,7 +791,7 @@ function ArtifactSectionContent({
|
||||
exporting ? (
|
||||
profile.targetConditions.filter((group) => group.rows.length).map((group, groupIndex) => (
|
||||
<div key={groupIndex} data-artifact-export-section="target-stats" data-target-condition={groupIndex}>
|
||||
<p className="mb-2 text-sm font-medium">{exportText("minimumStats", {}, exporting ? "en" : "th")}</p>
|
||||
<p className="mb-2 text-sm font-medium"><ExportText message="minimumStats" /></p>
|
||||
{group.note ? <p data-export-note className="mb-2 whitespace-pre-wrap text-sm text-muted-foreground">{group.note}</p> : null}
|
||||
<div className="flex flex-col gap-1">
|
||||
{group.rows.map((target, index) => (
|
||||
@@ -830,7 +815,7 @@ function ArtifactSectionContent({
|
||||
) : null}
|
||||
<span>
|
||||
{statLabel(
|
||||
normalizeTargetStat(target.stat) ?? target.stat, exporting ? "en" : "th",
|
||||
normalizeTargetStat(target.stat) ?? target.stat,
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
@@ -841,10 +826,10 @@ function ArtifactSectionContent({
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<ConditionSwitcher label="เลือกเงื่อนไขค่าสถานะเป้าหมาย" labels={profile.targetConditions.filter((group) => group.rows.length).map((group, index) => group.name || `Condition ${index + 1}`)}>
|
||||
<ConditionSwitcher label="เลือกเงื่อนไขค่าสถานะเป้าหมาย" labels={profile.targetConditions.filter((group) => group.rows.length).map((group) => group.name || "")}>
|
||||
{profile.targetConditions.filter((group) => group.rows.length).map((group, groupIndex) => (
|
||||
<div key={groupIndex}>
|
||||
<p className="mb-2 text-sm font-medium">{exportText("minimumStats", {}, exporting ? "en" : "th")}</p>
|
||||
<p className="mb-2 text-sm font-medium"><ExportText message="minimumStats" /></p>
|
||||
{group.note ? <p data-export-note className="mb-2 whitespace-pre-wrap text-sm text-muted-foreground">{group.note}</p> : null}
|
||||
<div className="flex flex-col gap-1">
|
||||
{group.rows.map((target, index) => (
|
||||
@@ -865,7 +850,7 @@ function ArtifactSectionContent({
|
||||
{profile.talents.length ? (
|
||||
<div data-artifact-export-section="talents">
|
||||
<p className="mb-2 text-sm font-medium">
|
||||
{exportText("talents", {}, exporting ? "en" : "th")}
|
||||
<ExportText message="talents" />
|
||||
</p>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -915,7 +900,7 @@ function ArtifactSectionContent({
|
||||
</div>
|
||||
<Badge variant="secondary">
|
||||
{talent.level
|
||||
? exportText("level", { level: talent.level }, exporting ? "en" : "th")
|
||||
? <ExportText message="level" params={{ level: talent.level }} />
|
||||
: "-"}
|
||||
</Badge>
|
||||
<p
|
||||
@@ -932,11 +917,11 @@ function ArtifactSectionContent({
|
||||
exporting ? "text-xs" : "text-[11px] sm:text-xs",
|
||||
)}
|
||||
>
|
||||
{exportText(talent.type === "E" ? "skill" : talent.type === "Q" ? "burst" : "normalAttack", {}, exporting ? "en" : "th")}
|
||||
<ExportText message={talent.type === "E" ? "skill" : talent.type === "Q" ? "burst" : "normalAttack"} />
|
||||
</p>
|
||||
</div>
|
||||
{index < talents.length - 1 ? (
|
||||
<span
|
||||
<Localized attributes={{ "aria-label": talent.relationToNext === "gte" ? "มากกว่าหรือเท่ากับ" : talent.relationToNext === "eq" ? "เท่ากับ" : "มากกว่า" }}><span
|
||||
className={cn(
|
||||
"font-semibold",
|
||||
exporting
|
||||
@@ -952,7 +937,7 @@ function ArtifactSectionContent({
|
||||
}
|
||||
>
|
||||
{talentRelationSymbol(talent.relationToNext)}
|
||||
</span>
|
||||
</span></Localized>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
@@ -963,7 +948,7 @@ function ArtifactSectionContent({
|
||||
) : null}
|
||||
{profile.note ? (
|
||||
<div data-export-note data-artifact-export-section="note" className="pt-3">
|
||||
<p className="mb-2 text-sm font-medium">{exportText("note", {}, exporting ? "en" : "th")}</p>
|
||||
<p className="mb-2 text-sm font-medium"><ExportText message="note" /></p>
|
||||
<p className="text-sm leading-6 text-muted-foreground">
|
||||
{profile.note}
|
||||
</p>
|
||||
@@ -978,8 +963,8 @@ function ArtifactSectionContent({
|
||||
);
|
||||
}
|
||||
|
||||
function statLabel(value: string, locale: Locale = "th"): string {
|
||||
return exportStatLabel(value, locale);
|
||||
function statLabel(value: string) {
|
||||
return <StatText value={value} />;
|
||||
}
|
||||
|
||||
function ArtifactStat({
|
||||
@@ -990,7 +975,7 @@ function ArtifactStat({
|
||||
elementIconUrls,
|
||||
}: {
|
||||
iconSrc: string;
|
||||
label: string;
|
||||
label: ReactNode;
|
||||
values: string[];
|
||||
exporting?: boolean;
|
||||
elementIconUrls: Record<string, string>;
|
||||
@@ -1080,7 +1065,7 @@ function ArtifactStatValue({
|
||||
className="size-5 shrink-0 object-contain brightness-0 invert"
|
||||
/>
|
||||
) : null}
|
||||
<span className="break-words">{statLabel(value, exporting ? "en" : "th")}</span>
|
||||
<span className="break-words">{statLabel(value)}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1093,8 +1078,7 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
|
||||
<section className="flex flex-col gap-6">
|
||||
<header className="flex flex-col items-center gap-3 text-center">
|
||||
<p className="text-sm font-medium text-primary">
|
||||
กลุ่มดาวที่แนะนำ
|
||||
</p>
|
||||
<Text message="กลุ่มดาวที่แนะนำ" /></p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Image
|
||||
src="/icon/nav/Constellations.webp"
|
||||
@@ -1105,8 +1089,7 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
|
||||
className="size-14 shrink-0 object-contain"
|
||||
/>
|
||||
<h1 className="whitespace-nowrap font-heading text-4xl font-semibold">
|
||||
Constellations
|
||||
</h1>
|
||||
<Text message="Constellations" /></h1>
|
||||
</div>
|
||||
{note ? (
|
||||
<p className="max-w-3xl whitespace-pre-wrap text-muted-foreground">
|
||||
@@ -1114,7 +1097,7 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
|
||||
</p>
|
||||
) : null}
|
||||
</header>
|
||||
<ConditionSwitcher label="เลือกเงื่อนไขกลุ่มดาว" labels={guide.constellationGroups.filter((group) => group.rows.length > 0).map((group, index) => group.name || `Condition ${index + 1}`)}>
|
||||
<ConditionSwitcher label="เลือกเงื่อนไขกลุ่มดาว" labels={guide.constellationGroups.filter((group) => group.rows.length > 0).map((group) => group.name || "")}>
|
||||
{guide.constellationGroups.filter((group) => group.rows.length > 0).map((group, groupIndex, groups) => (
|
||||
<div key={group.id} className="flex flex-col gap-4">
|
||||
<div>
|
||||
@@ -1130,7 +1113,7 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
|
||||
/>
|
||||
</GuideImageExport>
|
||||
</div>
|
||||
{groups.length > 1 ? <h2 className="font-heading text-2xl font-semibold">{group.name || `Condition ${groupIndex + 1}`}</h2> : null}
|
||||
{groups.length > 1 ? <h2 className="font-heading text-2xl font-semibold"><ConditionLabel name={group.name} index={groupIndex + 1} /></h2> : null}
|
||||
{group.note ? <p className="whitespace-pre-wrap">{group.note}</p> : null}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{group.rows.map((row) => (
|
||||
@@ -1155,7 +1138,7 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
|
||||
{metric(row.cumulativePercent)}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-base font-medium text-white">
|
||||
C{row.level} · {row.name}
|
||||
<Text message="C" />{row.level} · {row.name}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -1176,8 +1159,8 @@ function TeamSection({ guide }: { guide: PublicGuide }) {
|
||||
return (
|
||||
<section className="flex flex-col gap-6">
|
||||
<header className="text-center">
|
||||
<p className="text-sm font-medium text-primary">คำนวณความเสียหาย</p>
|
||||
<h1 className="font-heading text-4xl font-semibold">คำนวณทีม</h1>
|
||||
<p className="text-sm font-medium text-primary"><Text message="คำนวณความเสียหาย" /></p>
|
||||
<h1 className="font-heading text-4xl font-semibold"><Text message="คำนวณทีม" /></h1>
|
||||
</header>
|
||||
<TeamSearch groups={guide.teamGroups} />
|
||||
</section>
|
||||
@@ -1223,7 +1206,7 @@ function ExtraSectionContent({
|
||||
/>
|
||||
) : null}
|
||||
<header className="relative z-10 flex flex-col items-center gap-3 text-center">
|
||||
<p className="text-sm font-medium text-primary">{exportText("extraGuide", {}, exporting ? "en" : "th")}</p>
|
||||
<p className="text-sm font-medium text-primary"><ExportText message="extraGuide" /></p>
|
||||
<h1 className="font-heading text-4xl font-semibold">{extra.title}</h1>
|
||||
{action}
|
||||
</header>
|
||||
@@ -1258,7 +1241,7 @@ function ExtraSectionContent({
|
||||
{row.catalogItems.length ? (
|
||||
<CardContent className="grid grid-cols-2 gap-2 pt-5">
|
||||
{row.catalogItems.map((item) => (
|
||||
<a
|
||||
<Localized key={`${item.kind}:${item.key}`} attributes={{ "aria-label": { message: "เปิดข้อมูล {name} บน Lunaris", params: { name: item.name } } }}><a
|
||||
key={`${item.kind}:${item.key}`}
|
||||
href={lunarisItemUrl(item.kind, item.key)}
|
||||
target="_blank"
|
||||
@@ -1274,10 +1257,10 @@ function ExtraSectionContent({
|
||||
) : null}
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium">{item.name}</p>
|
||||
{!item.available ? <Badge variant="secondary">{exportText("missingItem", {}, exporting ? "en" : "th")}</Badge> : null}
|
||||
{!item.available ? <Badge variant="secondary"><ExportText message="missingItem" /></Badge> : null}
|
||||
</div>
|
||||
<ArrowUpRightIcon aria-hidden="true" className="ml-auto shrink-0" />
|
||||
</a>
|
||||
</a></Localized>
|
||||
))}
|
||||
</CardContent>
|
||||
) : null}
|
||||
@@ -1286,7 +1269,7 @@ function ExtraSectionContent({
|
||||
<CardDescription>
|
||||
<RichTextContent html={normalizeExtraRichText(row.body, row.highlights)} />
|
||||
</CardDescription>
|
||||
{row.percent ? <Badge>{metric(row.percent, exporting ? "en-US" : undefined)}</Badge> : null}
|
||||
{row.percent ? <Badge>{metric(row.percent)}</Badge> : null}
|
||||
</CardHeader>
|
||||
{row.sheet ? (
|
||||
<CardContent
|
||||
|
||||
Reference in New Issue
Block a user