Commit 8c215ebd authored by BBeretta's avatar BBeretta

feat/language-translation (Filter, CardDetail)

parent f4da8169
Pipeline #27261 failed with stages
in 6 minutes
...@@ -15,12 +15,14 @@ import { ...@@ -15,12 +15,14 @@ import {
import { CardEffectText, IconFont, ScrollableArea, YgoCard } from "@/ui/Shared"; import { CardEffectText, IconFont, ScrollableArea, YgoCard } from "@/ui/Shared";
import styles from "./CardDetail.module.scss"; import styles from "./CardDetail.module.scss";
import { useTranslation } from "react-i18next";
export const CardDetail: React.FC<{ export const CardDetail: React.FC<{
code: number; code: number;
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
}> = ({ code, open, onClose }) => { }> = ({ code, open, onClose }) => {
const { t: i18n } = useTranslation("CardDetails");
const [card, setCard] = useState<CardMeta>(); const [card, setCard] = useState<CardMeta>();
useEffect(() => { useEffect(() => {
setCard(fetchCard(code)); setCard(fetchCard(code));
...@@ -44,20 +46,20 @@ export const CardDetail: React.FC<{ ...@@ -44,20 +46,20 @@ export const CardDetail: React.FC<{
const result: DescriptionsProps["items"] = []; const result: DescriptionsProps["items"] = [];
if (card?.data.level) { if (card?.data.level) {
result.push({ result.push({
label: "等级", label: i18n("Level"),
children: card?.data.level, children: card?.data.level,
}); });
} }
result.push({ result.push({
label: "类型", label: i18n("Type"),
children: cardType, children: cardType,
span: 2, span: 2,
}); });
if (card?.data.attribute) { if (card?.data.attribute) {
result.push({ result.push({
label: "属性", label: i18n("Attribute"),
children: fetchStrings( children: fetchStrings(
Region.System, Region.System,
Attribute2StringCodeMap.get(card?.data.attribute ?? 0) || 0, Attribute2StringCodeMap.get(card?.data.attribute ?? 0) || 0,
...@@ -67,7 +69,7 @@ export const CardDetail: React.FC<{ ...@@ -67,7 +69,7 @@ export const CardDetail: React.FC<{
if (card?.data.race) { if (card?.data.race) {
result.push({ result.push({
label: "种族", label: i18n("Race"),
children: fetchStrings( children: fetchStrings(
Region.System, Region.System,
Race2StringCodeMap.get(card?.data.race ?? 0) || 0, Race2StringCodeMap.get(card?.data.race ?? 0) || 0,
...@@ -78,20 +80,20 @@ export const CardDetail: React.FC<{ ...@@ -78,20 +80,20 @@ export const CardDetail: React.FC<{
if (isMonster(card?.data.type ?? 0)) { if (isMonster(card?.data.type ?? 0)) {
result.push({ result.push({
label: "攻击力", label: i18n("Attack"),
children: card?.data.atk, children: card?.data.atk,
}); });
if (!isLinkMonster(card?.data.type ?? 0)) { if (!isLinkMonster(card?.data.type ?? 0)) {
result.push({ result.push({
label: "守备力", label: i18n("Defence"),
children: card?.data.def, children: card?.data.def,
}); });
} }
if (card?.data.lscale) { if (card?.data.lscale) {
result.push({ result.push({
label: "灵摆刻度", label: i18n("PendulumScale"),
children: ( children: (
<> <>
{card.data.lscale} - {card.data.rscale} {card.data.lscale} - {card.data.rscale}
...@@ -125,7 +127,7 @@ export const CardDetail: React.FC<{ ...@@ -125,7 +127,7 @@ export const CardDetail: React.FC<{
size="small" size="small"
items={desc.filter(Boolean).map((d, i) => ({ items={desc.filter(Boolean).map((d, i) => ({
label: label:
desc.length > 1 ? (i ? "怪兽效果" : "灵摆效果") : "卡片效果", desc.length > 1 ? (i ? i18n("MonsterEffect") : i18n("PendulumEffect")) : i18n("CardEffect"),
span: 3, span: 3,
children: <CardEffectText desc={d} />, children: <CardEffectText desc={d} />,
}))} }))}
......
...@@ -17,6 +17,7 @@ import { ...@@ -17,6 +17,7 @@ import {
import { FtsConditions } from "@/middleware/sqlite/fts"; import { FtsConditions } from "@/middleware/sqlite/fts";
import styles from "./Filter.module.scss"; import styles from "./Filter.module.scss";
import { useTranslation } from "react-i18next";
const levels = Array.from({ length: 12 }, (_, index) => ({ const levels = Array.from({ length: 12 }, (_, index) => ({
value: index + 1, value: index + 1,
...@@ -47,12 +48,15 @@ export const Filter: React.FC<{ ...@@ -47,12 +48,15 @@ export const Filter: React.FC<{
value: key, value: key,
label: fetchStrings(Region.System, value), label: fetchStrings(Region.System, value),
})); }));
const { t: i18n } = useTranslation("Filter");
const T = [ const T = [
[genOptions(Attribute2StringCodeMap), "属性", "attributes"], [genOptions(Attribute2StringCodeMap), i18n("Attribute"), "attributes"],
[genOptions(Race2StringCodeMap), "种族", "races"], [genOptions(Race2StringCodeMap), i18n("Race"), "races"],
[genOptions(Type2StringCodeMap), "类型", "types"], [genOptions(Type2StringCodeMap), i18n("Type"), "types"],
[levels, "星级", "levels"], [levels, i18n("Level"), "levels"],
[lscales, "灵摆刻度", "lscales"], [lscales, i18n("PendulumScale"), "lscales"],
] as const; ] as const;
const handleInputNumberChange = const handleInputNumberChange =
...@@ -68,7 +72,7 @@ export const Filter: React.FC<{ ...@@ -68,7 +72,7 @@ export const Filter: React.FC<{
return ( return (
<> <>
<div className={styles.title}>卡片筛选</div> <div className={styles.title}>{i18n("CardFilter")}</div>
<div className={styles.form}> <div className={styles.form}>
{T.map(([options, title, key]) => ( {T.map(([options, title, key]) => (
<Item title={title} key={key}> <Item title={title} key={key}>
...@@ -79,31 +83,31 @@ export const Filter: React.FC<{ ...@@ -79,31 +83,31 @@ export const Filter: React.FC<{
/> />
</Item> </Item>
))} ))}
<Item title="攻击力" showTip> <Item title={i18n("Attack")} showTip>
<div className={styles.number}> <div className={styles.number}>
<CustomInputNumber <CustomInputNumber
placeholder="最小值" placeholder={i18n("Minimum")}
onChange={handleInputNumberChange("atk", "min")} onChange={handleInputNumberChange("atk", "min")}
value={newConditions.atk.min} value={newConditions.atk.min}
/> />
<span className={styles.divider}>~</span> <span className={styles.divider}>~</span>
<CustomInputNumber <CustomInputNumber
placeholder="最大值" placeholder={i18n("Maximum")}
onChange={handleInputNumberChange("atk", "max")} onChange={handleInputNumberChange("atk", "max")}
value={newConditions.atk.max} value={newConditions.atk.max}
/> />
</div> </div>
</Item> </Item>
<Item title="守备力" showTip> <Item title={i18n("Defense")} showTip>
<div className={styles.number}> <div className={styles.number}>
<CustomInputNumber <CustomInputNumber
placeholder="最小值" placeholder={i18n("Minimum")}
onChange={handleInputNumberChange("def", "min")} onChange={handleInputNumberChange("def", "min")}
value={newConditions.def.min} value={newConditions.def.min}
/> />
<span className={styles.divider}>~</span> <span className={styles.divider}>~</span>
<CustomInputNumber <CustomInputNumber
placeholder="最大值" placeholder={i18n("Maximum")}
onChange={handleInputNumberChange("def", "max")} onChange={handleInputNumberChange("def", "max")}
value={newConditions.def.max} value={newConditions.def.max}
/> />
...@@ -117,10 +121,10 @@ export const Filter: React.FC<{ ...@@ -117,10 +121,10 @@ export const Filter: React.FC<{
onConfirm(newConditions); onConfirm(newConditions);
}} }}
> >
确定 {i18n("Confirm")}
</Button> </Button>
<Button type="text" onClick={onCancel}> <Button type="text" onClick={onCancel}>
&nbsp; {i18n("Cancel")}
</Button> </Button>
</div> </div>
</> </>
...@@ -151,12 +155,13 @@ const CustomSelect: React.FC<{ ...@@ -151,12 +155,13 @@ const CustomSelect: React.FC<{
defaultValue: number[]; defaultValue: number[];
onChange: (values: number[]) => void; onChange: (values: number[]) => void;
}> = ({ options, defaultValue, onChange }) => { }> = ({ options, defaultValue, onChange }) => {
const { t: i18n } = useTranslation("Filter");
return ( return (
<Select <Select
mode="multiple" mode="multiple"
allowClear allowClear
style={{ width: "100%" }} style={{ width: "100%" }}
placeholder="请选择" placeholder={i18n("Select")}
options={options} options={options}
defaultValue={defaultValue} defaultValue={defaultValue}
onChange={onChange} onChange={onChange}
......
...@@ -57,6 +57,7 @@ import { ...@@ -57,6 +57,7 @@ import {
editingDeckToIDeck, editingDeckToIDeck,
iDeckToEditingDeck, iDeckToEditingDeck,
} from "./utils"; } from "./utils";
import { useTranslation } from "react-i18next";
export const loader: LoaderFunction = async () => { export const loader: LoaderFunction = async () => {
// 必须先加载卡组,不然页面会崩溃 // 必须先加载卡组,不然页面会崩溃
...@@ -240,11 +241,13 @@ export const DeckEditor: React.FC<{ ...@@ -240,11 +241,13 @@ export const DeckEditor: React.FC<{
event.preventDefault(); event.preventDefault();
}; };
const { t: i18n } = useTranslation("BuildDeck");
return ( return (
<div className={styles.container}> <div className={styles.container}>
<Space className={styles.title}> <Space className={styles.title}>
<Input <Input
placeholder="请输入卡组名字" placeholder={i18n("EnterTheDeckName")}
bordered={false} bordered={false}
prefix={<EditOutlined />} prefix={<EditOutlined />}
style={{ width: "8.8rem" }} style={{ width: "8.8rem" }}
...@@ -258,7 +261,7 @@ export const DeckEditor: React.FC<{ ...@@ -258,7 +261,7 @@ export const DeckEditor: React.FC<{
icon={<SwapOutlined />} icon={<SwapOutlined />}
onClick={onShuffle} onClick={onShuffle}
> >
打乱 {i18n("Shuffle")}
</Button> </Button>
<Button <Button
type="text" type="text"
...@@ -266,7 +269,7 @@ export const DeckEditor: React.FC<{ ...@@ -266,7 +269,7 @@ export const DeckEditor: React.FC<{
icon={<RetweetOutlined />} icon={<RetweetOutlined />}
onClick={onSort} onClick={onSort}
> >
排序 {i18n("Sort")}
</Button> </Button>
<Button <Button
type="text" type="text"
...@@ -274,7 +277,7 @@ export const DeckEditor: React.FC<{ ...@@ -274,7 +277,7 @@ export const DeckEditor: React.FC<{
icon={<DeleteOutlined />} icon={<DeleteOutlined />}
onClick={onClear} onClick={onClear}
> >
清空 {i18n("Clear")}
</Button> </Button>
<Button <Button
type="text" type="text"
...@@ -282,7 +285,7 @@ export const DeckEditor: React.FC<{ ...@@ -282,7 +285,7 @@ export const DeckEditor: React.FC<{
icon={<UndoOutlined />} icon={<UndoOutlined />}
onClick={() => onReset()} onClick={() => onReset()}
> >
重置 {i18n("Reset")}
</Button> </Button>
<Button <Button
type={snapEditDeck.edited ? "primary" : "text"} type={snapEditDeck.edited ? "primary" : "text"}
...@@ -290,9 +293,9 @@ export const DeckEditor: React.FC<{ ...@@ -290,9 +293,9 @@ export const DeckEditor: React.FC<{
icon={<CheckOutlined />} icon={<CheckOutlined />}
onClick={() => onSave()} onClick={() => onSave()}
> >
保存 {i18n("Save")}
</Button> </Button>
<Tooltip title="双击添加卡片,单击右键删除卡片,按下滑轮在主卡组和副卡组之间切换卡片"> <Tooltip title={i18n("QuestionCircleTooltip")}>
<QuestionCircleOutlined /> <QuestionCircleOutlined />
</Tooltip> </Tooltip>
</Space> </Space>
...@@ -350,18 +353,20 @@ const Search: React.FC = () => { ...@@ -350,18 +353,20 @@ const Search: React.FC = () => {
); );
}; };
const { t } = useTranslation("BuildDeck");
const dropdownOptions: MenuProps["items"] = ( const dropdownOptions: MenuProps["items"] = (
[ [
["从新到旧", () => setSortRef((a, b) => b.id - a.id)], [t("FromNewToOld"), () => setSortRef((a, b) => b.id - a.id)],
["从旧到新", () => setSortRef((a, b) => a.id - b.id)], [t("FromOldToNew"), () => setSortRef((a, b) => a.id - b.id)],
["攻击力从高到低", genSort("atk", -1)], [t("AttackPowerFromHighToLow"), genSort("atk", -1)],
["攻击力从低到高", genSort("atk")], [t("AttackPowerFromLowToHigh"), genSort("atk")],
["守备力从高到低", genSort("def", -1)], [t("DefensePowerFromHighToLow"), genSort("def", -1)],
["守备力从低到高", genSort("def")], [t("DefensePowerFromLowToHigh"), genSort("def")],
["星/阶/刻/Link从高到低", genSort("level", -1)], [t("StarsRanksLevelsLinkFromHighToLow"), genSort("level", -1)],
["星/阶/刻/Link从低到高", genSort("level")], [t("StarsRanksLevelsLinkFromLowToHigh"), genSort("level")],
["灵摆刻度从高到低", genSort("lscale", -1)], [t("PendulumScaleFromHighToLow"), genSort("lscale", -1)],
["灵摆刻度从低到高", genSort("lscale")], [t("PendulumScaleFromLowToHigh"), genSort("lscale")],
] as const ] as const
).map(([label, onClick], key) => ({ key, label, onClick })); ).map(([label, onClick], key) => ({ key, label, onClick }));
...@@ -415,11 +420,13 @@ const Search: React.FC = () => { ...@@ -415,11 +420,13 @@ const Search: React.FC = () => {
if (viewport) viewport.scrollTop = 0; if (viewport) viewport.scrollTop = 0;
}, []); }, []);
const { t: i18n } = useTranslation("BuildDeck");
return ( return (
<div className={styles.container} ref={dropRef}> <div className={styles.container} ref={dropRef}>
<div className={styles.title}> <div className={styles.title}>
<Input <Input
placeholder="关键词(空格分隔)" placeholder={i18n("KeywordsPlaceholder")}
bordered={false} bordered={false}
suffix={ suffix={
<Button <Button
...@@ -445,7 +452,7 @@ const Search: React.FC = () => { ...@@ -445,7 +452,7 @@ const Search: React.FC = () => {
icon={<FilterOutlined />} icon={<FilterOutlined />}
onClick={showFilterModal} onClick={showFilterModal}
> >
筛选 {i18n("Filter")}
</Button> </Button>
<Dropdown <Dropdown
menu={{ items: dropdownOptions }} menu={{ items: dropdownOptions }}
...@@ -459,7 +466,7 @@ const Search: React.FC = () => { ...@@ -459,7 +466,7 @@ const Search: React.FC = () => {
icon={<SortAscendingOutlined />} icon={<SortAscendingOutlined />}
> >
<span> <span>
排列 {i18n("SortBy")}
<span className={styles["search-count"]}> <span className={styles["search-count"]}>
({searchResult.length}) ({searchResult.length})
</span> </span>
...@@ -477,7 +484,7 @@ const Search: React.FC = () => { ...@@ -477,7 +484,7 @@ const Search: React.FC = () => {
handleSearch(emptySearchConditions); handleSearch(emptySearchConditions);
}} }}
> >
重置 {i18n("Reset")}
</Button> </Button>
</div> </div>
<ScrollableArea className={styles["search-cards-container"]} ref={ref}> <ScrollableArea className={styles["search-cards-container"]} ref={ref}>
......
...@@ -24,8 +24,8 @@ export const I18NSelector: React.FC = () => { ...@@ -24,8 +24,8 @@ export const I18NSelector: React.FC = () => {
{ value: "en", label: "English" }, { value: "en", label: "English" },
{ value: "fr", label: "Français" }, { value: "fr", label: "Français" },
{ value: "jp", label: "日本語" }, { value: "jp", label: "日本語" },
{ value: "br", label: "português do Brasil" }, { value: "br", label: "Português do Brasil" },
{ value: "pt", label: "português" }, { value: "pt", label: "Português" },
{ value: "es", label: "Castellano" }, { value: "es", label: "Castellano" },
]} ]}
/> />
......
...@@ -36,5 +36,58 @@ ...@@ -36,5 +36,58 @@
"ReplayDesc": "自由查看进行过的决斗,回味那些精彩的逆转瞬间。", "ReplayDesc": "自由查看进行过的决斗,回味那些精彩的逆转瞬间。",
"WIPTitle": "开发中...", "WIPTitle": "开发中...",
"WIPDesc": "其他功能敬请期待。" "WIPDesc": "其他功能敬请期待。"
},
"BuildDeck": {
"EnterTheDeckName": "请输入卡组名字",
"Shuffle": "打乱",
"Sort": "排序",
"Clear": "清空",
"Reset": "重置",
"Save": "保存",
"QuestionCircleTooltip": "双击添加卡片,单击右键删除卡片,按下滑轮在主卡组和副卡组之间切换卡片",
"Filter": "筛选",
"SortBy": "排列 ",
"KeywordsPlaceholder": "关键词(空格分隔)",
"FromNewToOld": "从新到旧",
"FromOldToNew": "从旧到新",
"AttackPowerFromHighToLow": "攻击力从高到低",
"AttackPowerFromLowToHigh": "攻击力从低到高",
"DefensePowerFromHighToLow": "守备力从高到低",
"DefensePowerFromLowToHigh": "守备力从低到高",
"StarsRanksLevelsLinkFromHighToLow": "星/阶/刻/Link从高到低",
"StarsRanksLevelsLinkFromLowToHigh": "星/阶/刻/Link从低到高",
"PendulumScaleFromHighToLow": "灵摆刻度从高到低",
"PendulumScaleFromLowToHigh": "灵摆刻度从低到高"
},
"Filter": {
"CardFilter": "卡片筛选",
"Attribute": "属性",
"Race": "种族",
"Type": "类型",
"Level": "星级",
"PendulumScale": "灵摆刻度",
"Attack": "攻击力",
"Defense": "守备力",
"Select": "请选择",
"Minimum": "最小值",
"Maximum": "最大值",
"Confirm": "确 定",
"Cancel": "取 消"
},
"CardDetails": {
"Level": "等级",
"Type": "类型",
"Attribute": "类型",
"Race": "种族",
"Attack": "攻击力",
"Defence": "守备力",
"PendulumScale": "灵摆刻度",
"MonsterEffect": "怪兽效果",
"PendulumEffect": "灵摆效果",
"CardEffect": "卡片效果"
},
"Store": {
"CannotAddTokens": "不能添加衍生物",
"CardTypeDoesNotMatch": "卡片种类不符合"
} }
} }
\ No newline at end of file
...@@ -36,5 +36,58 @@ ...@@ -36,5 +36,58 @@
"ReplayDesc": "Freely watch past duels and relive those exciting moments of reversal.", "ReplayDesc": "Freely watch past duels and relive those exciting moments of reversal.",
"WIPTitle": "Under development...", "WIPTitle": "Under development...",
"WIPDesc": "Stay tuned for other features." "WIPDesc": "Stay tuned for other features."
},
"BuildDeck": {
"EnterTheDeckName": "Deck name",
"Shuffle": "Shuffle",
"Sort": "Sort",
"Clear": "Clear",
"Reset": "Reset",
"Save": "Save",
"QuestionCircleTooltip": "Double-click to add a card, right-click to remove a card, press the mouse wheel to switch cards between the main deck and the side deck.",
"Filter": "Filter",
"SortBy": "Sort By ",
"KeywordsPlaceholder": "Keywords (separated by spaces)",
"FromNewToOld": "From new to old",
"FromOldToNew": "From old to new",
"AttackPowerFromHighToLow": "Attack power from high to low",
"AttackPowerFromLowToHigh": "Attack power from low to high",
"DefensePowerFromHighToLow": "Defense power from high to low",
"DefensePowerFromLowToHigh": "Defense power from low to high",
"StarsRanksLevelsLinkFromHighToLow": "Stars/Ranks/Levels/Link from high to low",
"StarsRanksLevelsLinkFromLowToHigh": "Stars/Ranks/Levels/Link from low to high",
"PendulumScaleFromHighToLow": "Pendulum Scale from high to low",
"PendulumScaleFromLowToHigh": "Pendulum Scale from low to high"
},
"Filter": {
"CardFilter": "Card Filter",
"Attribute": "Attribute",
"Race": "Race",
"Type": "Type",
"Level": "Level",
"PendulumScale": "Pendulum Scale",
"Attack": "Attack",
"Defense": "Defense",
"Select": "-Select-",
"Minimum": "Min",
"Maximum": "Max",
"Confirm": "Confirm",
"Cancel": "Cancel"
},
"CardDetails": {
"Level": "Level",
"Type": "Type",
"Attribute": "Attribute",
"Race": "Race",
"Attack": "Attack",
"Defence": "Defence",
"PendulumScale": "Pendulum Scale",
"MonsterEffect": "Monster Effect",
"PendulumEffect": "Pendulum Effect",
"CardEffect": "Card Effect"
},
"Store": {
"CannotAddTokens": "Cannot add Tokens",
"CardTypeDoesNotMatch": "The card type does not match"
} }
} }
\ No newline at end of file
...@@ -18,11 +18,19 @@ const resources = { ...@@ -18,11 +18,19 @@ const resources = {
Header: translationChinese.Header, Header: translationChinese.Header,
Start: translationChinese.Start, Start: translationChinese.Start,
Match: translationChinese.Match, Match: translationChinese.Match,
BuildDeck: translationChinese.BuildDeck,
Filter: translationChinese.Filter,
CardDetails: translationChinese.CardDetails,
Store: translationChinese.Store,
}, },
en: { en: {
Header: translationEnglish.Header, Header: translationEnglish.Header,
Start: translationEnglish.Start, Start: translationEnglish.Start,
Match: translationEnglish.Match, Match: translationEnglish.Match,
BuildDeck: translationEnglish.BuildDeck,
Filter: translationEnglish.Filter,
CardDetails: translationEnglish.CardDetails,
Store: translationChinese.Store,
}, },
es: { es: {
Header: translationSpanish.Header, Header: translationSpanish.Header,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment