feat(i18n) : add personal languages and compact weapon exports
This commit is contained in:
@@ -2,6 +2,7 @@ import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { LocaleBoundary } from "@/components/i18n/locale-provider";
|
||||
import { GuideImageExport } from "@/components/public/guide-image-export";
|
||||
import { ConstellationImage } from "@/components/public/constellation-image";
|
||||
import type { PublicGuide } from "@/lib/guides/queries";
|
||||
@@ -18,9 +19,9 @@ describe("export translations", () => {
|
||||
});
|
||||
|
||||
it("defaults to English and interpolates names without interpreting their contents", () => {
|
||||
expect(exportText("weaponTitle", { name: "Raiden" })).toBe("Raiden’s Weapon Comparison");
|
||||
expect(exportText("weaponTitle", { name: "Raiden" })).toBe("Raiden Weapons");
|
||||
expect(exportText("rotation", { seconds: "20.5" })).toBe("Rotation duration: 20.5 seconds");
|
||||
expect(exportText("weaponTitle", { name: "{seconds}" })).toBe("{seconds}’s Weapon Comparison");
|
||||
expect(exportText("weaponTitle", { name: "{seconds}" })).toBe("{seconds} Weapons");
|
||||
expect(exportText("condition", { index: 2 }, "th")).toBe("เงื่อนไข 2");
|
||||
});
|
||||
|
||||
@@ -41,7 +42,7 @@ describe("export translations", () => {
|
||||
|
||||
describe("image export controls", () => {
|
||||
it("always offers both export modes even without notes", () => {
|
||||
const html = renderToStaticMarkup(<GuideImageExport fileName="guide.png">No notes</GuideImageExport>);
|
||||
const html = renderToStaticMarkup(<LocaleBoundary locale="en"><GuideImageExport fileName="guide.png">No notes</GuideImageExport></LocaleBoundary>);
|
||||
expect(html).toContain("Export image");
|
||||
expect(html).toContain("Export without note");
|
||||
expect(html.match(/<button\b/gu)).toHaveLength(2);
|
||||
@@ -55,7 +56,7 @@ describe("image export controls", () => {
|
||||
it.each(["English note", "โน้ตภาษาไทย"])("marks constellation notes for omission regardless of language: %s", (note) => {
|
||||
const guide = { character: { name: "Raiden", splashImageUrl: "/splash.png" }, coverUrl: "/cover.png" } as PublicGuide;
|
||||
const group = { rows: [], note } as unknown as PublicGuide["constellationGroups"][number];
|
||||
const html = renderToStaticMarkup(createElement(ConstellationImage, { guide, group }));
|
||||
const html = renderToStaticMarkup(<LocaleBoundary locale="en">{createElement(ConstellationImage, { guide, group })}</LocaleBoundary>);
|
||||
expect(html).toContain("Raiden’s Constellations");
|
||||
expect(html).toContain("Team Damage");
|
||||
expect(html).toContain('data-export-note="true"');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type Locale = "th" | "en";
|
||||
import type { Locale } from "@/lib/i18n/locales";
|
||||
export type { Locale } from "@/lib/i18n/locales";
|
||||
|
||||
const english = {
|
||||
exportImage: "Export image",
|
||||
@@ -9,7 +10,7 @@ const english = {
|
||||
damageDescription: "Choose the damage metric for your weapon comparison image.",
|
||||
overallDamage: "Overall Damage",
|
||||
personalDamage: "Personal Damage",
|
||||
weaponTitle: "{name}’s Weapon Comparison",
|
||||
weaponTitle: "{name} Weapons",
|
||||
condition: "Condition {index}",
|
||||
sectionTitle: "Choose what to export",
|
||||
sectionDescription: "Select individual sections or choose All sections to export everything.",
|
||||
@@ -41,7 +42,8 @@ const english = {
|
||||
constellationTitle: "{name}’s Constellations",
|
||||
teamDamage: "Team Damage",
|
||||
rotation: "Rotation duration: {seconds} seconds",
|
||||
teamCredit: "By YT Genshin Is Not a Mobile Game",
|
||||
teamCredit: "Genshin isnt Mobile Game",
|
||||
watermark: "Genshin isnt Mobile Game",
|
||||
disclaimer: "Results cannot be directly compared with other theorycrafters’ calculations,\nas each uses different stats.\nThe figures shown may change in future updates.",
|
||||
overviewDownloaded: "Overview image downloaded.",
|
||||
downloadPng: "Download PNG {width}×{height}",
|
||||
@@ -60,7 +62,7 @@ const thai: Record<ExportMessageKey, string> = {
|
||||
damageDescription: "เลือกค่าที่ต้องการใช้สร้างภาพเปรียบเทียบอาวุธ",
|
||||
overallDamage: "ดาเมจทั้งทีม",
|
||||
personalDamage: "ดาเมจส่วนตัว",
|
||||
weaponTitle: "เปรียบเทียบอาวุธของ {name}",
|
||||
weaponTitle: "อาวุธของ {name}",
|
||||
condition: "เงื่อนไข {index}",
|
||||
sectionTitle: "เลือกข้อมูลสำหรับส่งออก",
|
||||
sectionDescription: "เลือกได้หลายส่วน หรือเลือกทั้งหมดเพื่อส่งออกทุกส่วน",
|
||||
@@ -78,7 +80,7 @@ const thai: Record<ExportMessageKey, string> = {
|
||||
goblet: "ถ้วย",
|
||||
circlet: "หัว",
|
||||
artifactRecommendation: "อาร์ติแฟกต์และสแตทที่แนะนำ",
|
||||
artifactTitle: "Artifact & Stats",
|
||||
artifactTitle: "อาร์ติแฟกต์และค่าสถานะ",
|
||||
lateGame: "เลทเกม",
|
||||
earlyGame: "ต้นเกม",
|
||||
minimumStats: "ค่าสถานะต่ำๆที่ควรจะปั้นไว้",
|
||||
@@ -92,7 +94,8 @@ const thai: Record<ExportMessageKey, string> = {
|
||||
constellationTitle: "กลุ่มดาวของ {name}",
|
||||
teamDamage: "ดาเมจทั้งทีม",
|
||||
rotation: "ระยะเวลาโรเทชั่น {seconds} วิ",
|
||||
teamCredit: "By YT เกนชินไม่ใช่เกมมือถือ",
|
||||
teamCredit: "Genshin isnt Mobile Game",
|
||||
watermark: "Genshin isnt Mobile Game",
|
||||
disclaimer: "ไม่สามารถเอาไปเปรียบเทียบกับการคำนวณของ TC ท่านอื่นๆได้\nเพราะแต่ละคนใช้สแตทในการคำนวณที่ไม่เหมือนกัน\nและตารางที่เห็นอาจมีการเปลี่ยนแปลงได้ในอนาคต",
|
||||
overviewDownloaded: "ดาวน์โหลดภาพ Overview แล้ว",
|
||||
downloadPng: "ดาวน์โหลด PNG {width}×{height}",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { translate } from "@/lib/i18n/messages";
|
||||
import type { Locale } from "@/lib/i18n/locales";
|
||||
|
||||
export type GuideValidationSection = "weapon" | "artifact" | "constellations" | "team" | "extra";
|
||||
|
||||
interface ValidationIssue {
|
||||
@@ -34,30 +37,30 @@ function indexAfter(path: readonly PropertyKey[], key: string): number | null {
|
||||
return position >= 0 && typeof value === "number" ? value : null;
|
||||
}
|
||||
|
||||
function issueContext(section: GuideValidationSection, path: readonly PropertyKey[], teamNames?: readonly string[]): string {
|
||||
function issueContext(section: GuideValidationSection, path: readonly PropertyKey[], teamNames?: readonly string[], locale: Locale = "th"): string {
|
||||
const rowIndex = indexAfter(path, "rows");
|
||||
const groupIndex = indexAfter(path, "groups");
|
||||
const teamIndex = indexAfter(path, "teams");
|
||||
const memberIndex = indexAfter(path, "members");
|
||||
const sectionIndex = indexAfter(path, "sections");
|
||||
|
||||
if (section === "weapon" && groupIndex === null && rowIndex !== null) return `อาวุธอันดับ ${rowIndex + 1}`;
|
||||
if (section === "weapon" && groupIndex === null && rowIndex !== null) return translate("อาวุธอันดับ {rank}", { rank: rowIndex + 1 }, locale);
|
||||
if (section === "weapon" && groupIndex !== null) {
|
||||
return rowIndex === null ? `Condition ${groupIndex + 1}` : `Condition ${groupIndex + 1} · อาวุธอันดับ ${rowIndex + 1}`;
|
||||
return rowIndex === null ? `Condition ${groupIndex + 1}` : translate("Condition {index} · อาวุธอันดับ {rank}", { index: groupIndex + 1, rank: rowIndex + 1 }, locale);
|
||||
}
|
||||
if (section === "artifact" && groupIndex !== null) {
|
||||
const group = groupIndex === 0 ? "Late Game" : "Early Game";
|
||||
return rowIndex === null ? `Artifact ${group}` : `Artifact ${group} อันดับ ${rowIndex + 1}`;
|
||||
return rowIndex === null ? `Artifact ${group}` : translate("Artifact {name} อันดับ {rank}", { name: group, rank: rowIndex + 1 }, locale);
|
||||
}
|
||||
if (section === "constellations" && groupIndex !== null) {
|
||||
return rowIndex === null ? `Condition ${groupIndex + 1}` : `Condition ${groupIndex + 1} · C${rowIndex + 1}`;
|
||||
}
|
||||
if (section === "team" && teamIndex !== null) {
|
||||
const teamName = teamNames?.[teamIndex]?.trim() || `Team ${teamIndex + 1}`;
|
||||
return memberIndex === null ? teamName : `${teamName} · ตัวละคร ${memberIndex + 1}`;
|
||||
return memberIndex === null ? teamName : translate("{name} · ตัวละคร {index}", { name: teamName, index: memberIndex + 1 }, locale);
|
||||
}
|
||||
if (section === "extra" && sectionIndex !== null) {
|
||||
return rowIndex === null ? `Extra section ${sectionIndex + 1}` : `Extra section ${sectionIndex + 1} · แถว ${rowIndex + 1}`;
|
||||
return rowIndex === null ? `Extra section ${sectionIndex + 1}` : translate("Extra section {index} · แถว {row}", { index: sectionIndex + 1, row: rowIndex + 1 }, locale);
|
||||
}
|
||||
return sectionLabels[section];
|
||||
}
|
||||
@@ -74,7 +77,7 @@ function issueMessage(issue: ValidationIssue): string {
|
||||
export function formatGuideValidationIssues(
|
||||
section: GuideValidationSection,
|
||||
issues: readonly ValidationIssue[],
|
||||
options: { teamNames?: readonly string[] } = {},
|
||||
options: { teamNames?: readonly string[]; locale?: Locale } = {},
|
||||
): string[] {
|
||||
return issues.map((issue) => `${issueContext(section, issue.path, options.teamNames)}: ${issueMessage(issue)}`);
|
||||
return issues.map((issue) => `${issueContext(section, issue.path, options.teamNames, options.locale)}: ${translate(issueMessage(issue), {}, options.locale)}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user