From fb8af2626c67205e1b89be0af93610a6ccd65fec Mon Sep 17 00:00:00 2001 From: Yilun Wu Date: Wed, 23 Sep 2026 23:10:53 -0400 Subject: [PATCH 1/6] Store Chinese move names with their source (V3 Milestone 4, part 1) - moves.name_zh / name_zh_source ('wiki' | 'translation') / name_zh_source_url, with check constraints added by hand to the reviewed migration (autogenerate misses them) - seed validation requires a Chinese name for every move; 102 existing moves named: 6 from BWIKI combat-art pages, 96 translations - API returns the Chinese names; interim frontend display to be replaced by the language switch Co-Authored-By: Claude Opus 5.5 --- .../01e9ec08d5ac_add_move_chinese_names.py | 46 + backend/app/db/models.py | 18 +- backend/app/models/analytics.py | 1 + backend/app/models/boss.py | 17 +- backend/app/seed.py | 14 +- backend/app/services/boss_service.py | 3 + .../app/services/game_analytics_service.py | 5 +- backend/seed/bosses.json | 850 ++++++++++++++---- backend/tests/test_move_chinese_names.py | 77 ++ backend/tests/test_sekiro_analytics.py | 1 + .../src/components/AnalyticsPanel.test.tsx | 4 +- .../src/components/AttemptHistory.test.tsx | 2 +- frontend/src/components/ChineseMoveName.tsx | 31 + .../src/components/MovesetReference.test.tsx | 25 +- frontend/src/components/MovesetReference.tsx | 2 + .../src/components/RecordAttemptForm.test.tsx | 4 +- frontend/src/components/RecordAttemptForm.tsx | 3 +- frontend/src/index.css | 21 + frontend/src/pages/BossDashboardPage.test.tsx | 2 +- .../src/pages/SekiroDashboardPage.test.tsx | 2 + frontend/src/pages/SekiroDashboardPage.tsx | 3 +- frontend/src/types/index.ts | 7 + frontend/src/utils/moves.test.ts | 4 +- frontend/src/utils/moves.ts | 13 +- 24 files changed, 967 insertions(+), 188 deletions(-) create mode 100644 backend/alembic/versions/01e9ec08d5ac_add_move_chinese_names.py create mode 100644 backend/tests/test_move_chinese_names.py create mode 100644 frontend/src/components/ChineseMoveName.tsx diff --git a/backend/alembic/versions/01e9ec08d5ac_add_move_chinese_names.py b/backend/alembic/versions/01e9ec08d5ac_add_move_chinese_names.py new file mode 100644 index 0000000..3f7c39f --- /dev/null +++ b/backend/alembic/versions/01e9ec08d5ac_add_move_chinese_names.py @@ -0,0 +1,46 @@ +"""add move chinese names + +Revision ID: 01e9ec08d5ac +Revises: 30b90438d428 +Create Date: 2026-09-23 22:26:38.514977 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '01e9ec08d5ac' +down_revision: Union[str, Sequence[str], None] = '30b90438d428' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema: Chinese move names, from a Chinese wiki or marked as translations. + + Existing rows get NULLs, which satisfy the checks; the next seed sync fills them in. + """ + op.add_column('moves', sa.Column('name_zh', sa.String(length=200), nullable=True)) + op.add_column('moves', sa.Column('name_zh_source', sa.String(length=20), nullable=True)) + op.add_column('moves', sa.Column('name_zh_source_url', sa.Text(), nullable=True)) + # Autogenerate does not detect check constraints; these mirror models.Move. + op.create_check_constraint( + 'ck_moves_name_zh_source', 'moves', "name_zh_source IS NULL OR name_zh_source IN ('wiki', 'translation')" + ) + op.create_check_constraint('ck_moves_name_zh_has_source', 'moves', '(name_zh IS NULL) = (name_zh_source IS NULL)') + op.create_check_constraint( + 'ck_moves_name_zh_source_url', 'moves', "(name_zh_source = 'wiki') = (name_zh_source_url IS NOT NULL)" + ) + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_constraint('ck_moves_name_zh_source_url', 'moves', type_='check') + op.drop_constraint('ck_moves_name_zh_has_source', 'moves', type_='check') + op.drop_constraint('ck_moves_name_zh_source', 'moves', type_='check') + op.drop_column('moves', 'name_zh_source_url') + op.drop_column('moves', 'name_zh_source') + op.drop_column('moves', 'name_zh') diff --git a/backend/app/db/models.py b/backend/app/db/models.py index ab2c757..3e3bb95 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -63,7 +63,18 @@ class BossPhase(Base): class Move(Base): __tablename__ = "moves" - __table_args__ = (UniqueConstraint("boss_id", "slug"),) + __table_args__ = ( + UniqueConstraint("boss_id", "slug"), + CheckConstraint( + "name_zh_source IS NULL OR name_zh_source IN ('wiki', 'translation')", + name="ck_moves_name_zh_source", + ), + CheckConstraint("(name_zh IS NULL) = (name_zh_source IS NULL)", name="ck_moves_name_zh_has_source"), + # A name taken from a Chinese wiki must say which page; a translation has no page. + CheckConstraint( + "(name_zh_source = 'wiki') = (name_zh_source_url IS NOT NULL)", name="ck_moves_name_zh_source_url" + ), + ) id: Mapped[int] = mapped_column(primary_key=True) boss_id: Mapped[int] = mapped_column(ForeignKey("bosses.id", ondelete="CASCADE")) @@ -74,6 +85,11 @@ class Move(Base): telegraph: Mapped[str | None] = mapped_column(Text) counter: Mapped[str | None] = mapped_column(Text) common_mistakes: Mapped[str | None] = mapped_column(Text) + # Chinese name: either the name used by a Chinese wiki ('wiki', with the page in + # name_zh_source_url) or a translation ('translation'), never presented as official. + name_zh: Mapped[str | None] = mapped_column(String(200)) + name_zh_source: Mapped[str | None] = mapped_column(String(20)) + name_zh_source_url: Mapped[str | None] = mapped_column(Text) boss: Mapped[Boss] = relationship(back_populates="moves") phase_links: Mapped[list["PhaseMove"]] = relationship(back_populates="move", cascade="all, delete-orphan") diff --git a/backend/app/models/analytics.py b/backend/app/models/analytics.py index eecafbb..04ec0ca 100644 --- a/backend/app/models/analytics.py +++ b/backend/app/models/analytics.py @@ -53,6 +53,7 @@ class RecentAttempt(BaseModel): phase_reached: int failure_move_id: str | None = None failure_move_name: str | None = None + failure_move_name_zh: str | None = None failure_category: FailureCategory | None = None diff --git a/backend/app/models/boss.py b/backend/app/models/boss.py index 617de2d..f956ce8 100644 --- a/backend/app/models/boss.py +++ b/backend/app/models/boss.py @@ -1,4 +1,6 @@ -from pydantic import BaseModel +from typing import Literal + +from pydantic import BaseModel, model_validator class BossMove(BaseModel): @@ -9,6 +11,19 @@ class BossMove(BaseModel): telegraph: str | None = None counter: str | None = None common_mistakes: str | None = None + name_zh: str | None = None + # "wiki": the name used by a Chinese wiki, see name_zh_source_url. + # "translation": translated for this app; not an official name. + name_zh_source: Literal["wiki", "translation"] | None = None + name_zh_source_url: str | None = None + + @model_validator(mode="after") + def check_chinese_name_source(self) -> "BossMove": + if (self.name_zh is None) != (self.name_zh_source is None): + raise ValueError(f"move '{self.id}': name_zh and name_zh_source must be set together") + if (self.name_zh_source == "wiki") != (self.name_zh_source_url is not None): + raise ValueError(f"move '{self.id}': a wiki name needs name_zh_source_url; a translation must not have one") + return self class BossPhase(BaseModel): diff --git a/backend/app/seed.py b/backend/app/seed.py index 3c4165b..fea609b 100644 --- a/backend/app/seed.py +++ b/backend/app/seed.py @@ -16,7 +16,17 @@ GAMES = {"sekiro": "Sekiro: Shadows Die Twice"} -MOVE_FIELDS = ("name", "move_type", "description", "telegraph", "counter", "common_mistakes") +MOVE_FIELDS = ( + "name", + "move_type", + "description", + "telegraph", + "counter", + "common_mistakes", + "name_zh", + "name_zh_source", + "name_zh_source_url", +) class SeedError(ValueError): @@ -57,6 +67,8 @@ def _validate(bosses: list[BossData]) -> None: if len(move_ids) != len(set(move_ids)): errors.append(f"boss '{boss.id}' phase {phase.phase_number} lists a move twice") for move in phase.moves: + if move.name_zh is None: + errors.append(f"boss '{boss.id}' move '{move.id}' has no Chinese name (name_zh)") data = move.model_dump(include=set(MOVE_FIELDS)) if move.id not in first_seen: first_seen[move.id] = (phase.phase_number, data) diff --git a/backend/app/services/boss_service.py b/backend/app/services/boss_service.py index 7d5fc0d..72da43f 100644 --- a/backend/app/services/boss_service.py +++ b/backend/app/services/boss_service.py @@ -46,6 +46,9 @@ def get_boss(session: Session, boss_slug: str) -> Boss | None: telegraph=link.move.telegraph, counter=link.move.counter, common_mistakes=link.move.common_mistakes, + name_zh=link.move.name_zh, + name_zh_source=link.move.name_zh_source, + name_zh_source_url=link.move.name_zh_source_url, ) for link in phase.move_links ], diff --git a/backend/app/services/game_analytics_service.py b/backend/app/services/game_analytics_service.py index 635fba4..5314b98 100644 --- a/backend/app/services/game_analytics_service.py +++ b/backend/app/services/game_analytics_service.py @@ -88,7 +88,7 @@ def _boss_rows(session: Session, user_id: int) -> list[BossComparisonRow]: def _recent_attempts(session: Session, user_id: int) -> list[RecentAttempt]: rows = session.execute( - select(db.Attempt, db.Boss.slug, db.Boss.name, db.Move.slug, db.Move.name) + select(db.Attempt, db.Boss.slug, db.Boss.name, db.Move.slug, db.Move.name, db.Move.name_zh) .join(db.Boss, db.Attempt.boss_id == db.Boss.id) .outerjoin(db.Move, db.Attempt.failure_move_id == db.Move.id) .where(db.Attempt.user_id == user_id) @@ -105,9 +105,10 @@ def _recent_attempts(session: Session, user_id: int) -> list[RecentAttempt]: phase_reached=attempt.phase_reached, failure_move_id=move_slug, failure_move_name=move_name, + failure_move_name_zh=move_name_zh, failure_category=FailureCategory(attempt.failure_category) if attempt.failure_category else None, ) - for attempt, boss_slug, boss_name, move_slug, move_name in rows + for attempt, boss_slug, boss_name, move_slug, move_name, move_name_zh in rows ] diff --git a/backend/seed/bosses.json b/backend/seed/bosses.json index 064fbc0..e57a300 100644 --- a/backend/seed/bosses.json +++ b/backend/seed/bosses.json @@ -19,7 +19,10 @@ "description": "Delivers an overhead smash while airborne and connects with a Perilous Thrust upon landing.", "telegraph": "Perilous Attack kanji.", "counter": "In this phase he always follows with a Perilous Thrust, opening him up to an easy Mikiri Counter.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危攻击后续追击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-attack", @@ -28,7 +31,10 @@ "description": "Runs towards Wolf and does a plunging attack.", "telegraph": "Perilous Attack kanji.", "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-grab", @@ -37,7 +43,10 @@ "description": "A sideways jumping slash to the left, then a quick elbow jab; if it connects, he punches Wolf in the stomach.", "telegraph": "Perilous Attack kanji.", "counter": "Striking him during this attack topples his balance, even with a visible block at the end of the move.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·投技", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-thrust", @@ -46,7 +55,10 @@ "description": "Thrusts with his katana.", "telegraph": "Perilous Attack kanji.", "counter": "Mikiri Counter.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·突刺", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-sweep", @@ -55,7 +67,10 @@ "description": "Sweeps the floor and follows up with an arrow shot.", "telegraph": "Perilous Attack kanji.", "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "advance", @@ -64,7 +79,10 @@ "description": "Two horizontal slashes while stepping forth.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "突进", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "chasing-slice", @@ -73,7 +91,10 @@ "description": "Runs a short distance, then slashes twice.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "追击斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "closing-tide", @@ -82,7 +103,10 @@ "description": "Slides and shoots one or two arrows, then rolls forward and slashes once. In Phase 1, it is always his first attack.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "涨潮", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "floating-passage", @@ -91,7 +115,10 @@ "description": "A deadly seven-hit combo ending in a guard-breaking strike. If the target is too far away, he may cancel it at any time into an arrow shot.", "telegraph": "Starts with him sliding to the left.", "counter": "If your posture bar is almost full, deflect the first two hits, then dodge away and punish him once he finishes.", - "common_mistakes": "Getting too far away during the combo - he interrupts it with a Guard Breaking Shot." + "common_mistakes": "Getting too far away during the combo - he interrupts it with a Guard Breaking Shot.", + "name_zh": "绝技·飞渡浮舟", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F" }, { "id": "guard-breaking-shot", @@ -100,7 +127,10 @@ "description": "Notches an arrow with incredible force, capable of breaking guard when released.", "telegraph": null, "counter": null, - "common_mistakes": "Healing in front of him - he answers every use of the Healing Gourd or another healing item with this shot." + "common_mistakes": "Healing in front of him - he answers every use of the Healing Gourd or another healing item with this shot.", + "name_zh": "破防射击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "hawk-shot", @@ -109,7 +139,10 @@ "description": "Stays airborne and shoots rapidly four times.", "telegraph": "Always dashes backward first.", "counter": "Interrupt the move as it starts, or dodge all the arrows by dashing twice toward him.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "鹰射", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "iaijutsu", @@ -118,7 +151,10 @@ "description": "A quick drawn strike with a short closing distance.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "居合斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "receding-tide", @@ -127,7 +163,10 @@ "description": "Slides back and shoots two arrows at the same time, then prepares for an Iaijutsu.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "退潮", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "two-hit-combo", @@ -136,7 +175,10 @@ "description": "Spins his body, slashing, then kicks right after. May follow up with a Guard Breaking Shot.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "二连击", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -151,7 +193,10 @@ "description": "Delivers an overhead smash while airborne and connects with a Perilous Thrust or a Perilous Sweep upon landing.", "telegraph": "Perilous Attack kanji.", "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危攻击后续追击(第二阶段)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-attack", @@ -160,7 +205,10 @@ "description": "Runs towards Wolf and does a plunging attack.", "telegraph": "Perilous Attack kanji.", "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-grab", @@ -169,7 +217,10 @@ "description": "A sideways jumping slash to the left, then a quick elbow jab; if it connects, he punches Wolf in the stomach.", "telegraph": "Perilous Attack kanji.", "counter": "Striking him during this attack topples his balance, even with a visible block at the end of the move.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·投技", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-thrust", @@ -178,7 +229,10 @@ "description": "Thrusts with his katana.", "telegraph": "Perilous Attack kanji.", "counter": "Mikiri Counter.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·突刺", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-sweep", @@ -187,7 +241,10 @@ "description": "Sweeps the floor and follows up with an arrow shot.", "telegraph": "Perilous Attack kanji.", "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "advance", @@ -196,7 +253,10 @@ "description": "Two horizontal slashes while stepping forth.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "突进", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "chasing-slice", @@ -205,7 +265,10 @@ "description": "Runs a short distance, then slashes twice.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "追击斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "closing-tide", @@ -214,7 +277,10 @@ "description": "Slides and shoots one or two arrows, then rolls forward and slashes once. In Phase 1, it is always his first attack.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "涨潮", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "floating-passage", @@ -223,7 +289,10 @@ "description": "A deadly seven-hit combo ending in a guard-breaking strike. If the target is too far away, he may cancel it at any time into an arrow shot.", "telegraph": "Starts with him sliding to the left.", "counter": "If your posture bar is almost full, deflect the first two hits, then dodge away and punish him once he finishes.", - "common_mistakes": "Getting too far away during the combo - he interrupts it with a Guard Breaking Shot." + "common_mistakes": "Getting too far away during the combo - he interrupts it with a Guard Breaking Shot.", + "name_zh": "绝技·飞渡浮舟", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F" }, { "id": "guard-breaking-shot", @@ -232,7 +301,10 @@ "description": "Notches an arrow with incredible force, capable of breaking guard when released.", "telegraph": null, "counter": null, - "common_mistakes": "Healing in front of him - he answers every use of the Healing Gourd or another healing item with this shot." + "common_mistakes": "Healing in front of him - he answers every use of the Healing Gourd or another healing item with this shot.", + "name_zh": "破防射击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "hawk-shot", @@ -241,7 +313,10 @@ "description": "Stays airborne and shoots rapidly four times.", "telegraph": "Always dashes backward first.", "counter": "Interrupt the move as it starts, or dodge all the arrows by dashing twice toward him.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "鹰射", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "iaijutsu", @@ -250,7 +325,10 @@ "description": "A quick drawn strike with a short closing distance.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "居合斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "receding-tide", @@ -259,7 +337,10 @@ "description": "Slides back and shoots two arrows at the same time, then prepares for an Iaijutsu.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "退潮", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "two-hit-combo", @@ -268,7 +349,10 @@ "description": "Spins his body, slashing, then kicks right after. May follow up with a Guard Breaking Shot.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "二连击", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -283,7 +367,10 @@ "description": "Lunges forward, thrusting over a long distance. He always opens Phase 3 with this move. May follow up with a Perilous Grab.", "telegraph": "Perilous Attack kanji.", "counter": null, - "common_mistakes": "Using an item - he answers it with this thrust." + "common_mistakes": "Using an item - he answers it with this thrust.", + "name_zh": "死亡突刺", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "floating-passage-phase-3", @@ -292,7 +379,10 @@ "description": "A deadly seven-hit combo ending in a guard-breaking strike, followed by a delayed Perilous Thrust. If the target is too far away, he may cancel it at any time into an arrow shot.", "telegraph": "Perilous Attack kanji. Starts with him sliding to the left.", "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "绝技·飞渡浮舟(第三阶段)", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F" }, { "id": "perilous-attack-follow-up-phase-3", @@ -301,7 +391,10 @@ "description": "Delivers an overhead smash while airborne, then one of three follow-ups: a Perilous Thrust, a Perilous Sweep, or a quick upward cut that, if deflected, is followed by a delayed Perilous Thrust.", "telegraph": "Perilous Attack kanji. After landing: crouching down means a Perilous Thrust, turning clockwise means a Perilous Sweep, turning anti-clockwise means the quick upward cut.", "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危攻击后续追击(第三阶段)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-grab-phase-3", @@ -310,7 +403,10 @@ "description": "Runs toward Wolf attempting to grab him. If it connects, he throws Wolf to the ground and stabs him in the chest.", "telegraph": "Perilous Attack kanji.", "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·投技(第三阶段)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "lightning-of-tomoe-shot", @@ -319,7 +415,10 @@ "description": "Shoots an electrifying arrow that instantly inflicts Shock.", "telegraph": "Jumps up and imbues his arrow with lightning.", "counter": "Lightning Reversal sends it back for significant posture and vitality damage and briefly paralyzes him. His lightning attacks can also be interrupted by hitting him, or avoided by dashing under him.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "巴之雷·射击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "lightning-of-tomoe-slash", @@ -328,7 +427,10 @@ "description": "Leaps up, then slashes forward in a long horizontal arc of lightning.", "telegraph": "Raises his katana to his right with both hands as it is imbued with lightning.", "counter": "Lightning Reversal sends it back for significant posture and vitality damage and briefly paralyzes him. His lightning attacks can also be interrupted by hitting him, or avoided by dashing under him.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "巴之雷·斩击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "lightning-of-tomoe-smash", @@ -337,7 +439,10 @@ "description": "Leaps up, then smashes straight down with lightning.", "telegraph": "Raises his katana to his left as it is imbued with lightning.", "counter": "Lightning Reversal sends it back for significant posture and vitality damage and briefly paralyzes him. His lightning attacks can also be interrupted by hitting him, or avoided by dashing under him.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "巴之雷·砸击", + "name_zh_source": "translation", + "name_zh_source_url": null } ] } @@ -363,7 +468,10 @@ "description": "Many of Owl's attacks begin with a zig-zag motion designed to confuse the player, leading into one of five possible follow-up attacks.", "telegraph": null, "counter": "Stay ready to react to whichever attack follows.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "之字形移动", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "shuriken-throw", @@ -372,7 +480,10 @@ "description": "A single shuriken projectile attack.", "telegraph": null, "counter": "Block, deflect, or dodge to the side.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "投掷手里剑", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "double-shuriken-flip", @@ -381,7 +492,10 @@ "description": "Two shuriken throws followed by an aerial flip and downward sword strike.", "telegraph": null, "counter": "Deflect/block the shurikens and dodge sideways to avoid the flip, then counter.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "双手里剑 + 后翻", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "shadowfall", @@ -390,7 +504,10 @@ "description": "Unblockable thrust; if it hits, he jumps off and comes down with two huge slashes.", "telegraph": "Charges up before the unblockable thrust.", "counter": "Mikiri the thrust.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "秘传·巨型忍者落杀", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E5%B7%A8%E5%9E%8B%E5%BF%8D%E8%80%85%E8%90%BD%E6%9D%80" }, { "id": "shuriken-slash", @@ -399,7 +516,10 @@ "description": "Throws a shuriken immediately followed by a running slash.", "telegraph": null, "counter": "Block/deflect both. A well-timed dodge to the right is also possible.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "手里剑 + 斩击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-sweep", @@ -408,7 +528,10 @@ "description": "Unblockable sweep attack performed from the side.", "telegraph": null, "counter": "Jump over it and stay close to bait his overhead slash, or jump back for a jump kick to the head.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "large-overhead-slash", @@ -417,7 +540,10 @@ "description": "A very powerful overhead slash that adapts based on the player's response.", "telegraph": "Loads up for the overhead slash.", "counter": "Dodge at the last moment and counter, or step back out of range. Hits can land during the charge-up.", - "common_mistakes": "Dodging too early makes him switch to a quick horizontal attack." + "common_mistakes": "Dodging too early makes him switch to a quick horizontal attack.", + "name_zh": "大幅度下劈", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "backflip", @@ -426,7 +552,10 @@ "description": "Leaps away; deals no vitality damage but significant posture damage.", "telegraph": null, "counter": "Deflect to minimize posture damage.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "后空翻", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "firecrackers", @@ -435,7 +564,10 @@ "description": "Throws blinding firecrackers followed by an explosion; can chain into Shuriken Throw, Shadowfall, or Shuriken Slash.", "telegraph": null, "counter": "Dodge backwards to avoid.", - "common_mistakes": "Dodging too quickly after the throw lets his follow-up slash hit you." + "common_mistakes": "Dodging too quickly after the throw lets his follow-up slash hit you.", + "name_zh": "爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "running-slash", @@ -444,7 +576,10 @@ "description": "A single strong slash performed while approaching the player.", "telegraph": null, "counter": "Block/deflect. Dodging to the left is also possible.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "冲刺斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "five-slash-firecracker-combo", @@ -453,7 +588,10 @@ "description": "Three consecutive sword slashes, followed by an elbow strike, followed by firecrackers that he slashes through.", "telegraph": null, "counter": "Deflect/block the slashes, then dodge right when the firecrackers are thrown for a counter opportunity.", - "common_mistakes": "Getting pushed back into a corner by the combo, where there is no chance of survival." + "common_mistakes": "Getting pushed back into a corner by the combo, where there is no chance of survival.", + "name_zh": "五连斩接爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "double-slash-perilous-sweep", @@ -462,7 +600,10 @@ "description": "Two consecutive slashes ending in an unblockable sweep; can chain into Large Overhead Slash.", "telegraph": null, "counter": "Jump over it and stay close to bait his overhead slash.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "二连斩接危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "stomp-deathblow", @@ -471,7 +612,10 @@ "description": "Raises a foot then stomps down, pinning the player and stabbing through the chest for massive damage. Cannot be blocked and gives no Perilous indicator.", "telegraph": "Raises his foot into the air before stomping down. There is no Perilous indicator.", "counter": "Dodge backwards or safety roll to avoid it.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "踩踏(忍杀)", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -486,7 +630,10 @@ "description": "Many of Owl's attacks begin with a zig-zag motion designed to confuse the player, leading into one of five possible follow-up attacks.", "telegraph": null, "counter": "Stay ready to react to whichever attack follows.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "之字形移动", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "shuriken-throw", @@ -495,7 +642,10 @@ "description": "A single shuriken projectile attack.", "telegraph": null, "counter": "Block, deflect, or dodge to the side.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "投掷手里剑", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "double-shuriken-flip", @@ -504,7 +654,10 @@ "description": "Two shuriken throws followed by an aerial flip and downward sword strike.", "telegraph": null, "counter": "Deflect/block the shurikens and dodge sideways to avoid the flip, then counter.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "双手里剑 + 后翻", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "shadowfall", @@ -513,7 +666,10 @@ "description": "Unblockable thrust; if it hits, he jumps off and comes down with two huge slashes.", "telegraph": "Charges up before the unblockable thrust.", "counter": "Mikiri the thrust.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "秘传·巨型忍者落杀", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E5%B7%A8%E5%9E%8B%E5%BF%8D%E8%80%85%E8%90%BD%E6%9D%80" }, { "id": "shuriken-slash", @@ -522,7 +678,10 @@ "description": "Throws a shuriken immediately followed by a running slash.", "telegraph": null, "counter": "Block/deflect both. A well-timed dodge to the right is also possible.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "手里剑 + 斩击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-sweep", @@ -531,7 +690,10 @@ "description": "Unblockable sweep attack performed from the side.", "telegraph": null, "counter": "Jump over it and stay close to bait his overhead slash, or jump back for a jump kick to the head.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "large-overhead-slash", @@ -540,7 +702,10 @@ "description": "A very powerful overhead slash that adapts based on the player's response.", "telegraph": "Loads up for the overhead slash.", "counter": "Dodge at the last moment and counter, or step back out of range. Hits can land during the charge-up.", - "common_mistakes": "Dodging too early makes him switch to a quick horizontal attack." + "common_mistakes": "Dodging too early makes him switch to a quick horizontal attack.", + "name_zh": "大幅度下劈", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "backflip", @@ -549,7 +714,10 @@ "description": "Leaps away; deals no vitality damage but significant posture damage.", "telegraph": null, "counter": "Deflect to minimize posture damage.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "后空翻", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "firecrackers", @@ -558,7 +726,10 @@ "description": "Throws blinding firecrackers followed by an explosion; can chain into Shuriken Throw, Shadowfall, or Shuriken Slash.", "telegraph": null, "counter": "Dodge backwards to avoid.", - "common_mistakes": "Dodging too quickly after the throw lets his follow-up slash hit you." + "common_mistakes": "Dodging too quickly after the throw lets his follow-up slash hit you.", + "name_zh": "爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "running-slash", @@ -567,7 +738,10 @@ "description": "A single strong slash performed while approaching the player.", "telegraph": null, "counter": "Block/deflect. Dodging to the left is also possible.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "冲刺斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "five-slash-firecracker-combo", @@ -576,7 +750,10 @@ "description": "Three consecutive sword slashes, followed by an elbow strike, followed by firecrackers that he slashes through.", "telegraph": null, "counter": "Deflect/block the slashes, then dodge right when the firecrackers are thrown for a counter opportunity.", - "common_mistakes": "Getting pushed back into a corner by the combo, where there is no chance of survival." + "common_mistakes": "Getting pushed back into a corner by the combo, where there is no chance of survival.", + "name_zh": "五连斩接爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "double-slash-perilous-sweep", @@ -585,7 +762,10 @@ "description": "Two consecutive slashes ending in an unblockable sweep; can chain into Large Overhead Slash.", "telegraph": null, "counter": "Jump over it and stay close to bait his overhead slash.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "二连斩接危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "stomp-deathblow", @@ -594,7 +774,10 @@ "description": "Raises a foot then stomps down, pinning the player and stabbing through the chest for massive damage. Cannot be blocked and gives no Perilous indicator.", "telegraph": "Raises his foot into the air before stomping down. There is no Perilous indicator.", "counter": "Dodge backwards or safety roll to avoid it.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "踩踏(忍杀)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "backflip-shuriken", @@ -603,7 +786,10 @@ "description": "Same as the Phase 1 backflip, with a shuriken thrown afterward.", "telegraph": null, "counter": "Block/deflect both.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "后空翻 + 手里剑", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "owl-teleport", @@ -612,7 +798,10 @@ "description": "Uses his summoned owl to teleport above the player and drop down with a slash.", "telegraph": null, "counter": "Deflect the slash, or run/stay away from the owl to avoid it.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "枭之瞬移", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "fire-owl-shadowfall", @@ -621,7 +810,10 @@ "description": "Summons his owl and throws an unblockable fire owl projectile, then follows up with Shadowfall.", "telegraph": null, "counter": "Jump over the fire owl and Mikiri the thrust.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "火枭 + 巨型忍者落杀", + "name_zh_source": "translation", + "name_zh_source_url": null } ] } @@ -647,7 +839,10 @@ "description": "Projectile attack using kunai.", "telegraph": null, "counter": "Can be blocked or deflected.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "投掷苦无", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "jump-into-rafters", @@ -656,7 +851,10 @@ "description": "Leaps into the air and disappears.", "telegraph": null, "counter": "Use the Shuriken Prosthetic immediately as she begins jumping to knock her out of the air and create an opening.", - "common_mistakes": "Throwing the shuriken too late - she will likely deflect it." + "common_mistakes": "Throwing the shuriken too late - she will likely deflect it.", + "name_zh": "跃上房梁", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-aerial-grab", @@ -665,7 +863,10 @@ "description": "Overhead perilous attack performed while airborne.", "telegraph": "Perilous Attack kanji.", "counter": "Dodge or move out of the way. Alternatively, use the Shuriken Prosthetic to knock her down for a punish window.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·空中投技", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-sweep", @@ -674,7 +875,10 @@ "description": "Low-sweeping perilous attack.", "telegraph": "Perilous Attack kanji.", "counter": "Jump and kick her for considerable posture damage.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "ground-combos", @@ -683,7 +887,10 @@ "description": "Standard melee attack sequences performed from ground level.", "telegraph": null, "counter": "Stay close, deflect attacks, then counter-attack during her recovery windows.", - "common_mistakes": "Attacking too much. Deflect more than you attack, and only strike after openings such as her perilous attacks." + "common_mistakes": "Attacking too much. Deflect more than you attack, and only strike after openings such as her perilous attacks.", + "name_zh": "地面连击", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -698,7 +905,10 @@ "description": "Projectile attack using kunai.", "telegraph": null, "counter": "Can be blocked or deflected.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "投掷苦无", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "jump-into-rafters", @@ -707,7 +917,10 @@ "description": "Leaps into the air and disappears.", "telegraph": null, "counter": "Use the Shuriken Prosthetic immediately as she begins jumping to knock her out of the air and create an opening.", - "common_mistakes": "Throwing the shuriken too late - she will likely deflect it." + "common_mistakes": "Throwing the shuriken too late - she will likely deflect it.", + "name_zh": "跃上房梁", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-aerial-grab", @@ -716,7 +929,10 @@ "description": "Overhead perilous attack performed while airborne.", "telegraph": "Perilous Attack kanji.", "counter": "Dodge or move out of the way. Alternatively, use the Shuriken Prosthetic to knock her down for a punish window.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·空中投技", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "perilous-sweep", @@ -725,7 +941,10 @@ "description": "Low-sweeping perilous attack.", "telegraph": "Perilous Attack kanji.", "counter": "Jump and kick her for considerable posture damage.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "ground-combos", @@ -734,7 +953,10 @@ "description": "Standard melee attack sequences performed from ground level.", "telegraph": null, "counter": "Stay close, deflect attacks, then counter-attack during her recovery windows.", - "common_mistakes": "Attacking too much. Deflect more than you attack, and only strike after openings such as her perilous attacks." + "common_mistakes": "Attacking too much. Deflect more than you attack, and only strike after openings such as her perilous attacks.", + "name_zh": "地面连击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "illusions", @@ -743,7 +965,10 @@ "description": "Summons phantom enemies that attack with spears.", "telegraph": null, "counter": "Use a Snap Seed to dispel the phantoms, or kill them individually for Spirit Emblems. Alternatively, run around the arena perimeter to avoid their attacks.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "幻影", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "phantom-kunai", @@ -752,7 +977,10 @@ "description": "Upgraded kunai projectile attack with additional volleys.", "telegraph": null, "counter": "Deflect or block; dodging sideways offers the best protection.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "幻影苦无", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "homing-butterflies", @@ -761,7 +989,10 @@ "description": "Homing projectiles created when illusions are dispelled.", "telegraph": null, "counter": "Hide behind pillars or keep sprinting around the arena perimeter.", - "common_mistakes": "Turning corners while the butterflies are tracking you can make them hit you." + "common_mistakes": "Turning corners while the butterflies are tracking you can make them hit you.", + "name_zh": "追踪幻蝶", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "aerial-attacks-enhanced", @@ -770,7 +1001,10 @@ "description": "Enhanced aerial maneuvers performed with increased frequency.", "telegraph": null, "counter": "Continue using the Shuriken Prosthetic to interrupt; maintain aggressive pressure to prevent aerial transitions.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "空中攻击(强化)", + "name_zh_source": "translation", + "name_zh_source_url": null } ] } @@ -796,7 +1030,10 @@ "description": "Rapid slamming combinations performed at close range.", "telegraph": null, "counter": "All slamming attacks can be blocked; attempt to deflect them.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "连续拍地", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "hand-swipe-grab", @@ -805,7 +1042,10 @@ "description": "Unblockable grab attack where the ape swipes the floor from left to right at close range.", "telegraph": null, "counter": "Time a jump to dodge it.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "挥手抓取", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "body-slam-grab", @@ -814,7 +1054,10 @@ "description": "The ape jumps and attempts to grab the player.", "telegraph": null, "counter": "Time a dodge to avoid it; a double dodge (tap the button twice) is safer.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "扑身抓取", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "poisonous-fart", @@ -823,7 +1066,10 @@ "description": "Status attack inflicting heavy poison buildup when the ape's back is turned; always followed by running away and jumping high.", "telegraph": null, "counter": "Quickly dodge away from the cloud to avoid the poison.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "毒屁", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "poisonous-poo-throw", @@ -832,7 +1078,10 @@ "description": "High-damage instant poison attack.", "telegraph": "Jumps high before throwing.", "counter": "Run underneath as it jumps and retaliate; its recovery from this attack is lengthy.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "投掷毒粪", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "roar-jump-back", @@ -841,7 +1090,10 @@ "description": "The ape jumps back and unleashes a roar.", "telegraph": null, "counter": "A grapple prompt appears, letting Sekiro close the gap and causing a brief stagger.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "咆哮 / 后跳", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "rolling-body-slam", @@ -850,7 +1102,10 @@ "description": "The ape jumps onto its back and thrashes the ground.", "telegraph": null, "counter": "Stay away from its arms and hit its head.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "翻滚压击", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -865,7 +1120,10 @@ "description": "Sword attacks that cover a wide area, after the ape decapitates itself and resurrects wielding a sword through its neck.", "telegraph": null, "counter": "Strike a few times after its combos, then back away.", - "common_mistakes": "Staying on top of it and attacking for too long." + "common_mistakes": "Staying on top of it and attacking for too long.", + "name_zh": "大范围挥刀", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "diving-lunge", @@ -874,7 +1132,10 @@ "description": "Lunges forward with a swipe while falling to the ground.", "telegraph": "Clear wind-up before it dives.", "counter": "Jump over it as it dives and strike it in the air, following with a full combo.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "飞扑", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "overhead-slam", @@ -883,7 +1144,10 @@ "description": "Large overhead attack that can be deflected.", "telegraph": null, "counter": "Deflecting it stuns the boss and exposes its neck. Using the Loaded Spear (activate it twice) briefly pulls out the centipede for massive posture and health damage.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "高举砸地", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "blood-scream", @@ -892,7 +1156,10 @@ "description": "Battle roar that unleashes shockwaves filling the Terror meter, which can inflict Terror status and instantly kill Sekiro.", "telegraph": "Puts its head back on its neck before screaming.", "counter": "Any Loaded Umbrella-type prosthetic completely negates the scream; the Phoenix's Lilac Umbrella is recommended for NG+.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "血之咆哮", + "name_zh_source": "translation", + "name_zh_source_url": null } ] } @@ -918,7 +1185,10 @@ "description": "Perilous sweep attack variant, often followed by attack opportunities.", "telegraph": null, "counter": "Jump over it.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "横扫攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "thrust-attack", @@ -927,7 +1197,10 @@ "description": "Perilous thrust attack variant.", "telegraph": null, "counter": "Mikiri Counter.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "突刺攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "jump-attack-sweep", @@ -936,7 +1209,10 @@ "description": "Jumping attack that sweeps during the jump.", "telegraph": null, "counter": "Sidestep and strike as she lands.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "跳跃攻击(横扫)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "jump-attack-overhead", @@ -945,7 +1221,10 @@ "description": "Jumping attack that ends in an overhead slash.", "telegraph": null, "counter": "Run below and attack her exposed back.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "跳跃攻击(下劈)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "upwards-slash", @@ -954,7 +1233,10 @@ "description": "An upward slash that goes through Sekiro's normal defenses and cannot be blocked.", "telegraph": null, "counter": "Can be deflected, though the timing is very precise.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "上挑斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "whirlwind-attack", @@ -963,7 +1245,10 @@ "description": "Continuous spinning attack with the Naginata that can quickly exhaust Sekiro's posture gauge. Tends to be used when at a distance from the player, more often if the player uses a consumable.", "telegraph": null, "counter": "Run away when she starts spinning.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "旋风攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "mid-range-combo", @@ -972,7 +1257,10 @@ "description": "One jumping air hit followed by two ground hits at mid-range.", "telegraph": null, "counter": "Let the landing cancel the air-attack lag, then jump away to mid-range.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "中距离连击", + "name_zh_source": "translation", + "name_zh_source_url": null } ] } @@ -998,7 +1286,10 @@ "description": "Perilous sweep attack variant, used more often here than in the Mibu Village fight. More telegraphed and predictable in Phase 3.", "telegraph": null, "counter": "Jump over it.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "横扫攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "thrust-attack", @@ -1007,7 +1298,10 @@ "description": "Perilous thrust attack variant, used more often here than in the Mibu Village fight.", "telegraph": null, "counter": "Mikiri Counter.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "突刺攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "jump-attack-sweep", @@ -1016,7 +1310,10 @@ "description": "Jumping attack that sweeps during the jump.", "telegraph": null, "counter": "Sidestep and strike as she lands.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "跳跃攻击(横扫)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "jump-attack-overhead", @@ -1025,7 +1322,10 @@ "description": "Jumping attack that ends in an overhead slash. In Phase 3 it tends to be used as a finishing move.", "telegraph": null, "counter": "Run below and attack her exposed back.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "跳跃攻击(下劈)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "upwards-slash", @@ -1034,7 +1334,10 @@ "description": "An upward slash that goes through Sekiro's normal defenses and cannot be blocked.", "telegraph": null, "counter": "Can be deflected, though the timing is very precise.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "上挑斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "mid-range-combo", @@ -1043,7 +1346,10 @@ "description": "One jumping air hit followed by two ground hits at mid-range.", "telegraph": null, "counter": "Let the landing cancel the air-attack lag, then jump away to mid-range.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "中距离连击", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -1058,7 +1364,10 @@ "description": "She disappears and the area fills with mist, then summons three shadow illusions of herself that each attack the player three times. The shadows disappear between attacks, making lock-on difficult. Standing near a wall before the shadows spawn limits them to one direction, repeating the same three moves.", "telegraph": null, "counter": "The shadows don't need to be defeated - she eventually ends the illusion and returns on her own. A Bite Down pill instantly dispels them too.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "召唤幻影", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -1073,7 +1382,10 @@ "description": "Perilous sweep attack variant, used more often here than in the Mibu Village fight. More telegraphed and predictable in Phase 3.", "telegraph": null, "counter": "Jump over it.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "横扫攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "thrust-attack", @@ -1082,7 +1394,10 @@ "description": "Perilous thrust attack variant, used more often here than in the Mibu Village fight.", "telegraph": null, "counter": "Mikiri Counter.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "突刺攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "jump-attack-sweep", @@ -1091,7 +1406,10 @@ "description": "Jumping attack that sweeps during the jump.", "telegraph": null, "counter": "Sidestep and strike as she lands.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "跳跃攻击(横扫)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "jump-attack-overhead", @@ -1100,7 +1418,10 @@ "description": "Jumping attack that ends in an overhead slash. In Phase 3 it tends to be used as a finishing move.", "telegraph": null, "counter": "Run below and attack her exposed back.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "跳跃攻击(下劈)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "upwards-slash", @@ -1109,7 +1430,10 @@ "description": "An upward slash that goes through Sekiro's normal defenses and cannot be blocked.", "telegraph": null, "counter": "Can be deflected, though the timing is very precise.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "上挑斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "mid-range-combo", @@ -1118,7 +1442,10 @@ "description": "One jumping air hit followed by two ground hits at mid-range.", "telegraph": null, "counter": "Let the landing cancel the air-attack lag, then jump away to mid-range.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "中距离连击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "whirlwind-attack", @@ -1127,7 +1454,10 @@ "description": "Continuous spinning attack with the Naginata, regained in this phase after being absent since Phase 1. Very susceptible to the Shinobi Firecracker, especially during the windup.", "telegraph": null, "counter": "Run away when she starts spinning, or interrupt the windup with a Shinobi Firecracker.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "旋风攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "centipede-toxic-goo", @@ -1136,7 +1466,10 @@ "description": "Perilous attack where the demon centipede erupting from her neck spews toxic goo in front of her; can inflict Terror.", "telegraph": null, "counter": "Sidestep or jump-kick when she uses this and retaliate - the attack has lengthy ending lag.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "百足毒液", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "centipede-toxic-cloud", @@ -1145,7 +1478,10 @@ "description": "A non-perilous variant of the centipede's toxin attack.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "百足毒雾", + "name_zh_source": "translation", + "name_zh_source_url": null } ] } @@ -1171,7 +1507,10 @@ "description": "Tosses a charm that releases a green mist, preventing healing item usage for 30 seconds.", "telegraph": null, "counter": "Sidestep and then strike, taking care not to get debuffed.", - "common_mistakes": "Walking into the mist, which locks you out of healing items." + "common_mistakes": "Walking into the mist, which locks you out of healing items.", + "name_zh": "忍符", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "backflip", @@ -1180,7 +1519,10 @@ "description": "Leaps backward while throwing a shuriken.", "telegraph": null, "counter": "Deflect or dodge the projectile.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "后空翻", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "running-slash", @@ -1189,7 +1531,10 @@ "description": "A single powerful slash performed while charging toward the player.", "telegraph": null, "counter": "Deflect or dodge depending on posture status.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "冲刺斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "four-slash-combo", @@ -1198,7 +1543,10 @@ "description": "Four consecutive sword strikes, often followed by Shinobi Charm.", "telegraph": null, "counter": "Deflect or retreat.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "四连斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "shuriken-throw-flip", @@ -1207,7 +1555,10 @@ "description": "Two shuriken throws followed by an airborne downward strike.", "telegraph": null, "counter": "Dodge diagonally forward through the flip strike to open up an opportunity to attack 3-4 times, or land two Mortal Draw hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "投掷手里剑 + 翻身", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "shuriken-slash", @@ -1216,7 +1567,10 @@ "description": "A quick shuriken throw immediately followed by a running slash.", "telegraph": null, "counter": "Sidestep the projectile, then deflect or retreat.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "手里剑 + 斩击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "firecracker", @@ -1225,7 +1579,10 @@ "description": "Black gunpowder explosion attack dealing significant damage.", "telegraph": "Black smoke.", "counter": "Run away when you see black smoke, or dodge in aggressively.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "stomp-grab", @@ -1234,7 +1591,10 @@ "description": "Raises a foot then stomps down, pinning and stabbing the player. Deals massive damage, cannot be blocked, and gives no Perilous indicator.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "踩踏(投技)", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -1249,7 +1609,10 @@ "description": "Tosses a charm that releases a green mist, preventing healing item usage for 30 seconds.", "telegraph": null, "counter": "Sidestep and then strike, taking care not to get debuffed.", - "common_mistakes": "Walking into the mist, which locks you out of healing items." + "common_mistakes": "Walking into the mist, which locks you out of healing items.", + "name_zh": "忍符", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "backflip-poison", @@ -1258,7 +1621,10 @@ "description": "Same motion as the Phase 1 backflip, but throws poison instead of a shuriken.", "telegraph": null, "counter": "Dodge and maintain distance from the poisoned area.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "后空翻(毒)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "running-slash", @@ -1267,7 +1633,10 @@ "description": "A single powerful slash performed while charging toward the player.", "telegraph": null, "counter": "Deflect or dodge depending on posture status.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "冲刺斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "four-slash-combo", @@ -1276,7 +1645,10 @@ "description": "Four consecutive sword strikes, often followed by Shinobi Charm.", "telegraph": null, "counter": "Deflect or retreat.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "四连斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "shuriken-throw-flip", @@ -1285,7 +1657,10 @@ "description": "Two shuriken throws followed by an airborne downward strike.", "telegraph": null, "counter": "Dodge diagonally forward through the flip strike to open up an opportunity to attack 3-4 times, or land two Mortal Draw hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "投掷手里剑 + 翻身", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "shuriken-slash", @@ -1294,7 +1669,10 @@ "description": "A quick shuriken throw immediately followed by a running slash.", "telegraph": null, "counter": "Sidestep the projectile, then deflect or retreat.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "手里剑 + 斩击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "firecracker", @@ -1303,7 +1681,10 @@ "description": "Black gunpowder explosion attack dealing significant damage.", "telegraph": "Black smoke.", "counter": "Run away when you see black smoke, or dodge in aggressively.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "stomp-grab", @@ -1312,7 +1693,10 @@ "description": "Raises a foot then stomps down, pinning and stabbing the player. Deals massive damage, cannot be blocked, and gives no Perilous indicator.", "telegraph": null, "counter": null, - "common_mistakes": null + "common_mistakes": null, + "name_zh": "踩踏(投技)", + "name_zh_source": "translation", + "name_zh_source_url": null } ] } @@ -1338,7 +1722,10 @@ "description": "Strikes downward launching a force wave, followed by a second wave after a short delay.", "telegraph": "Sheathes his sword and readies a stance; a glint shows on the hilt.", "counter": "Deflect the first wave, then dodge the second.", - "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage." + "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage.", + "name_zh": "秘传·龙闪", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA" }, { "id": "ashina-cross", @@ -1347,7 +1734,10 @@ "description": "Steps forward with two rapid slashes.", "telegraph": "Sheathes his sword; after a short delay there is a flash of light at his hand as he steps forward.", "counter": "Press deflect twice without pausing, or run far away to avoid it entirely.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "绝技·苇名十字斩", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9" }, { "id": "ashina-cross-cancel", @@ -1356,7 +1746,10 @@ "description": "If the player is close when he readies Ashina Cross, he cancels it into a quick sword smack followed by a perilous sweep. He also does this after the player repeatedly deflects his attacks.", "telegraph": null, "counter": "Deflect the initial hit, then jump and stomp.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "苇名十字斩(取消)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "ichimonji-double", @@ -1365,7 +1758,10 @@ "description": "Performs a double overhead strike.", "telegraph": "Raises his blade into a ready stance.", "counter": "Dodge or block; the attack can also be sidestepped. Deflecting triggers a stagger.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "一字斩·二连", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E" }, { "id": "distance-slash", @@ -1374,7 +1770,10 @@ "description": "If the player is far away or using an item, he quickly dashes in and slashes.", "telegraph": null, "counter": "Maintain enough distance to react in time.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "远距离斩击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "charged-horizontal-force-wave", @@ -1383,7 +1782,10 @@ "description": "Swings in a spinning motion, causing a large circular force wave.", "telegraph": "Sheathes his sword and charges the attack.", "counter": "Wait, then deflect as soon as he begins moving his arm, or sprint and jump toward him, then jump again onto his head.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "蓄力横向剑气", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "three-to-four-hit-combo", @@ -1392,7 +1794,10 @@ "description": "A series of main slashes with delays between hits.", "telegraph": null, "counter": "Deflect each hit; once he finishes slashing he's open for a quick two hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "三至四连击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "thrust", @@ -1401,7 +1806,10 @@ "description": "A telegraphed thrust attack.", "telegraph": null, "counter": "Mikiri Counter - easy to react to once telegraphed.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "突刺", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "long-range-wind-attack", @@ -1410,7 +1818,10 @@ "description": "A long-range wind-based attack.", "telegraph": null, "counter": "Dodge by running sideways to gain a couple of free hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "远距离风压攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "close-range-full-screen-sweep", @@ -1419,7 +1830,10 @@ "description": "A close-range sweep that covers the full screen.", "telegraph": null, "counter": "Sprint backwards before it goes off.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "近距离全屏横扫", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -1434,7 +1848,10 @@ "description": "Strikes downward launching a force wave, followed by a second wave after a short delay.", "telegraph": "Sheathes his sword and readies a stance; a glint shows on the hilt.", "counter": "Deflect the first wave, then dodge the second.", - "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage." + "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage.", + "name_zh": "秘传·龙闪", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA" }, { "id": "ashina-cross", @@ -1443,7 +1860,10 @@ "description": "Steps forward with two rapid slashes.", "telegraph": "Sheathes his sword; after a short delay there is a flash of light at his hand as he steps forward.", "counter": "Press deflect twice without pausing, or run far away to avoid it entirely.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "绝技·苇名十字斩", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9" }, { "id": "ashina-cross-cancel", @@ -1452,7 +1872,10 @@ "description": "If the player is close when he readies Ashina Cross, he cancels it into a quick sword smack followed by a perilous sweep. He also does this after the player repeatedly deflects his attacks.", "telegraph": null, "counter": "Deflect the initial hit, then jump and stomp.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "苇名十字斩(取消)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "ichimonji-double", @@ -1461,7 +1884,10 @@ "description": "Performs a double overhead strike.", "telegraph": "Raises his blade into a ready stance.", "counter": "Dodge or block; the attack can also be sidestepped. Deflecting triggers a stagger.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "一字斩·二连", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E" }, { "id": "distance-slash", @@ -1470,7 +1896,10 @@ "description": "If the player is far away or using an item, he quickly dashes in and slashes.", "telegraph": null, "counter": "Maintain enough distance to react in time.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "远距离斩击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "charged-horizontal-force-wave", @@ -1479,7 +1908,10 @@ "description": "Swings in a spinning motion, causing a large circular force wave.", "telegraph": "Sheathes his sword and charges the attack.", "counter": "Wait, then deflect as soon as he begins moving his arm, or sprint and jump toward him, then jump again onto his head.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "蓄力横向剑气", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "three-to-four-hit-combo", @@ -1488,7 +1920,10 @@ "description": "A series of main slashes with delays between hits.", "telegraph": null, "counter": "Deflect each hit; once he finishes slashing he's open for a quick two hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "三至四连击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "thrust", @@ -1497,7 +1932,10 @@ "description": "A telegraphed thrust attack.", "telegraph": null, "counter": "Mikiri Counter - easy to react to once telegraphed.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "突刺", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "long-range-wind-attack", @@ -1506,7 +1944,10 @@ "description": "A long-range wind-based attack.", "telegraph": null, "counter": "Dodge by running sideways to gain a couple of free hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "远距离风压攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "close-range-full-screen-sweep", @@ -1515,7 +1956,10 @@ "description": "A close-range sweep that covers the full screen.", "telegraph": null, "counter": "Sprint backwards before it goes off.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "近距离全屏横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "charged-double-projected-force", @@ -1524,7 +1968,10 @@ "description": "Makes two slashes that each launch a force disc. Deflecting the discs does not affect his posture.", "telegraph": "Sheathes his sword and charges the attack.", "counter": "The discs deal damage through block if not deflected properly, but the attack is easy to dodge.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "蓄力双重剑气", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "leaping-slam", @@ -1533,7 +1980,10 @@ "description": "When far away or the player is using an item, he leaps and slams with his spear, followed by a sword strike or gunshots.", "telegraph": "Leaps into the air before slamming down with his spear.", "counter": "Deflect the slam, then counter-attack.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "跳跃砸击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "retreating-sweep", @@ -1542,7 +1992,10 @@ "description": "Leaps backward into the air and sweeps with his spear, often following several blocked player attacks. Comes out very quickly.", "telegraph": "Leaps backward into the air.", "counter": "Deflect, or quickly dodge forward for a free hit.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "后撤横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "quad-shot", @@ -1551,7 +2004,10 @@ "description": "At a distance, fires several rapid pistol shots followed by a large thrust from the spear. The last bullet is slightly delayed.", "telegraph": "Pulls out a pistol.", "counter": "Learn to deflect the shots, then Mikiri counter the following thrust or jump-attack if it's a sweep. Simply blocking the bullets damages posture heavily.", - "common_mistakes": "Missing the slightly delayed last bullet, which can break your posture." + "common_mistakes": "Missing the slightly delayed last bullet, which can break your posture.", + "name_zh": "四连射击", + "name_zh_source": "translation", + "name_zh_source_url": null } ] }, @@ -1566,7 +2022,10 @@ "description": "Strikes downward launching a force wave, followed by a second wave after a short delay.", "telegraph": "Sheathes his sword and readies a stance; a glint shows on the hilt.", "counter": "Deflect the first wave, then dodge the second.", - "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage." + "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage.", + "name_zh": "秘传·龙闪", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA" }, { "id": "ashina-cross", @@ -1575,7 +2034,10 @@ "description": "Steps forward with two rapid slashes.", "telegraph": "Sheathes his sword; after a short delay there is a flash of light at his hand as he steps forward.", "counter": "Press deflect twice without pausing, or run far away to avoid it entirely.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "绝技·苇名十字斩", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9" }, { "id": "ashina-cross-cancel", @@ -1584,7 +2046,10 @@ "description": "If the player is close when he readies Ashina Cross, he cancels it into a quick sword smack followed by a perilous sweep. He also does this after the player repeatedly deflects his attacks.", "telegraph": null, "counter": "Deflect the initial hit, then jump and stomp.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "苇名十字斩(取消)", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "ichimonji-double", @@ -1593,7 +2058,10 @@ "description": "Performs a double overhead strike.", "telegraph": "Raises his blade into a ready stance.", "counter": "Dodge or block; the attack can also be sidestepped. Deflecting triggers a stagger.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "一字斩·二连", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E" }, { "id": "distance-slash", @@ -1602,7 +2070,10 @@ "description": "If the player is far away or using an item, he quickly dashes in and slashes.", "telegraph": null, "counter": "Maintain enough distance to react in time.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "远距离斩击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "charged-horizontal-force-wave", @@ -1611,7 +2082,10 @@ "description": "Swings in a spinning motion, causing a large circular force wave.", "telegraph": "Sheathes his sword and charges the attack.", "counter": "Wait, then deflect as soon as he begins moving his arm, or sprint and jump toward him, then jump again onto his head.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "蓄力横向剑气", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "three-to-four-hit-combo", @@ -1620,7 +2094,10 @@ "description": "A series of main slashes with delays between hits.", "telegraph": null, "counter": "Deflect each hit; once he finishes slashing he's open for a quick two hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "三至四连击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "thrust", @@ -1629,7 +2106,10 @@ "description": "A telegraphed thrust attack.", "telegraph": null, "counter": "Mikiri Counter - easy to react to once telegraphed.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "突刺", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "long-range-wind-attack", @@ -1638,7 +2118,10 @@ "description": "A long-range wind-based attack.", "telegraph": null, "counter": "Dodge by running sideways to gain a couple of free hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "远距离风压攻击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "close-range-full-screen-sweep", @@ -1647,7 +2130,10 @@ "description": "A close-range sweep that covers the full screen.", "telegraph": null, "counter": "Sprint backwards before it goes off.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "近距离全屏横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "charged-double-projected-force", @@ -1656,7 +2142,10 @@ "description": "Makes two slashes that each launch a force disc. Deflecting the discs does not affect his posture.", "telegraph": "Sheathes his sword and charges the attack.", "counter": "The discs deal damage through block if not deflected properly, but the attack is easy to dodge.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "蓄力双重剑气", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "leaping-slam", @@ -1665,7 +2154,10 @@ "description": "When far away or the player is using an item, he leaps and slams with his spear, followed by a sword strike or gunshots.", "telegraph": "Leaps into the air before slamming down with his spear.", "counter": "Deflect the slam, then counter-attack.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "跳跃砸击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "retreating-sweep", @@ -1674,7 +2166,10 @@ "description": "Leaps backward into the air and sweeps with his spear, often following several blocked player attacks. Comes out very quickly.", "telegraph": "Leaps backward into the air.", "counter": "Deflect, or quickly dodge forward for a free hit.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "后撤横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "quad-shot", @@ -1683,7 +2178,10 @@ "description": "At a distance, fires several rapid pistol shots followed by a large thrust from the spear. The last bullet is slightly delayed.", "telegraph": "Pulls out a pistol.", "counter": "Learn to deflect the shots, then Mikiri counter the following thrust or jump-attack if it's a sweep. Simply blocking the bullets damages posture heavily.", - "common_mistakes": "Missing the slightly delayed last bullet, which can break your posture." + "common_mistakes": "Missing the slightly delayed last bullet, which can break your posture.", + "name_zh": "四连射击", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "lightning-slash", @@ -1692,7 +2190,10 @@ "description": "Launches lightning at the player with a sword swing.", "telegraph": "Jumps into the air as lightning converges on him.", "counter": "Jump into the air and use Lightning Reversal, Mist Raven, or Sakura Dance to redirect the attack back at him and stun him.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "雷电斩", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "lightning-overhead", @@ -1701,7 +2202,10 @@ "description": "Large overhead attacks with added lightning damage.", "telegraph": null, "counter": "Can be deflected, though the timing is difficult.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "雷电强化下劈", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "lightning-spear-sweep", @@ -1710,7 +2214,10 @@ "description": "A large swing with the spear covered in lightning.", "telegraph": null, "counter": "Get close when he does this and jump to deflect it directly back at him for big vitality and posture damage, plus a flurry of free hits.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "雷枪大横扫", + "name_zh_source": "translation", + "name_zh_source_url": null }, { "id": "distance-slash-lightning", @@ -1719,7 +2226,10 @@ "description": "More of a distance slash than the vertical Phase 1 wind attack.", "telegraph": null, "counter": "Similar avoidance strategy to the wind attacks - dodge by running sideways.", - "common_mistakes": null + "common_mistakes": null, + "name_zh": "远距离斩击(雷电)", + "name_zh_source": "translation", + "name_zh_source_url": null } ] } diff --git a/backend/tests/test_move_chinese_names.py b/backend/tests/test_move_chinese_names.py new file mode 100644 index 0000000..1601b83 --- /dev/null +++ b/backend/tests/test_move_chinese_names.py @@ -0,0 +1,77 @@ +"""Chinese move names: from a Chinese wiki (with its page) or marked as translations (V3 Milestone 4).""" + +import pytest +from pydantic import ValidationError +from sqlalchemy import select +from sqlalchemy.exc import IntegrityError + +from app.db.models import Boss, Move +from app.models.boss import BossMove +from app.seed import SeedError, load_boss_data, sync_reference_data + +WIKI_URL = "https://wiki.biligame.com/sekiro/example" + + +def test_every_seeded_move_has_a_chinese_name_with_a_valid_source(): + for boss in load_boss_data(): + for phase in boss.phases: + for move in phase.moves: + assert move.name_zh, f"{boss.id}/{move.id}" + assert move.name_zh_source in ("wiki", "translation") + if move.name_zh_source == "wiki": + assert move.name_zh_source_url.startswith("https://"), f"{boss.id}/{move.id}" + + +def test_seed_rejects_a_move_without_a_chinese_name(session): + data = load_boss_data() + move = data[0].phases[0].moves[0] + move.name_zh = None + move.name_zh_source = None + + with pytest.raises(SeedError, match="has no Chinese name"): + sync_reference_data(session, data) + + +@pytest.mark.parametrize( + "fields", + [ + {"name_zh": "突刺"}, + {"name_zh_source": "translation"}, + {"name_zh": "突刺", "name_zh_source": "wiki"}, + {"name_zh": "突刺", "name_zh_source": "translation", "name_zh_source_url": WIKI_URL}, + {"name_zh": "突刺", "name_zh_source": "official"}, + ], + ids=["name-without-source", "source-without-name", "wiki-without-page", "translation-with-page", "unknown-source"], +) +def test_move_model_rejects_inconsistent_chinese_name_sources(fields): + with pytest.raises(ValidationError): + BossMove(id="thrust", name="Thrust", move_type="thrust", **fields) + + +@pytest.mark.parametrize( + "fields", + [ + {"name_zh": "突刺", "name_zh_source": None}, + {"name_zh": "突刺", "name_zh_source": "wiki", "name_zh_source_url": None}, + {"name_zh": "突刺", "name_zh_source": "translation", "name_zh_source_url": WIKI_URL}, + {"name_zh": "突刺", "name_zh_source": "official"}, + ], + ids=["name-without-source", "wiki-without-page", "translation-with-page", "unknown-source"], +) +def test_database_rejects_inconsistent_chinese_name_sources(seeded_session, fields): + boss = seeded_session.scalar(select(Boss).where(Boss.slug == "genichiro-ashina")) + seeded_session.add(Move(boss=boss, slug="db-check", name="Check", move_type="thrust", **fields)) + + with pytest.raises(IntegrityError): + seeded_session.flush() + + +def test_boss_detail_returns_chinese_names_and_their_sources(anon_client): + boss = anon_client.get("/api/bosses/genichiro-ashina").json() + moves = {m["id"]: m for phase in boss["phases"] for m in phase["moves"]} + + assert moves["floating-passage"]["name_zh"] == "绝技·飞渡浮舟" + assert moves["floating-passage"]["name_zh_source"] == "wiki" + assert moves["floating-passage"]["name_zh_source_url"].startswith("https://wiki.biligame.com/") + assert moves["perilous-thrust"]["name_zh_source"] == "translation" + assert moves["perilous-thrust"]["name_zh_source_url"] is None diff --git a/backend/tests/test_sekiro_analytics.py b/backend/tests/test_sekiro_analytics.py index 6d7192a..50ba399 100644 --- a/backend/tests/test_sekiro_analytics.py +++ b/backend/tests/test_sekiro_analytics.py @@ -98,6 +98,7 @@ def test_recent_attempts_are_newest_first_with_names_and_capped_at_ten(client): "phase_reached": 2, "failure_move_id": "blood-scream", "failure_move_name": "Blood Scream", + "failure_move_name_zh": "血之咆哮", "failure_category": None, } assert [int(a["attempt_id"]) for a in recent] == sorted((int(a["attempt_id"]) for a in recent), reverse=True) diff --git a/frontend/src/components/AnalyticsPanel.test.tsx b/frontend/src/components/AnalyticsPanel.test.tsx index 5e57774..faf02be 100644 --- a/frontend/src/components/AnalyticsPanel.test.tsx +++ b/frontend/src/components/AnalyticsPanel.test.tsx @@ -13,14 +13,14 @@ const boss: Boss = { phase_number: 1, name: "Phase 1", moves: [ - { id: "perilous-thrust", name: "Perilous Thrust", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null }, + { id: "perilous-thrust", name: "Perilous Thrust", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, ], }, { phase_number: 2, name: "Phase 2", moves: [ - { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null }, + { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, ], }, ], diff --git a/frontend/src/components/AttemptHistory.test.tsx b/frontend/src/components/AttemptHistory.test.tsx index 10a2b8f..c0b31fc 100644 --- a/frontend/src/components/AttemptHistory.test.tsx +++ b/frontend/src/components/AttemptHistory.test.tsx @@ -13,7 +13,7 @@ const boss: Boss = { phase_number: 2, name: "Phase 2", moves: [ - { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null }, + { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, ], }, ], diff --git a/frontend/src/components/ChineseMoveName.tsx b/frontend/src/components/ChineseMoveName.tsx new file mode 100644 index 0000000..bcd61c9 --- /dev/null +++ b/frontend/src/components/ChineseMoveName.tsx @@ -0,0 +1,31 @@ +import type { BossMove } from "../types"; + +const TRANSLATION_NOTE = "Translated for this app; not an official in-game name."; + +/** + * A move's Chinese name next to its English one. Names taken from a Chinese wiki + * link to that page; translations are labelled so they are never mistaken for + * official names. + */ +export default function ChineseMoveName({ move }: { move: BossMove }) { + if (!move.name_zh) return null; + + if (move.name_zh_source === "wiki" && move.name_zh_source_url) { + return ( + + + {move.name_zh} + + + ); + } + + return ( + + {move.name_zh} + + 译名 + + + ); +} diff --git a/frontend/src/components/MovesetReference.test.tsx b/frontend/src/components/MovesetReference.test.tsx index 83be176..4341f24 100644 --- a/frontend/src/components/MovesetReference.test.tsx +++ b/frontend/src/components/MovesetReference.test.tsx @@ -24,6 +24,9 @@ const boss: Boss = { telegraph: "A glint shows on the hilt.", counter: "Deflect the first wave.", common_mistakes: "Blocking the waves instead of deflecting them.", + name_zh: "秘传·龙闪", + name_zh_source: "wiki", + name_zh_source_url: "https://wiki.biligame.com/sekiro/dragon-flash", }, { id: "thrust", @@ -33,6 +36,9 @@ const boss: Boss = { telegraph: null, counter: null, common_mistakes: null, + name_zh: "突刺", + name_zh_source: "translation", + name_zh_source_url: null, }, ], }, @@ -52,7 +58,9 @@ describe("MovesetReference", () => { render(); const thrust = screen.getByText("Thrust").closest("li") as HTMLElement; - expect(thrust.textContent).toBe("Thrust"); + // Only the names: no description, telegraph, counter or common-mistake paragraphs. + expect(thrust.querySelectorAll("p")).toHaveLength(0); + expect(thrust.textContent).toBe("Thrust突刺译名"); }); it("links to the boss's data source", () => { @@ -66,4 +74,19 @@ describe("MovesetReference", () => { expect(screen.queryByText(/source:/i)).not.toBeInTheDocument(); }); + + it("links Chinese names taken from a wiki to their source page", () => { + render(); + + const link = screen.getByRole("link", { name: "秘传·龙闪" }); + expect(link).toHaveAttribute("href", "https://wiki.biligame.com/sekiro/dragon-flash"); + }); + + it("marks translated Chinese names as translations", () => { + render(); + + expect(screen.getByText("突刺")).toBeInTheDocument(); + expect(screen.queryByRole("link", { name: "突刺" })).not.toBeInTheDocument(); + expect(screen.getByTitle(/not an official in-game name/)).toHaveTextContent("译名"); + }); }); diff --git a/frontend/src/components/MovesetReference.tsx b/frontend/src/components/MovesetReference.tsx index 94a4ae2..ef4b316 100644 --- a/frontend/src/components/MovesetReference.tsx +++ b/frontend/src/components/MovesetReference.tsx @@ -1,4 +1,5 @@ import type { Boss } from "../types"; +import ChineseMoveName from "./ChineseMoveName"; export default function MovesetReference({ boss }: { boss: Boss }) { return ( @@ -12,6 +13,7 @@ export default function MovesetReference({ boss }: { boss: Boss }) { {phase.moves.map((move) => (
  • {move.name} + {move.description &&

    {move.description}

    } {move.telegraph &&

    Telegraph: {move.telegraph}

    } {move.counter &&

    Counter: {move.counter}

    } diff --git a/frontend/src/components/RecordAttemptForm.test.tsx b/frontend/src/components/RecordAttemptForm.test.tsx index 83dc722..706df1d 100644 --- a/frontend/src/components/RecordAttemptForm.test.tsx +++ b/frontend/src/components/RecordAttemptForm.test.tsx @@ -18,14 +18,14 @@ const boss: Boss = { phase_number: 1, name: "Phase 1", moves: [ - { id: "thrust-attack", name: "Thrust Attack", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null }, + { id: "thrust-attack", name: "Thrust Attack", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, ], }, { phase_number: 2, name: "Phase 2", moves: [ - { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null }, + { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, ], }, ], diff --git a/frontend/src/components/RecordAttemptForm.tsx b/frontend/src/components/RecordAttemptForm.tsx index e9685fb..f4f7ce1 100644 --- a/frontend/src/components/RecordAttemptForm.tsx +++ b/frontend/src/components/RecordAttemptForm.tsx @@ -3,6 +3,7 @@ import type { FormEvent } from "react"; import { createAttempt } from "../api/attempts"; import { ApiError } from "../api/client"; import type { AttemptResult, Boss, CreateAttemptRequest } from "../types"; +import { moveLabel } from "../utils/moves"; const OTHER = "__other__"; const NOT_SURE = "__not_sure__"; @@ -106,7 +107,7 @@ export default function RecordAttemptForm({ boss, onSuccess, onCancel }: RecordA {movesForPhase.map((move) => ( ))} diff --git a/frontend/src/index.css b/frontend/src/index.css index 6ae9797..1fff0e2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -464,3 +464,24 @@ a { outline: 2px solid var(--color-primary-hover); outline-offset: 1px; } + +.move-name-zh { + margin-left: 0.5rem; + color: var(--color-text-muted); +} + +.move-name-zh a { + color: inherit; + text-decoration-style: dotted; +} + +.translation-badge { + margin-left: 0.35rem; + padding: 0 0.3rem; + border: 1px solid var(--color-border); + border-radius: 4px; + font-size: 0.7rem; + text-decoration: none; + cursor: help; + vertical-align: middle; +} diff --git a/frontend/src/pages/BossDashboardPage.test.tsx b/frontend/src/pages/BossDashboardPage.test.tsx index be0c82d..761b5fb 100644 --- a/frontend/src/pages/BossDashboardPage.test.tsx +++ b/frontend/src/pages/BossDashboardPage.test.tsx @@ -23,7 +23,7 @@ const boss: Boss = { phase_number: 1, name: "Phase 1", moves: [ - { id: "thrust-attack", name: "Thrust Attack", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null }, + { id: "thrust-attack", name: "Thrust Attack", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, ], }, ], diff --git a/frontend/src/pages/SekiroDashboardPage.test.tsx b/frontend/src/pages/SekiroDashboardPage.test.tsx index a821c96..6986523 100644 --- a/frontend/src/pages/SekiroDashboardPage.test.tsx +++ b/frontend/src/pages/SekiroDashboardPage.test.tsx @@ -48,6 +48,7 @@ const analytics: SekiroAnalytics = { phase_reached: 2, failure_move_id: "owl-teleport", failure_move_name: "Owl Teleport", + failure_move_name_zh: "枭之瞬移", failure_category: null, }, { @@ -59,6 +60,7 @@ const analytics: SekiroAnalytics = { phase_reached: 3, failure_move_id: null, failure_move_name: null, + failure_move_name_zh: null, failure_category: null, }, ], diff --git a/frontend/src/pages/SekiroDashboardPage.tsx b/frontend/src/pages/SekiroDashboardPage.tsx index 59fd82d..c13df73 100644 --- a/frontend/src/pages/SekiroDashboardPage.tsx +++ b/frontend/src/pages/SekiroDashboardPage.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { getSekiroAnalytics } from "../api/sekiro"; +import { formatMoveName } from "../utils/moves"; import type { BossComparisonRow, RecentAttempt, SekiroAnalytics } from "../types"; type LoadState = "loading" | "error" | "ready"; @@ -17,7 +18,7 @@ function bestResult(row: BossComparisonRow): string { function attemptCause(attempt: RecentAttempt): string | null { if (attempt.result === "victory") return null; - if (attempt.failure_move_name) return attempt.failure_move_name; + if (attempt.failure_move_name) return formatMoveName(attempt.failure_move_name, attempt.failure_move_name_zh); if (attempt.failure_category === "other") return "Other"; return "Not Sure"; } diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 38a2dec..5f93cd1 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -10,8 +10,14 @@ export interface BossMove { telegraph: string | null; counter: string | null; common_mistakes: string | null; + /** Chinese name: from a Chinese wiki ("wiki", see name_zh_source_url) or a translation. */ + name_zh: string | null; + name_zh_source: ChineseNameSource | null; + name_zh_source_url: string | null; } +export type ChineseNameSource = "wiki" | "translation"; + export interface BossPhase { phase_number: number; name: string; @@ -111,6 +117,7 @@ export interface RecentAttempt { phase_reached: number; failure_move_id: string | null; failure_move_name: string | null; + failure_move_name_zh: string | null; failure_category: FailureCategory | null; } diff --git a/frontend/src/utils/moves.test.ts b/frontend/src/utils/moves.test.ts index cf29fc8..a40184c 100644 --- a/frontend/src/utils/moves.test.ts +++ b/frontend/src/utils/moves.test.ts @@ -12,14 +12,14 @@ const boss: Boss = { phase_number: 1, name: "Phase 1", moves: [ - { id: "thrust-attack", name: "Thrust Attack", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null }, + { id: "thrust-attack", name: "Thrust Attack", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, ], }, { phase_number: 3, name: "Phase 3", moves: [ - { id: "lightning-attack", name: "Lightning Attack", move_type: "lightning", description: null, telegraph: null, counter: null, common_mistakes: null }, + { id: "lightning-attack", name: "Lightning Attack", move_type: "lightning", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, ], }, ], diff --git a/frontend/src/utils/moves.ts b/frontend/src/utils/moves.ts index 360766d..1edca32 100644 --- a/frontend/src/utils/moves.ts +++ b/frontend/src/utils/moves.ts @@ -1,10 +1,19 @@ -import type { Attempt, Boss } from "../types"; +import type { Attempt, Boss, BossMove } from "../types"; + +/** "English · 中文" when a Chinese name exists, for places that show a move as plain text. */ +export function formatMoveName(name: string, nameZh: string | null | undefined): string { + return nameZh ? `${name} · ${nameZh}` : name; +} + +export function moveLabel(move: Pick): string { + return formatMoveName(move.name, move.name_zh); +} export function getMoveName(boss: Boss, moveId: string | null): string | null { if (!moveId) return null; for (const phase of boss.phases) { const move = phase.moves.find((m) => m.id === moveId); - if (move) return move.name; + if (move) return moveLabel(move); } return moveId; } From bdd6d557e1bfe9602421be0083de93e0e692401e Mon Sep 17 00:00:00 2001 From: Yilun Wu Date: Wed, 23 Sep 2026 23:26:23 -0400 Subject: [PATCH 2/6] Add an English/Chinese interface (V3 Milestone 4, part 2) - language switch in the nav bar; saved on the account (users.preferred_language, PATCH /api/auth/me) or in the browser for visitors, defaulting to the browser language - every interface string in both languages via a small in-house dictionary and t(); each language shows only its own text - Chinese boss content for the 8 existing bosses: locations, phase names, and all 234 move text fields, translated from the sourced English; the seed requires a Chinese version for every English field - Chinese moveset shows one note about translations; wiki-sourced names link to their page - reviewed migration with a hand-added check constraint Co-Authored-By: Claude Opus 5.5 --- ...abd4d80_bilingual_content_and_language_.py | 48 + backend/app/db/models.py | 12 + backend/app/models/analytics.py | 1 + backend/app/models/auth.py | 9 + backend/app/models/boss.py | 7 + backend/app/routers/auth.py | 11 +- backend/app/seed.py | 22 + backend/app/services/auth_service.py | 6 + backend/app/services/boss_service.py | 11 +- .../app/services/game_analytics_service.py | 5 +- backend/seed/bosses.json | 1726 ++++++++++++----- backend/tests/test_auth.py | 19 + backend/tests/test_move_chinese_names.py | 43 + backend/tests/test_sekiro_analytics.py | 1 + .../integration-tests/attemptWorkflow.test.ts | 17 + frontend/src/api/auth.test.ts | 4 +- frontend/src/api/auth.ts | 4 + frontend/src/api/bosses.test.ts | 3 +- frontend/src/auth/AuthProvider.test.tsx | 6 +- frontend/src/auth/testAuth.tsx | 2 +- .../src/components/AnalyticsPanel.test.tsx | 7 +- frontend/src/components/AnalyticsPanel.tsx | 58 +- .../src/components/AttemptHistory.test.tsx | 4 +- frontend/src/components/AttemptHistory.tsx | 13 +- frontend/src/components/BossList.tsx | 14 +- frontend/src/components/ChineseMoveName.tsx | 31 - .../src/components/MovesetReference.test.tsx | 41 +- frontend/src/components/MovesetReference.tsx | 56 +- frontend/src/components/NavBar.tsx | 36 +- frontend/src/components/PasswordInput.tsx | 9 +- .../src/components/ProgressionChart.test.tsx | 5 +- frontend/src/components/ProgressionChart.tsx | 23 +- .../src/components/RecordAttemptForm.test.tsx | 7 +- frontend/src/components/RecordAttemptForm.tsx | 42 +- frontend/src/i18n/LanguageProvider.test.tsx | 104 + frontend/src/i18n/LanguageProvider.tsx | 73 + frontend/src/i18n/content.ts | 31 + frontend/src/i18n/language.ts | 46 + frontend/src/i18n/messages.ts | 294 +++ frontend/src/i18n/testLanguage.tsx | 11 + frontend/src/index.css | 35 +- frontend/src/main.tsx | 5 +- frontend/src/pages/AuthPage.tsx | 47 +- frontend/src/pages/BossDashboardPage.test.tsx | 4 +- frontend/src/pages/BossDashboardPage.tsx | 48 +- frontend/src/pages/BossSelectionPage.test.tsx | 18 +- frontend/src/pages/BossSelectionPage.tsx | 8 +- frontend/src/pages/HomePage.test.tsx | 2 +- frontend/src/pages/HomePage.tsx | 4 +- .../src/pages/SekiroDashboardPage.test.tsx | 27 +- frontend/src/pages/SekiroDashboardPage.tsx | 106 +- frontend/src/pages/WelcomePage.tsx | 17 +- frontend/src/types/index.ts | 11 + frontend/src/utils/moves.test.ts | 18 +- frontend/src/utils/moves.ts | 29 +- 55 files changed, 2427 insertions(+), 814 deletions(-) create mode 100644 backend/alembic/versions/4b0d6abd4d80_bilingual_content_and_language_.py delete mode 100644 frontend/src/components/ChineseMoveName.tsx create mode 100644 frontend/src/i18n/LanguageProvider.test.tsx create mode 100644 frontend/src/i18n/LanguageProvider.tsx create mode 100644 frontend/src/i18n/content.ts create mode 100644 frontend/src/i18n/language.ts create mode 100644 frontend/src/i18n/messages.ts create mode 100644 frontend/src/i18n/testLanguage.tsx diff --git a/backend/alembic/versions/4b0d6abd4d80_bilingual_content_and_language_.py b/backend/alembic/versions/4b0d6abd4d80_bilingual_content_and_language_.py new file mode 100644 index 0000000..6709780 --- /dev/null +++ b/backend/alembic/versions/4b0d6abd4d80_bilingual_content_and_language_.py @@ -0,0 +1,48 @@ +"""bilingual content and language preference + +Revision ID: 4b0d6abd4d80 +Revises: 01e9ec08d5ac +Create Date: 2026-09-23 23:11:42.011448 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '4b0d6abd4d80' +down_revision: Union[str, Sequence[str], None] = '01e9ec08d5ac' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema: Chinese boss content and each user's interface language. + + Existing rows get NULLs; the next seed sync fills in the Chinese content. + """ + op.add_column('boss_phases', sa.Column('name_zh', sa.String(length=100), nullable=True)) + op.add_column('bosses', sa.Column('location_zh', sa.String(length=200), nullable=True)) + op.add_column('moves', sa.Column('description_zh', sa.Text(), nullable=True)) + op.add_column('moves', sa.Column('telegraph_zh', sa.Text(), nullable=True)) + op.add_column('moves', sa.Column('counter_zh', sa.Text(), nullable=True)) + op.add_column('moves', sa.Column('common_mistakes_zh', sa.Text(), nullable=True)) + op.add_column('users', sa.Column('preferred_language', sa.String(length=5), nullable=True)) + # Autogenerate does not detect check constraints; this mirrors models.User. + op.create_check_constraint( + 'ck_users_preferred_language', 'users', "preferred_language IS NULL OR preferred_language IN ('en', 'zh')" + ) + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_constraint('ck_users_preferred_language', 'users', type_='check') + op.drop_column('users', 'preferred_language') + op.drop_column('moves', 'common_mistakes_zh') + op.drop_column('moves', 'counter_zh') + op.drop_column('moves', 'telegraph_zh') + op.drop_column('moves', 'description_zh') + op.drop_column('bosses', 'location_zh') + op.drop_column('boss_phases', 'name_zh') diff --git a/backend/app/db/models.py b/backend/app/db/models.py index 3e3bb95..fbf976d 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -35,6 +35,7 @@ class Boss(Base): name: Mapped[str] = mapped_column(String(200)) name_zh: Mapped[str | None] = mapped_column(String(200)) location: Mapped[str] = mapped_column(String(200)) + location_zh: Mapped[str | None] = mapped_column(String(200)) # Where this boss's phase and move data came from; moves share their boss's source. source_name: Mapped[str | None] = mapped_column(String(200)) source_url: Mapped[str | None] = mapped_column(Text) @@ -54,6 +55,7 @@ class BossPhase(Base): boss_id: Mapped[int] = mapped_column(ForeignKey("bosses.id", ondelete="CASCADE")) phase_number: Mapped[int] = mapped_column(Integer) name: Mapped[str] = mapped_column(String(100)) + name_zh: Mapped[str | None] = mapped_column(String(100)) boss: Mapped[Boss] = relationship(back_populates="phases") move_links: Mapped[list["PhaseMove"]] = relationship( @@ -85,6 +87,11 @@ class Move(Base): telegraph: Mapped[str | None] = mapped_column(Text) counter: Mapped[str | None] = mapped_column(Text) common_mistakes: Mapped[str | None] = mapped_column(Text) + # Chinese versions of the text fields above: translations of the sourced English text. + description_zh: Mapped[str | None] = mapped_column(Text) + telegraph_zh: Mapped[str | None] = mapped_column(Text) + counter_zh: Mapped[str | None] = mapped_column(Text) + common_mistakes_zh: Mapped[str | None] = mapped_column(Text) # Chinese name: either the name used by a Chinese wiki ('wiki', with the page in # name_zh_source_url) or a translation ('translation'), never presented as official. name_zh: Mapped[str | None] = mapped_column(String(200)) @@ -149,12 +156,17 @@ class User(Base): __tablename__ = "users" __table_args__ = ( CheckConstraint("username = lower(username)", name="ck_users_username_lowercase"), + CheckConstraint( + "preferred_language IS NULL OR preferred_language IN ('en', 'zh')", name="ck_users_preferred_language" + ), ) id: Mapped[int] = mapped_column(primary_key=True) # Stored lowercase so usernames are unique regardless of case. username: Mapped[str] = mapped_column(String(30), unique=True) password_hash: Mapped[str] = mapped_column(String(255)) + # Interface language chosen in the app; null until the user picks one. + preferred_language: Mapped[str | None] = mapped_column(String(5)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) sessions: Mapped[list["UserSession"]] = relationship(back_populates="user", cascade="all, delete-orphan") diff --git a/backend/app/models/analytics.py b/backend/app/models/analytics.py index 04ec0ca..445ce3e 100644 --- a/backend/app/models/analytics.py +++ b/backend/app/models/analytics.py @@ -54,6 +54,7 @@ class RecentAttempt(BaseModel): failure_move_id: str | None = None failure_move_name: str | None = None failure_move_name_zh: str | None = None + boss_name_zh: str | None = None failure_category: FailureCategory | None = None diff --git a/backend/app/models/auth.py b/backend/app/models/auth.py index 80aee60..c56de41 100644 --- a/backend/app/models/auth.py +++ b/backend/app/models/auth.py @@ -1,4 +1,5 @@ import re +from typing import Literal from pydantic import BaseModel, Field, field_validator @@ -28,6 +29,14 @@ class LoginRequest(BaseModel): password: str = Field(max_length=128) +Language = Literal["en", "zh"] + + class User(BaseModel): id: str username: str + preferred_language: Language | None = None + + +class UpdateUserRequest(BaseModel): + preferred_language: Language diff --git a/backend/app/models/boss.py b/backend/app/models/boss.py index f956ce8..2d23748 100644 --- a/backend/app/models/boss.py +++ b/backend/app/models/boss.py @@ -12,6 +12,10 @@ class BossMove(BaseModel): counter: str | None = None common_mistakes: str | None = None name_zh: str | None = None + description_zh: str | None = None + telegraph_zh: str | None = None + counter_zh: str | None = None + common_mistakes_zh: str | None = None # "wiki": the name used by a Chinese wiki, see name_zh_source_url. # "translation": translated for this app; not an official name. name_zh_source: Literal["wiki", "translation"] | None = None @@ -29,6 +33,7 @@ def check_chinese_name_source(self) -> "BossMove": class BossPhase(BaseModel): phase_number: int name: str + name_zh: str | None = None moves: list[BossMove] = [] @@ -38,6 +43,7 @@ class Boss(BaseModel): name_zh: str | None = None game: str location: str + location_zh: str | None = None phases: list[BossPhase] source_name: str | None = None source_url: str | None = None @@ -48,3 +54,4 @@ class BossSummary(BaseModel): name: str name_zh: str | None = None location: str + location_zh: str | None = None diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index 69423f6..b536382 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -4,14 +4,14 @@ from app.auth import SESSION_COOKIE, clear_session_cookie, get_current_user, set_session_cookie from app.db import models from app.db.session import get_db -from app.models.auth import LoginRequest, RegisterRequest, User +from app.models.auth import LoginRequest, RegisterRequest, UpdateUserRequest, User from app.services import auth_service router = APIRouter(prefix="/api/auth", tags=["auth"]) def _to_response(user: models.User) -> User: - return User(id=str(user.id), username=user.username) + return User(id=str(user.id), username=user.username, preferred_language=user.preferred_language) @router.post("/register", response_model=User, status_code=status.HTTP_201_CREATED) @@ -48,3 +48,10 @@ def logout( @router.get("/me", response_model=User) def me(user: models.User = Depends(get_current_user)): return _to_response(user) + + +@router.patch("/me", response_model=User) +def update_me( + req: UpdateUserRequest, user: models.User = Depends(get_current_user), db: Session = Depends(get_db) +): + return _to_response(auth_service.set_preferred_language(db, user, req.preferred_language)) diff --git a/backend/app/seed.py b/backend/app/seed.py index fea609b..4154176 100644 --- a/backend/app/seed.py +++ b/backend/app/seed.py @@ -26,8 +26,15 @@ "name_zh", "name_zh_source", "name_zh_source_url", + "description_zh", + "telegraph_zh", + "counter_zh", + "common_mistakes_zh", ) +# Every English text field that has a value needs its Chinese version. +TRANSLATED_MOVE_FIELDS = ("description", "telegraph", "counter", "common_mistakes") + class SeedError(ValueError): pass @@ -57,6 +64,12 @@ def _validate(bosses: list[BossData]) -> None: if boss.game not in GAMES: errors.append(f"boss '{boss.id}' references unknown game '{boss.game}'") + if boss.location_zh is None: + errors.append(f"boss '{boss.id}' has no Chinese location (location_zh)") + for phase in boss.phases: + if phase.name_zh is None: + errors.append(f"boss '{boss.id}' phase {phase.phase_number} has no Chinese name (name_zh)") + numbers = [p.phase_number for p in boss.phases] if len(numbers) != len(set(numbers)): errors.append(f"boss '{boss.id}' has duplicate phase numbers") @@ -69,6 +82,13 @@ def _validate(bosses: list[BossData]) -> None: for move in phase.moves: if move.name_zh is None: errors.append(f"boss '{boss.id}' move '{move.id}' has no Chinese name (name_zh)") + for field_name in TRANSLATED_MOVE_FIELDS: + has_english = getattr(move, field_name) is not None + has_chinese = getattr(move, f"{field_name}_zh") is not None + if has_english != has_chinese: + errors.append( + f"boss '{boss.id}' move '{move.id}': {field_name} and {field_name}_zh must both be set or both be empty" + ) data = move.model_dump(include=set(MOVE_FIELDS)) if move.id not in first_seen: first_seen[move.id] = (phase.phase_number, data) @@ -118,6 +138,7 @@ def sync_reference_data(session: Session, bosses: list[BossData]) -> SyncReport: boss.name = data.name boss.name_zh = data.name_zh boss.location = data.location + boss.location_zh = data.location_zh boss.source_name = data.source_name boss.source_url = data.source_url report.bosses += 1 @@ -147,6 +168,7 @@ def sync_reference_data(session: Session, bosses: list[BossData]) -> SyncReport: phase = BossPhase(phase_number=phase_data.phase_number) boss.phases.append(phase) phase.name = phase_data.name + phase.name_zh = phase_data.name_zh report.phases += 1 phase.move_links.clear() diff --git a/backend/app/services/auth_service.py b/backend/app/services/auth_service.py index 73b996d..b2bfd6b 100644 --- a/backend/app/services/auth_service.py +++ b/backend/app/services/auth_service.py @@ -65,6 +65,12 @@ def authenticate(db: Session, username: str, password: str) -> User | None: return user +def set_preferred_language(db: Session, user: User, language: str) -> User: + user.preferred_language = language + db.commit() + return user + + def create_session(db: Session, user: User) -> str: """Starts a session for the user and returns the raw token for the cookie.""" db.execute(delete(UserSession).where(UserSession.user_id == user.id, UserSession.expires_at <= _now())) diff --git a/backend/app/services/boss_service.py b/backend/app/services/boss_service.py index 72da43f..bbe5de1 100644 --- a/backend/app/services/boss_service.py +++ b/backend/app/services/boss_service.py @@ -7,7 +7,10 @@ def get_all_bosses(session: Session) -> list[BossSummary]: rows = session.scalars(select(db.Boss).order_by(db.Boss.id)) - return [BossSummary(id=b.slug, name=b.name, name_zh=b.name_zh, location=b.location) for b in rows] + return [ + BossSummary(id=b.slug, name=b.name, name_zh=b.name_zh, location=b.location, location_zh=b.location_zh) + for b in rows + ] def get_boss_row(session: Session, boss_slug: str) -> db.Boss | None: @@ -31,12 +34,14 @@ def get_boss(session: Session, boss_slug: str) -> Boss | None: name_zh=boss.name_zh, game=boss.game.slug, location=boss.location, + location_zh=boss.location_zh, source_name=boss.source_name, source_url=boss.source_url, phases=[ BossPhase( phase_number=phase.phase_number, name=phase.name, + name_zh=phase.name_zh, moves=[ BossMove( id=link.move.slug, @@ -47,6 +52,10 @@ def get_boss(session: Session, boss_slug: str) -> Boss | None: counter=link.move.counter, common_mistakes=link.move.common_mistakes, name_zh=link.move.name_zh, + description_zh=link.move.description_zh, + telegraph_zh=link.move.telegraph_zh, + counter_zh=link.move.counter_zh, + common_mistakes_zh=link.move.common_mistakes_zh, name_zh_source=link.move.name_zh_source, name_zh_source_url=link.move.name_zh_source_url, ) diff --git a/backend/app/services/game_analytics_service.py b/backend/app/services/game_analytics_service.py index 5314b98..c9aaae9 100644 --- a/backend/app/services/game_analytics_service.py +++ b/backend/app/services/game_analytics_service.py @@ -88,7 +88,7 @@ def _boss_rows(session: Session, user_id: int) -> list[BossComparisonRow]: def _recent_attempts(session: Session, user_id: int) -> list[RecentAttempt]: rows = session.execute( - select(db.Attempt, db.Boss.slug, db.Boss.name, db.Move.slug, db.Move.name, db.Move.name_zh) + select(db.Attempt, db.Boss.slug, db.Boss.name, db.Boss.name_zh, db.Move.slug, db.Move.name, db.Move.name_zh) .join(db.Boss, db.Attempt.boss_id == db.Boss.id) .outerjoin(db.Move, db.Attempt.failure_move_id == db.Move.id) .where(db.Attempt.user_id == user_id) @@ -100,6 +100,7 @@ def _recent_attempts(session: Session, user_id: int) -> list[RecentAttempt]: attempt_id=str(attempt.id), boss_id=boss_slug, boss_name=boss_name, + boss_name_zh=boss_name_zh, timestamp=attempt.created_at, result=AttemptResult(attempt.result), phase_reached=attempt.phase_reached, @@ -108,7 +109,7 @@ def _recent_attempts(session: Session, user_id: int) -> list[RecentAttempt]: failure_move_name_zh=move_name_zh, failure_category=FailureCategory(attempt.failure_category) if attempt.failure_category else None, ) - for attempt, boss_slug, boss_name, move_slug, move_name, move_name_zh in rows + for attempt, boss_slug, boss_name, boss_name_zh, move_slug, move_name, move_name_zh in rows ] diff --git a/backend/seed/bosses.json b/backend/seed/bosses.json index e57a300..bba7095 100644 --- a/backend/seed/bosses.json +++ b/backend/seed/bosses.json @@ -5,444 +5,588 @@ "name_zh": "苇名弦一郎", "game": "sekiro", "location": "Ashina Castle - Castle Tower Lookout", + "location_zh": "苇名城 - 天守阁瞭望台", "source_name": "Fandom Sekiro Wiki", "source_url": "https://sekiro-shadows-die-twice.fandom.com/wiki/Genichiro_Ashina,_Second_Encounter", "phases": [ { "phase_number": 1, "name": "Phase 1", + "name_zh": "第一阶段", "moves": [ { "id": "perilous-attack-follow-up", "name": "Perilous Attack Follow Up", + "name_zh": "危攻击后续追击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Delivers an overhead smash while airborne and connects with a Perilous Thrust upon landing.", + "description_zh": "跃起在空中向下重砸,落地后接一记危·突刺。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "In this phase he always follows with a Perilous Thrust, opening him up to an easy Mikiri Counter.", + "counter_zh": "这个阶段他一定会接危·突刺,正好可以轻松识破。", "common_mistakes": null, - "name_zh": "危攻击后续追击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-attack", "name": "Perilous Attack", + "name_zh": "危攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Runs towards Wolf and does a plunging attack.", + "description_zh": "冲向狼,然后发动一记下落攻击。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "危攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-grab", "name": "Perilous Grab", + "name_zh": "危·投技", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "A sideways jumping slash to the left, then a quick elbow jab; if it connects, he punches Wolf in the stomach.", + "description_zh": "向左侧跳斩,接着迅速用手肘一顶;如果命中,他会一拳打在狼的腹部。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "Striking him during this attack topples his balance, even with a visible block at the end of the move.", + "counter_zh": "在这招期间攻击他能打乱他的平衡,即使招式结尾他明显在防御也一样。", "common_mistakes": null, - "name_zh": "危·投技", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-thrust", "name": "Perilous Thrust", + "name_zh": "危·突刺", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "Thrusts with his katana.", + "description_zh": "用太刀突刺。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "Mikiri Counter.", + "counter_zh": "识破。", "common_mistakes": null, - "name_zh": "危·突刺", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-sweep", "name": "Perilous Sweep", + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Sweeps the floor and follows up with an arrow shot.", + "description_zh": "贴地横扫,接着射出一箭。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "危·下段横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "advance", "name": "Advance", + "name_zh": "突进", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Two horizontal slashes while stepping forth.", + "description_zh": "向前踏步的同时横斩两次。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "突进", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "chasing-slice", "name": "Chasing Slice", + "name_zh": "追击斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Runs a short distance, then slashes twice.", + "description_zh": "跑出一小段距离,然后斩击两次。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "追击斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "closing-tide", "name": "Closing Tide", + "name_zh": "涨潮", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Slides and shoots one or two arrows, then rolls forward and slashes once. In Phase 1, it is always his first attack.", + "description_zh": "滑步射出一到两支箭,然后向前翻滚斩击一次。第一阶段他的第一招一定是这招。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "涨潮", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "floating-passage", "name": "Floating Passage", + "name_zh": "绝技·飞渡浮舟", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F", "move_type": "combo", "description": "A deadly seven-hit combo ending in a guard-breaking strike. If the target is too far away, he may cancel it at any time into an arrow shot.", + "description_zh": "致命的七连击,最后一击能破防。如果目标离得太远,他可能随时中断连击改为射箭。", "telegraph": "Starts with him sliding to the left.", + "telegraph_zh": "起手时他会向左滑步。", "counter": "If your posture bar is almost full, deflect the first two hits, then dodge away and punish him once he finishes.", + "counter_zh": "如果你的躯干快满了,先弹开前两击,然后闪避拉开距离,等他打完再反击。", "common_mistakes": "Getting too far away during the combo - he interrupts it with a Guard Breaking Shot.", - "name_zh": "绝技·飞渡浮舟", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F" + "common_mistakes_zh": "连击过程中离他太远——他会用破防射击打断连击。" }, { "id": "guard-breaking-shot", "name": "Guard Breaking Shot", + "name_zh": "破防射击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Notches an arrow with incredible force, capable of breaking guard when released.", + "description_zh": "以极大的力道拉弓,射出后能破防。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": "Healing in front of him - he answers every use of the Healing Gourd or another healing item with this shot.", - "name_zh": "破防射击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "在他面前回复——你每次使用伤药葫芦或其他回复道具,他都会用这一箭回应。" }, { "id": "hawk-shot", "name": "Hawk Shot", + "name_zh": "鹰射", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Stays airborne and shoots rapidly four times.", + "description_zh": "停在空中快速连射四箭。", "telegraph": "Always dashes backward first.", + "telegraph_zh": "起手前一定会先向后冲刺。", "counter": "Interrupt the move as it starts, or dodge all the arrows by dashing twice toward him.", + "counter_zh": "在起手时打断这招,或者朝他冲刺两次躲开所有箭矢。", "common_mistakes": null, - "name_zh": "鹰射", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "iaijutsu", "name": "Iaijutsu", + "name_zh": "居合斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A quick drawn strike with a short closing distance.", + "description_zh": "一记快速的拔刀斩,突进距离较短。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "居合斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "receding-tide", "name": "Receding Tide", + "name_zh": "退潮", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Slides back and shoots two arrows at the same time, then prepares for an Iaijutsu.", + "description_zh": "向后滑步,同时射出两支箭,然后准备居合斩。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "退潮", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "two-hit-combo", "name": "Two-hit Combo", + "name_zh": "二连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Spins his body, slashing, then kicks right after. May follow up with a Guard Breaking Shot.", + "description_zh": "转身斩击,紧接着踢一脚。之后可能接破防射击。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "二连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] }, { "phase_number": 2, "name": "Phase 2", + "name_zh": "第二阶段", "moves": [ { "id": "perilous-attack-follow-up-phase-2", "name": "Perilous Attack Follow Up (Phase 2)", + "name_zh": "危攻击后续追击(第二阶段)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Delivers an overhead smash while airborne and connects with a Perilous Thrust or a Perilous Sweep upon landing.", + "description_zh": "跃起在空中向下重砸,落地后接危·突刺或危·下段横扫。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "危攻击后续追击(第二阶段)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-attack", "name": "Perilous Attack", + "name_zh": "危攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Runs towards Wolf and does a plunging attack.", + "description_zh": "冲向狼,然后发动一记下落攻击。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "危攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-grab", "name": "Perilous Grab", + "name_zh": "危·投技", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "A sideways jumping slash to the left, then a quick elbow jab; if it connects, he punches Wolf in the stomach.", + "description_zh": "向左侧跳斩,接着迅速用手肘一顶;如果命中,他会一拳打在狼的腹部。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "Striking him during this attack topples his balance, even with a visible block at the end of the move.", + "counter_zh": "在这招期间攻击他能打乱他的平衡,即使招式结尾他明显在防御也一样。", "common_mistakes": null, - "name_zh": "危·投技", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-thrust", "name": "Perilous Thrust", + "name_zh": "危·突刺", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "Thrusts with his katana.", + "description_zh": "用太刀突刺。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "Mikiri Counter.", + "counter_zh": "识破。", "common_mistakes": null, - "name_zh": "危·突刺", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-sweep", "name": "Perilous Sweep", + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Sweeps the floor and follows up with an arrow shot.", + "description_zh": "贴地横扫,接着射出一箭。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "危·下段横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "advance", "name": "Advance", + "name_zh": "突进", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Two horizontal slashes while stepping forth.", + "description_zh": "向前踏步的同时横斩两次。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "突进", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "chasing-slice", "name": "Chasing Slice", + "name_zh": "追击斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Runs a short distance, then slashes twice.", + "description_zh": "跑出一小段距离,然后斩击两次。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "追击斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "closing-tide", "name": "Closing Tide", + "name_zh": "涨潮", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Slides and shoots one or two arrows, then rolls forward and slashes once. In Phase 1, it is always his first attack.", + "description_zh": "滑步射出一到两支箭,然后向前翻滚斩击一次。第一阶段他的第一招一定是这招。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "涨潮", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "floating-passage", "name": "Floating Passage", + "name_zh": "绝技·飞渡浮舟", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F", "move_type": "combo", "description": "A deadly seven-hit combo ending in a guard-breaking strike. If the target is too far away, he may cancel it at any time into an arrow shot.", + "description_zh": "致命的七连击,最后一击能破防。如果目标离得太远,他可能随时中断连击改为射箭。", "telegraph": "Starts with him sliding to the left.", + "telegraph_zh": "起手时他会向左滑步。", "counter": "If your posture bar is almost full, deflect the first two hits, then dodge away and punish him once he finishes.", + "counter_zh": "如果你的躯干快满了,先弹开前两击,然后闪避拉开距离,等他打完再反击。", "common_mistakes": "Getting too far away during the combo - he interrupts it with a Guard Breaking Shot.", - "name_zh": "绝技·飞渡浮舟", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F" + "common_mistakes_zh": "连击过程中离他太远——他会用破防射击打断连击。" }, { "id": "guard-breaking-shot", "name": "Guard Breaking Shot", + "name_zh": "破防射击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Notches an arrow with incredible force, capable of breaking guard when released.", + "description_zh": "以极大的力道拉弓,射出后能破防。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": "Healing in front of him - he answers every use of the Healing Gourd or another healing item with this shot.", - "name_zh": "破防射击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "在他面前回复——你每次使用伤药葫芦或其他回复道具,他都会用这一箭回应。" }, { "id": "hawk-shot", "name": "Hawk Shot", + "name_zh": "鹰射", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Stays airborne and shoots rapidly four times.", + "description_zh": "停在空中快速连射四箭。", "telegraph": "Always dashes backward first.", + "telegraph_zh": "起手前一定会先向后冲刺。", "counter": "Interrupt the move as it starts, or dodge all the arrows by dashing twice toward him.", + "counter_zh": "在起手时打断这招,或者朝他冲刺两次躲开所有箭矢。", "common_mistakes": null, - "name_zh": "鹰射", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "iaijutsu", "name": "Iaijutsu", + "name_zh": "居合斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A quick drawn strike with a short closing distance.", + "description_zh": "一记快速的拔刀斩,突进距离较短。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "居合斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "receding-tide", "name": "Receding Tide", + "name_zh": "退潮", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Slides back and shoots two arrows at the same time, then prepares for an Iaijutsu.", + "description_zh": "向后滑步,同时射出两支箭,然后准备居合斩。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "退潮", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "two-hit-combo", "name": "Two-hit Combo", + "name_zh": "二连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Spins his body, slashing, then kicks right after. May follow up with a Guard Breaking Shot.", + "description_zh": "转身斩击,紧接着踢一脚。之后可能接破防射击。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "二连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] }, { "phase_number": 3, "name": "Phase 3", + "name_zh": "第三阶段", "moves": [ { "id": "deaths-thrust", "name": "Death's Thrust", + "name_zh": "死亡突刺", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "Lunges forward, thrusting over a long distance. He always opens Phase 3 with this move. May follow up with a Perilous Grab.", + "description_zh": "向前扑刺,突刺距离很长。第三阶段他一定以这招开场。之后可能接危·投技。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": null, + "counter_zh": null, "common_mistakes": "Using an item - he answers it with this thrust.", - "name_zh": "死亡突刺", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "使用道具——他会用这记突刺回应。" }, { "id": "floating-passage-phase-3", "name": "Floating Passage (Phase 3)", + "name_zh": "绝技·飞渡浮舟(第三阶段)", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F", "move_type": "combo", "description": "A deadly seven-hit combo ending in a guard-breaking strike, followed by a delayed Perilous Thrust. If the target is too far away, he may cancel it at any time into an arrow shot.", + "description_zh": "致命的七连击,最后一击能破防,之后还有一记延迟的危·突刺。如果目标离得太远,他可能随时中断连击改为射箭。", "telegraph": "Perilous Attack kanji. Starts with him sliding to the left.", + "telegraph_zh": "“危”字提示。起手时他会向左滑步。", "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "绝技·飞渡浮舟(第三阶段)", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E9%A3%9E%E6%B8%A1%E6%B5%AE%E8%88%9F" + "common_mistakes_zh": null }, { "id": "perilous-attack-follow-up-phase-3", "name": "Perilous Attack Follow Up (Phase 3)", + "name_zh": "危攻击后续追击(第三阶段)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Delivers an overhead smash while airborne, then one of three follow-ups: a Perilous Thrust, a Perilous Sweep, or a quick upward cut that, if deflected, is followed by a delayed Perilous Thrust.", + "description_zh": "跃起在空中向下重砸,然后接三种后续之一:危·突刺、危·下段横扫,或者一记快速上挑;上挑被弹开后,他会再接一记延迟的危·突刺。", "telegraph": "Perilous Attack kanji. After landing: crouching down means a Perilous Thrust, turning clockwise means a Perilous Sweep, turning anti-clockwise means the quick upward cut.", + "telegraph_zh": "“危”字提示。落地后:蹲下是危·突刺,顺时针转身是危·下段横扫,逆时针转身是快速上挑。", "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "危攻击后续追击(第三阶段)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-grab-phase-3", "name": "Perilous Grab (Phase 3)", + "name_zh": "危·投技(第三阶段)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Runs toward Wolf attempting to grab him. If it connects, he throws Wolf to the ground and stabs him in the chest.", + "description_zh": "冲向狼试图抓住他。如果命中,会把狼摔在地上并刺穿胸口。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "危·投技(第三阶段)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "lightning-of-tomoe-shot", "name": "Lightning of Tomoe Shot", + "name_zh": "巴之雷·射击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "lightning", "description": "Shoots an electrifying arrow that instantly inflicts Shock.", + "description_zh": "射出一支带电的箭,立即造成感电。", "telegraph": "Jumps up and imbues his arrow with lightning.", + "telegraph_zh": "跃起,让箭矢缠上雷电。", "counter": "Lightning Reversal sends it back for significant posture and vitality damage and briefly paralyzes him. His lightning attacks can also be interrupted by hitting him, or avoided by dashing under him.", + "counter_zh": "用雷反把雷电返还给他,能造成大量躯干和体力伤害,并让他短暂麻痹。他的雷电攻击也可以通过攻击他来打断,或者冲刺到他身下躲开。", "common_mistakes": null, - "name_zh": "巴之雷·射击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "lightning-of-tomoe-slash", "name": "Lightning of Tomoe Slash", + "name_zh": "巴之雷·斩击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "lightning", "description": "Leaps up, then slashes forward in a long horizontal arc of lightning.", + "description_zh": "跃起,然后向前挥出一道长长的横向雷电弧。", "telegraph": "Raises his katana to his right with both hands as it is imbued with lightning.", + "telegraph_zh": "双手把太刀举到右侧,刀身缠上雷电。", "counter": "Lightning Reversal sends it back for significant posture and vitality damage and briefly paralyzes him. His lightning attacks can also be interrupted by hitting him, or avoided by dashing under him.", + "counter_zh": "用雷反把雷电返还给他,能造成大量躯干和体力伤害,并让他短暂麻痹。他的雷电攻击也可以通过攻击他来打断,或者冲刺到他身下躲开。", "common_mistakes": null, - "name_zh": "巴之雷·斩击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "lightning-of-tomoe-smash", "name": "Lightning of Tomoe Smash", + "name_zh": "巴之雷·砸击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "lightning", "description": "Leaps up, then smashes straight down with lightning.", + "description_zh": "跃起,然后带着雷电垂直向下重砸。", "telegraph": "Raises his katana to his left as it is imbued with lightning.", + "telegraph_zh": "把太刀举到左侧,刀身缠上雷电。", "counter": "Lightning Reversal sends it back for significant posture and vitality damage and briefly paralyzes him. His lightning attacks can also be interrupted by hitting him, or avoided by dashing under him.", + "counter_zh": "用雷反把雷电返还给他,能造成大量躯干和体力伤害,并让他短暂麻痹。他的雷电攻击也可以通过攻击他来打断,或者冲刺到他身下躲开。", "common_mistakes": null, - "name_zh": "巴之雷·砸击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] } @@ -454,366 +598,485 @@ "name_zh": "义父", "game": "sekiro", "location": "Hirata Estate Revisited - Hidden Temple", + "location_zh": "平田宅邸(回忆)- 佛殿", "source_name": "Fextralife Sekiro Wiki", "source_url": "https://sekiroshadowsdietwice.wiki.fextralife.com/Owl+(Father)", "phases": [ { "phase_number": 1, "name": "Phase 1", + "name_zh": "第一阶段", "moves": [ { "id": "zig-zag", "name": "Zig-Zag", + "name_zh": "之字形移动", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "movement", "description": "Many of Owl's attacks begin with a zig-zag motion designed to confuse the player, leading into one of five possible follow-up attacks.", + "description_zh": "义父的很多攻击都以之字形移动起手,用来迷惑玩家,之后会接五种攻击之一。", "telegraph": null, + "telegraph_zh": null, "counter": "Stay ready to react to whichever attack follows.", + "counter_zh": "随时准备应对接下来的攻击。", "common_mistakes": null, - "name_zh": "之字形移动", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "shuriken-throw", "name": "Shuriken Throw", + "name_zh": "投掷手里剑", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "A single shuriken projectile attack.", + "description_zh": "投掷一枚手里剑。", "telegraph": null, + "telegraph_zh": null, "counter": "Block, deflect, or dodge to the side.", + "counter_zh": "防御、弹刀,或者向侧面闪避。", "common_mistakes": null, - "name_zh": "投掷手里剑", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "double-shuriken-flip", "name": "Double Shuriken + Flip", + "name_zh": "双手里剑 + 后翻", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Two shuriken throws followed by an aerial flip and downward sword strike.", + "description_zh": "投掷两枚手里剑,然后空翻并向下挥刀。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect/block the shurikens and dodge sideways to avoid the flip, then counter.", + "counter_zh": "弹开或防御手里剑,再向侧面闪避躲开空翻斩,然后反击。", "common_mistakes": null, - "name_zh": "双手里剑 + 后翻", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "shadowfall", "name": "Shadowfall", + "name_zh": "秘传·巨型忍者落杀", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E5%B7%A8%E5%9E%8B%E5%BF%8D%E8%80%85%E8%90%BD%E6%9D%80", "move_type": "thrust", "description": "Unblockable thrust; if it hits, he jumps off and comes down with two huge slashes.", + "description_zh": "无法防御的突刺;如果命中,他会跳起再落下,接两记巨大的斩击。", "telegraph": "Charges up before the unblockable thrust.", + "telegraph_zh": "无法防御的突刺前会蓄力。", "counter": "Mikiri the thrust.", + "counter_zh": "识破这记突刺。", "common_mistakes": null, - "name_zh": "秘传·巨型忍者落杀", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E5%B7%A8%E5%9E%8B%E5%BF%8D%E8%80%85%E8%90%BD%E6%9D%80" + "common_mistakes_zh": null }, { "id": "shuriken-slash", "name": "Shuriken Slash", + "name_zh": "手里剑 + 斩击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Throws a shuriken immediately followed by a running slash.", + "description_zh": "投掷一枚手里剑,紧接着冲刺斩。", "telegraph": null, + "telegraph_zh": null, "counter": "Block/deflect both. A well-timed dodge to the right is also possible.", + "counter_zh": "两下都防御或弹开。时机准确的话,也可以向右闪避。", "common_mistakes": null, - "name_zh": "手里剑 + 斩击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-sweep", "name": "Perilous Sweep", + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Unblockable sweep attack performed from the side.", + "description_zh": "从侧面发动的无法防御的横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Jump over it and stay close to bait his overhead slash, or jump back for a jump kick to the head.", + "counter_zh": "跳过去并留在他身边,引诱他出下劈;或者向后跳,踩头反击。", "common_mistakes": null, - "name_zh": "危·下段横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "large-overhead-slash", "name": "Large Overhead Slash", + "name_zh": "大幅度下劈", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "overhead", "description": "A very powerful overhead slash that adapts based on the player's response.", + "description_zh": "威力极大的下劈,会根据玩家的反应改变出招。", "telegraph": "Loads up for the overhead slash.", + "telegraph_zh": "蓄力准备下劈。", "counter": "Dodge at the last moment and counter, or step back out of range. Hits can land during the charge-up.", + "counter_zh": "在最后一刻闪避再反击,或者后退到范围外。他蓄力期间可以打中他。", "common_mistakes": "Dodging too early makes him switch to a quick horizontal attack.", - "name_zh": "大幅度下劈", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "闪避太早会让他改成一记快速横斩。" }, { "id": "backflip", "name": "Backflip", + "name_zh": "后空翻", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "movement", "description": "Leaps away; deals no vitality damage but significant posture damage.", + "description_zh": "向后跃开;不造成体力伤害,但会造成大量躯干伤害。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect to minimize posture damage.", + "counter_zh": "弹刀以尽量减少躯干伤害。", "common_mistakes": null, - "name_zh": "后空翻", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "firecrackers", "name": "Firecrackers", + "name_zh": "爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Throws blinding firecrackers followed by an explosion; can chain into Shuriken Throw, Shadowfall, or Shuriken Slash.", + "description_zh": "扔出致盲的爆竹并引发爆炸;之后可能接投掷手里剑、秘传·巨型忍者落杀或手里剑 + 斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Dodge backwards to avoid.", + "counter_zh": "向后闪避躲开。", "common_mistakes": "Dodging too quickly after the throw lets his follow-up slash hit you.", - "name_zh": "爆竹", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "他扔出爆竹后闪避得太快,会被他接下来的斩击命中。" }, { "id": "running-slash", "name": "Running Slash", + "name_zh": "冲刺斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A single strong slash performed while approaching the player.", + "description_zh": "接近玩家时发动的一记强力斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Block/deflect. Dodging to the left is also possible.", + "counter_zh": "防御或弹刀。也可以向左闪避。", "common_mistakes": null, - "name_zh": "冲刺斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "five-slash-firecracker-combo", "name": "5-Slash Firecracker Combo", + "name_zh": "五连斩接爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Three consecutive sword slashes, followed by an elbow strike, followed by firecrackers that he slashes through.", + "description_zh": "连续三次挥刀,接一记肘击,然后扔出爆竹并挥刀穿过。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect/block the slashes, then dodge right when the firecrackers are thrown for a counter opportunity.", + "counter_zh": "弹开或防御前几刀,在他扔出爆竹时向右闪避,寻找反击机会。", "common_mistakes": "Getting pushed back into a corner by the combo, where there is no chance of survival.", - "name_zh": "五连斩接爆竹", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "被这套连击逼进角落,那里几乎没有活路。" }, { "id": "double-slash-perilous-sweep", "name": "Double Slash into Perilous Sweep", + "name_zh": "二连斩接危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Two consecutive slashes ending in an unblockable sweep; can chain into Large Overhead Slash.", + "description_zh": "连续两次斩击,最后接一记无法防御的横扫;之后可能接大幅度下劈。", "telegraph": null, + "telegraph_zh": null, "counter": "Jump over it and stay close to bait his overhead slash.", + "counter_zh": "跳过去并留在他身边,引诱他出下劈。", "common_mistakes": null, - "name_zh": "二连斩接危·下段横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "stomp-deathblow", "name": "Stomp (Deathblow)", + "name_zh": "踩踏(忍杀)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Raises a foot then stomps down, pinning the player and stabbing through the chest for massive damage. Cannot be blocked and gives no Perilous indicator.", + "description_zh": "抬起一只脚然后踩下,把玩家踩住并刺穿胸口,造成大量伤害。无法防御,也没有“危”字提示。", "telegraph": "Raises his foot into the air before stomping down. There is no Perilous indicator.", + "telegraph_zh": "踩下前会把脚高高抬起。没有“危”字提示。", "counter": "Dodge backwards or safety roll to avoid it.", + "counter_zh": "向后闪避或受身翻滚躲开。", "common_mistakes": null, - "name_zh": "踩踏(忍杀)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] }, { "phase_number": 2, "name": "Phase 2", + "name_zh": "第二阶段", "moves": [ { "id": "zig-zag", "name": "Zig-Zag", + "name_zh": "之字形移动", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "movement", "description": "Many of Owl's attacks begin with a zig-zag motion designed to confuse the player, leading into one of five possible follow-up attacks.", + "description_zh": "义父的很多攻击都以之字形移动起手,用来迷惑玩家,之后会接五种攻击之一。", "telegraph": null, + "telegraph_zh": null, "counter": "Stay ready to react to whichever attack follows.", + "counter_zh": "随时准备应对接下来的攻击。", "common_mistakes": null, - "name_zh": "之字形移动", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "shuriken-throw", "name": "Shuriken Throw", + "name_zh": "投掷手里剑", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "A single shuriken projectile attack.", + "description_zh": "投掷一枚手里剑。", "telegraph": null, + "telegraph_zh": null, "counter": "Block, deflect, or dodge to the side.", + "counter_zh": "防御、弹刀,或者向侧面闪避。", "common_mistakes": null, - "name_zh": "投掷手里剑", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "double-shuriken-flip", "name": "Double Shuriken + Flip", + "name_zh": "双手里剑 + 后翻", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Two shuriken throws followed by an aerial flip and downward sword strike.", + "description_zh": "投掷两枚手里剑,然后空翻并向下挥刀。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect/block the shurikens and dodge sideways to avoid the flip, then counter.", + "counter_zh": "弹开或防御手里剑,再向侧面闪避躲开空翻斩,然后反击。", "common_mistakes": null, - "name_zh": "双手里剑 + 后翻", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "shadowfall", "name": "Shadowfall", + "name_zh": "秘传·巨型忍者落杀", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E5%B7%A8%E5%9E%8B%E5%BF%8D%E8%80%85%E8%90%BD%E6%9D%80", "move_type": "thrust", "description": "Unblockable thrust; if it hits, he jumps off and comes down with two huge slashes.", + "description_zh": "无法防御的突刺;如果命中,他会跳起再落下,接两记巨大的斩击。", "telegraph": "Charges up before the unblockable thrust.", + "telegraph_zh": "无法防御的突刺前会蓄力。", "counter": "Mikiri the thrust.", + "counter_zh": "识破这记突刺。", "common_mistakes": null, - "name_zh": "秘传·巨型忍者落杀", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E5%B7%A8%E5%9E%8B%E5%BF%8D%E8%80%85%E8%90%BD%E6%9D%80" + "common_mistakes_zh": null }, { "id": "shuriken-slash", "name": "Shuriken Slash", + "name_zh": "手里剑 + 斩击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Throws a shuriken immediately followed by a running slash.", + "description_zh": "投掷一枚手里剑,紧接着冲刺斩。", "telegraph": null, + "telegraph_zh": null, "counter": "Block/deflect both. A well-timed dodge to the right is also possible.", + "counter_zh": "两下都防御或弹开。时机准确的话,也可以向右闪避。", "common_mistakes": null, - "name_zh": "手里剑 + 斩击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-sweep", "name": "Perilous Sweep", + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Unblockable sweep attack performed from the side.", + "description_zh": "从侧面发动的无法防御的横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Jump over it and stay close to bait his overhead slash, or jump back for a jump kick to the head.", + "counter_zh": "跳过去并留在他身边,引诱他出下劈;或者向后跳,踩头反击。", "common_mistakes": null, - "name_zh": "危·下段横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "large-overhead-slash", "name": "Large Overhead Slash", + "name_zh": "大幅度下劈", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "overhead", "description": "A very powerful overhead slash that adapts based on the player's response.", + "description_zh": "威力极大的下劈,会根据玩家的反应改变出招。", "telegraph": "Loads up for the overhead slash.", + "telegraph_zh": "蓄力准备下劈。", "counter": "Dodge at the last moment and counter, or step back out of range. Hits can land during the charge-up.", + "counter_zh": "在最后一刻闪避再反击,或者后退到范围外。他蓄力期间可以打中他。", "common_mistakes": "Dodging too early makes him switch to a quick horizontal attack.", - "name_zh": "大幅度下劈", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "闪避太早会让他改成一记快速横斩。" }, { "id": "backflip", "name": "Backflip", + "name_zh": "后空翻", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "movement", "description": "Leaps away; deals no vitality damage but significant posture damage.", + "description_zh": "向后跃开;不造成体力伤害,但会造成大量躯干伤害。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect to minimize posture damage.", + "counter_zh": "弹刀以尽量减少躯干伤害。", "common_mistakes": null, - "name_zh": "后空翻", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "firecrackers", "name": "Firecrackers", + "name_zh": "爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Throws blinding firecrackers followed by an explosion; can chain into Shuriken Throw, Shadowfall, or Shuriken Slash.", + "description_zh": "扔出致盲的爆竹并引发爆炸;之后可能接投掷手里剑、秘传·巨型忍者落杀或手里剑 + 斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Dodge backwards to avoid.", + "counter_zh": "向后闪避躲开。", "common_mistakes": "Dodging too quickly after the throw lets his follow-up slash hit you.", - "name_zh": "爆竹", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "他扔出爆竹后闪避得太快,会被他接下来的斩击命中。" }, { "id": "running-slash", "name": "Running Slash", + "name_zh": "冲刺斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A single strong slash performed while approaching the player.", + "description_zh": "接近玩家时发动的一记强力斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Block/deflect. Dodging to the left is also possible.", + "counter_zh": "防御或弹刀。也可以向左闪避。", "common_mistakes": null, - "name_zh": "冲刺斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "five-slash-firecracker-combo", "name": "5-Slash Firecracker Combo", + "name_zh": "五连斩接爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Three consecutive sword slashes, followed by an elbow strike, followed by firecrackers that he slashes through.", + "description_zh": "连续三次挥刀,接一记肘击,然后扔出爆竹并挥刀穿过。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect/block the slashes, then dodge right when the firecrackers are thrown for a counter opportunity.", + "counter_zh": "弹开或防御前几刀,在他扔出爆竹时向右闪避,寻找反击机会。", "common_mistakes": "Getting pushed back into a corner by the combo, where there is no chance of survival.", - "name_zh": "五连斩接爆竹", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "被这套连击逼进角落,那里几乎没有活路。" }, { "id": "double-slash-perilous-sweep", "name": "Double Slash into Perilous Sweep", + "name_zh": "二连斩接危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Two consecutive slashes ending in an unblockable sweep; can chain into Large Overhead Slash.", + "description_zh": "连续两次斩击,最后接一记无法防御的横扫;之后可能接大幅度下劈。", "telegraph": null, + "telegraph_zh": null, "counter": "Jump over it and stay close to bait his overhead slash.", + "counter_zh": "跳过去并留在他身边,引诱他出下劈。", "common_mistakes": null, - "name_zh": "二连斩接危·下段横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "stomp-deathblow", "name": "Stomp (Deathblow)", + "name_zh": "踩踏(忍杀)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Raises a foot then stomps down, pinning the player and stabbing through the chest for massive damage. Cannot be blocked and gives no Perilous indicator.", + "description_zh": "抬起一只脚然后踩下,把玩家踩住并刺穿胸口,造成大量伤害。无法防御,也没有“危”字提示。", "telegraph": "Raises his foot into the air before stomping down. There is no Perilous indicator.", + "telegraph_zh": "踩下前会把脚高高抬起。没有“危”字提示。", "counter": "Dodge backwards or safety roll to avoid it.", + "counter_zh": "向后闪避或受身翻滚躲开。", "common_mistakes": null, - "name_zh": "踩踏(忍杀)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "backflip-shuriken", "name": "Backflip + Shuriken", + "name_zh": "后空翻 + 手里剑", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Same as the Phase 1 backflip, with a shuriken thrown afterward.", + "description_zh": "和第一阶段的后空翻相同,之后再扔出一枚手里剑。", "telegraph": null, + "telegraph_zh": null, "counter": "Block/deflect both.", + "counter_zh": "两下都防御或弹开。", "common_mistakes": null, - "name_zh": "后空翻 + 手里剑", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "owl-teleport", "name": "Owl Teleport", + "name_zh": "枭之瞬移", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "teleport", "description": "Uses his summoned owl to teleport above the player and drop down with a slash.", + "description_zh": "借助召唤出的枭瞬移到玩家上方,落下时挥刀斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect the slash, or run/stay away from the owl to avoid it.", + "counter_zh": "弹开这一斩,或者跑开、远离那只枭来躲开。", "common_mistakes": null, - "name_zh": "枭之瞬移", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "fire-owl-shadowfall", "name": "Fire Owl + Shadowfall", + "name_zh": "火枭 + 巨型忍者落杀", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Summons his owl and throws an unblockable fire owl projectile, then follows up with Shadowfall.", + "description_zh": "召唤出枭并扔出一只无法防御的火枭,然后接秘传·巨型忍者落杀。", "telegraph": null, + "telegraph_zh": null, "counter": "Jump over the fire owl and Mikiri the thrust.", + "counter_zh": "跳过火枭,然后识破那记突刺。", "common_mistakes": null, - "name_zh": "火枭 + 巨型忍者落杀", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] } @@ -825,186 +1088,245 @@ "name_zh": "蝴蝶夫人", "game": "sekiro", "location": "Hirata Estate - Hidden Temple", + "location_zh": "平田宅邸 - 佛殿", "source_name": "Fextralife Sekiro Wiki", "source_url": "https://sekiroshadowsdietwice.wiki.fextralife.com/Lady+Butterfly", "phases": [ { "phase_number": 1, "name": "Phase 1", + "name_zh": "第一阶段", "moves": [ { "id": "kunai-throw", "name": "Kunai Throw", + "name_zh": "投掷苦无", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Projectile attack using kunai.", + "description_zh": "投掷苦无的远程攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Can be blocked or deflected.", + "counter_zh": "可以防御或弹刀。", "common_mistakes": null, - "name_zh": "投掷苦无", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "jump-into-rafters", "name": "Jump into the Rafters", + "name_zh": "跃上房梁", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "movement", "description": "Leaps into the air and disappears.", + "description_zh": "跃到空中后消失。", "telegraph": null, + "telegraph_zh": null, "counter": "Use the Shuriken Prosthetic immediately as she begins jumping to knock her out of the air and create an opening.", + "counter_zh": "她一开始起跳就立刻使用手里剑,把她打下来并制造破绽。", "common_mistakes": "Throwing the shuriken too late - she will likely deflect it.", - "name_zh": "跃上房梁", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "手里剑扔得太晚——她很可能会弹开。" }, { "id": "perilous-aerial-grab", "name": "Perilous Attack - Above (Aerial Grab)", + "name_zh": "危·空中投技", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Overhead perilous attack performed while airborne.", + "description_zh": "在空中发动的自上而下的危攻击。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "Dodge or move out of the way. Alternatively, use the Shuriken Prosthetic to knock her down for a punish window.", + "counter_zh": "闪避或移动躲开。也可以用手里剑把她打下来,获得反击时机。", "common_mistakes": null, - "name_zh": "危·空中投技", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-sweep", "name": "Perilous Attack - Sweep", + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Low-sweeping perilous attack.", + "description_zh": "贴地横扫的危攻击。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "Jump and kick her for considerable posture damage.", + "counter_zh": "跳起踩她,能造成可观的躯干伤害。", "common_mistakes": null, - "name_zh": "危·下段横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "ground-combos", "name": "Ground Combos", + "name_zh": "地面连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Standard melee attack sequences performed from ground level.", + "description_zh": "在地面上发动的常规近战连击。", "telegraph": null, + "telegraph_zh": null, "counter": "Stay close, deflect attacks, then counter-attack during her recovery windows.", + "counter_zh": "贴近她,弹开攻击,然后在她的收招空档反击。", "common_mistakes": "Attacking too much. Deflect more than you attack, and only strike after openings such as her perilous attacks.", - "name_zh": "地面连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "攻击太多。弹刀要多于攻击,只在出现破绽后出手,比如她的危攻击之后。" } ] }, { "phase_number": 2, "name": "Phase 2", + "name_zh": "第二阶段", "moves": [ { "id": "kunai-throw", "name": "Kunai Throw", + "name_zh": "投掷苦无", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Projectile attack using kunai.", + "description_zh": "投掷苦无的远程攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Can be blocked or deflected.", + "counter_zh": "可以防御或弹刀。", "common_mistakes": null, - "name_zh": "投掷苦无", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "jump-into-rafters", "name": "Jump into the Rafters", + "name_zh": "跃上房梁", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "movement", "description": "Leaps into the air and disappears.", + "description_zh": "跃到空中后消失。", "telegraph": null, + "telegraph_zh": null, "counter": "Use the Shuriken Prosthetic immediately as she begins jumping to knock her out of the air and create an opening.", + "counter_zh": "她一开始起跳就立刻使用手里剑,把她打下来并制造破绽。", "common_mistakes": "Throwing the shuriken too late - she will likely deflect it.", - "name_zh": "跃上房梁", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "手里剑扔得太晚——她很可能会弹开。" }, { "id": "perilous-aerial-grab", "name": "Perilous Attack - Above (Aerial Grab)", + "name_zh": "危·空中投技", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Overhead perilous attack performed while airborne.", + "description_zh": "在空中发动的自上而下的危攻击。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "Dodge or move out of the way. Alternatively, use the Shuriken Prosthetic to knock her down for a punish window.", + "counter_zh": "闪避或移动躲开。也可以用手里剑把她打下来,获得反击时机。", "common_mistakes": null, - "name_zh": "危·空中投技", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "perilous-sweep", "name": "Perilous Attack - Sweep", + "name_zh": "危·下段横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Low-sweeping perilous attack.", + "description_zh": "贴地横扫的危攻击。", "telegraph": "Perilous Attack kanji.", + "telegraph_zh": "“危”字提示。", "counter": "Jump and kick her for considerable posture damage.", + "counter_zh": "跳起踩她,能造成可观的躯干伤害。", "common_mistakes": null, - "name_zh": "危·下段横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "ground-combos", "name": "Ground Combos", + "name_zh": "地面连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Standard melee attack sequences performed from ground level.", + "description_zh": "在地面上发动的常规近战连击。", "telegraph": null, + "telegraph_zh": null, "counter": "Stay close, deflect attacks, then counter-attack during her recovery windows.", + "counter_zh": "贴近她,弹开攻击,然后在她的收招空档反击。", "common_mistakes": "Attacking too much. Deflect more than you attack, and only strike after openings such as her perilous attacks.", - "name_zh": "地面连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "攻击太多。弹刀要多于攻击,只在出现破绽后出手,比如她的危攻击之后。" }, { "id": "illusions", "name": "Illusions", + "name_zh": "幻影", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "summon", "description": "Summons phantom enemies that attack with spears.", + "description_zh": "召唤手持长枪攻击的幻影敌人。", "telegraph": null, + "telegraph_zh": null, "counter": "Use a Snap Seed to dispel the phantoms, or kill them individually for Spirit Emblems. Alternatively, run around the arena perimeter to avoid their attacks.", + "counter_zh": "使用拍手种子驱散幻影,或者逐个击杀它们来获得纸人。也可以沿着场地边缘跑动来躲开它们的攻击。", "common_mistakes": null, - "name_zh": "幻影", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "phantom-kunai", "name": "Phantom Kunai", + "name_zh": "幻影苦无", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Upgraded kunai projectile attack with additional volleys.", + "description_zh": "强化版的苦无远程攻击,会多扔几轮。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect or block; dodging sideways offers the best protection.", + "counter_zh": "弹刀或防御;向侧面闪避最稳妥。", "common_mistakes": null, - "name_zh": "幻影苦无", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "homing-butterflies", "name": "Homing Butterflies", + "name_zh": "追踪幻蝶", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Homing projectiles created when illusions are dispelled.", + "description_zh": "驱散幻影时产生的追踪弹。", "telegraph": null, + "telegraph_zh": null, "counter": "Hide behind pillars or keep sprinting around the arena perimeter.", + "counter_zh": "躲到柱子后面,或者沿着场地边缘持续奔跑。", "common_mistakes": "Turning corners while the butterflies are tracking you can make them hit you.", - "name_zh": "追踪幻蝶", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "在幻蝶追踪你时转弯,可能会被它们打中。" }, { "id": "aerial-attacks-enhanced", "name": "Aerial Attacks (Enhanced)", + "name_zh": "空中攻击(强化)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Enhanced aerial maneuvers performed with increased frequency.", + "description_zh": "强化的空中动作,使用频率更高。", "telegraph": null, + "telegraph_zh": null, "counter": "Continue using the Shuriken Prosthetic to interrupt; maintain aggressive pressure to prevent aerial transitions.", + "counter_zh": "继续用手里剑打断;保持压制,不给她跃到空中的机会。", "common_mistakes": null, - "name_zh": "空中攻击(强化)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] } @@ -1016,150 +1338,197 @@ "name_zh": "狮子猿", "game": "sekiro", "location": "Sunken Valley Passage", + "location_zh": "坠落谷 - 通道", "source_name": "Fextralife Sekiro Wiki", "source_url": "https://sekiroshadowsdietwice.wiki.fextralife.com/Guardian+Ape", "phases": [ { "phase_number": 1, "name": "Phase 1", + "name_zh": "第一阶段", "moves": [ { "id": "rapid-slamming", "name": "Rapid Slamming", + "name_zh": "连续拍地", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Rapid slamming combinations performed at close range.", + "description_zh": "近距离的快速拍地连击。", "telegraph": null, + "telegraph_zh": null, "counter": "All slamming attacks can be blocked; attempt to deflect them.", + "counter_zh": "所有拍地攻击都可以防御;尽量弹刀。", "common_mistakes": null, - "name_zh": "连续拍地", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "hand-swipe-grab", "name": "Hand Swipe Grab", + "name_zh": "挥手抓取", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Unblockable grab attack where the ape swipes the floor from left to right at close range.", + "description_zh": "无法防御的抓取攻击:狮子猿在近距离从左到右扫过地面。", "telegraph": null, + "telegraph_zh": null, "counter": "Time a jump to dodge it.", + "counter_zh": "看准时机跳起躲开。", "common_mistakes": null, - "name_zh": "挥手抓取", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "body-slam-grab", "name": "Body Slam Grab", + "name_zh": "扑身抓取", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "The ape jumps and attempts to grab the player.", + "description_zh": "狮子猿跳起,试图抓住玩家。", "telegraph": null, + "telegraph_zh": null, "counter": "Time a dodge to avoid it; a double dodge (tap the button twice) is safer.", + "counter_zh": "看准时机闪避;连续闪避两次(快速按两下)更安全。", "common_mistakes": null, - "name_zh": "扑身抓取", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "poisonous-fart", "name": "Poisonous Fart", + "name_zh": "毒屁", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "status", "description": "Status attack inflicting heavy poison buildup when the ape's back is turned; always followed by running away and jumping high.", + "description_zh": "狮子猿背对玩家时放出的状态攻击,会大量累积中毒;之后一定会跑开并高高跃起。", "telegraph": null, + "telegraph_zh": null, "counter": "Quickly dodge away from the cloud to avoid the poison.", + "counter_zh": "迅速闪避离开毒雾,避免中毒。", "common_mistakes": null, - "name_zh": "毒屁", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "poisonous-poo-throw", "name": "Poisonous Poo Throw", + "name_zh": "投掷毒粪", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "High-damage instant poison attack.", + "description_zh": "高伤害、立即中毒的攻击。", "telegraph": "Jumps high before throwing.", + "telegraph_zh": "投掷前会高高跃起。", "counter": "Run underneath as it jumps and retaliate; its recovery from this attack is lengthy.", + "counter_zh": "在它跃起时跑到它身下再反击;这招的收招硬直很长。", "common_mistakes": null, - "name_zh": "投掷毒粪", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "roar-jump-back", "name": "Roar / Jump Back", + "name_zh": "咆哮 / 后跳", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "movement", "description": "The ape jumps back and unleashes a roar.", + "description_zh": "狮子猿向后跳开并发出咆哮。", "telegraph": null, + "telegraph_zh": null, "counter": "A grapple prompt appears, letting Sekiro close the gap and causing a brief stagger.", + "counter_zh": "会出现钩绳提示,可以借此拉近距离,并让它短暂硬直。", "common_mistakes": null, - "name_zh": "咆哮 / 后跳", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "rolling-body-slam", "name": "Body Slam (Rolling)", + "name_zh": "翻滚压击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "The ape jumps onto its back and thrashes the ground.", + "description_zh": "狮子猿仰面倒地,在地上翻滚拍打。", "telegraph": null, + "telegraph_zh": null, "counter": "Stay away from its arms and hit its head.", + "counter_zh": "远离它的手臂,攻击它的头部。", "common_mistakes": null, - "name_zh": "翻滚压击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] }, { "phase_number": 2, "name": "Phase 2", + "name_zh": "第二阶段", "moves": [ { "id": "wide-sword-swipes", "name": "Sword Swipes (Wide Area)", + "name_zh": "大范围挥刀", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Sword attacks that cover a wide area, after the ape decapitates itself and resurrects wielding a sword through its neck.", + "description_zh": "狮子猿被斩首后复活,把刀插在脖子里挥舞,攻击范围很大。", "telegraph": null, + "telegraph_zh": null, "counter": "Strike a few times after its combos, then back away.", + "counter_zh": "在它的连击后攻击几下,然后退开。", "common_mistakes": "Staying on top of it and attacking for too long.", - "name_zh": "大范围挥刀", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "一直贴着它攻击太久。" }, { "id": "diving-lunge", "name": "Diving Lunge", + "name_zh": "飞扑", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "Lunges forward with a swipe while falling to the ground.", + "description_zh": "向前扑出并挥爪,同时扑倒在地。", "telegraph": "Clear wind-up before it dives.", + "telegraph_zh": "扑出前有明显的蓄力动作。", "counter": "Jump over it as it dives and strike it in the air, following with a full combo.", + "counter_zh": "在它扑出时跳过去并在空中攻击,然后接一整套连击。", "common_mistakes": null, - "name_zh": "飞扑", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "overhead-slam", "name": "Overhead Slam", + "name_zh": "高举砸地", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "overhead", "description": "Large overhead attack that can be deflected.", + "description_zh": "大幅度的高举砸地,可以弹刀。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflecting it stuns the boss and exposes its neck. Using the Loaded Spear (activate it twice) briefly pulls out the centipede for massive posture and health damage.", + "counter_zh": "弹开这招会让它硬直并露出脖子。使用机关长枪(发动两次)可以短暂拔出蜈蚣,造成大量躯干和体力伤害。", "common_mistakes": null, - "name_zh": "高举砸地", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "blood-scream", "name": "Blood Scream", + "name_zh": "血之咆哮", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "terror", "description": "Battle roar that unleashes shockwaves filling the Terror meter, which can inflict Terror status and instantly kill Sekiro.", + "description_zh": "战吼,释放的冲击波会累积怖,怖满后会立即杀死狼。", "telegraph": "Puts its head back on its neck before screaming.", + "telegraph_zh": "咆哮前会把头放回脖子上。", "counter": "Any Loaded Umbrella-type prosthetic completely negates the scream; the Phoenix's Lilac Umbrella is recommended for NG+.", + "counter_zh": "任何机关伞类义手忍具都能完全抵挡这声咆哮;二周目以后推荐使用凤凰紫伞。", "common_mistakes": null, - "name_zh": "血之咆哮", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] } @@ -1171,96 +1540,126 @@ "name_zh": "破戒僧(幻影)", "game": "sekiro", "location": "Ashina Depths - Mibu Village", + "location_zh": "苇名之底 - 水生村", "source_name": "Fextralife Sekiro Wiki", "source_url": "https://sekiroshadowsdietwice.wiki.fextralife.com/Corrupted+Monk", "phases": [ { "phase_number": 1, "name": "Phase 1", + "name_zh": "第一阶段", "moves": [ { "id": "sweep-attack", "name": "Sweep Attack", + "name_zh": "横扫攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Perilous sweep attack variant, often followed by attack opportunities.", + "description_zh": "横扫类的危攻击,之后通常有攻击机会。", "telegraph": null, + "telegraph_zh": null, "counter": "Jump over it.", + "counter_zh": "跳过去。", "common_mistakes": null, - "name_zh": "横扫攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "thrust-attack", "name": "Thrust Attack", + "name_zh": "突刺攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "Perilous thrust attack variant.", + "description_zh": "突刺类的危攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Mikiri Counter.", + "counter_zh": "识破。", "common_mistakes": null, - "name_zh": "突刺攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "jump-attack-sweep", "name": "Jump Attack (Sweep Variant)", + "name_zh": "跳跃攻击(横扫)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Jumping attack that sweeps during the jump.", + "description_zh": "跳跃攻击,在跳跃过程中横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Sidestep and strike as she lands.", + "counter_zh": "向侧面闪避,在她落地时出手攻击。", "common_mistakes": null, - "name_zh": "跳跃攻击(横扫)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "jump-attack-overhead", "name": "Jump Attack (Overhead Variant)", + "name_zh": "跳跃攻击(下劈)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "overhead", "description": "Jumping attack that ends in an overhead slash.", + "description_zh": "跳跃攻击,最后以一记下劈收尾。", "telegraph": null, + "telegraph_zh": null, "counter": "Run below and attack her exposed back.", + "counter_zh": "跑到她身下,攻击她露出的背后。", "common_mistakes": null, - "name_zh": "跳跃攻击(下劈)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "upwards-slash", "name": "Upwards Slash", + "name_zh": "上挑斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "An upward slash that goes through Sekiro's normal defenses and cannot be blocked.", + "description_zh": "一记上挑斩,会穿透狼的常规防御,无法防御。", "telegraph": null, + "telegraph_zh": null, "counter": "Can be deflected, though the timing is very precise.", + "counter_zh": "可以弹刀,但时机要求非常精确。", "common_mistakes": null, - "name_zh": "上挑斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "whirlwind-attack", "name": "Whirlwind Attack", + "name_zh": "旋风攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Continuous spinning attack with the Naginata that can quickly exhaust Sekiro's posture gauge. Tends to be used when at a distance from the player, more often if the player uses a consumable.", + "description_zh": "挥舞薙刀持续旋转攻击,能迅速打满狼的躯干。通常在与玩家有一定距离时使用,玩家使用道具时更频繁。", "telegraph": null, + "telegraph_zh": null, "counter": "Run away when she starts spinning.", + "counter_zh": "她开始旋转时就跑开。", "common_mistakes": null, - "name_zh": "旋风攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "mid-range-combo", "name": "Mid-Range Combo", + "name_zh": "中距离连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "One jumping air hit followed by two ground hits at mid-range.", + "description_zh": "先跳起在空中攻击一次,接着在中距离落地连续攻击两次。", "telegraph": null, + "telegraph_zh": null, "counter": "Let the landing cancel the air-attack lag, then jump away to mid-range.", + "counter_zh": "利用落地抵消空中攻击的硬直,然后跳开拉到中距离。", "common_mistakes": null, - "name_zh": "中距离连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] } @@ -1272,216 +1671,284 @@ "name_zh": "宫内破戒僧", "game": "sekiro", "location": "Fountainhead Palace", + "location_zh": "源之宫", "source_name": "Fextralife Sekiro Wiki", "source_url": "https://sekiroshadowsdietwice.wiki.fextralife.com/Corrupted+Monk", "phases": [ { "phase_number": 1, "name": "Phase 1", + "name_zh": "第一阶段", "moves": [ { "id": "sweep-attack", "name": "Sweep Attack", + "name_zh": "横扫攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Perilous sweep attack variant, used more often here than in the Mibu Village fight. More telegraphed and predictable in Phase 3.", + "description_zh": "横扫类的危攻击,这里比水生村那一战用得更频繁。第三阶段的预兆更明显,也更好预判。", "telegraph": null, + "telegraph_zh": null, "counter": "Jump over it.", + "counter_zh": "跳过去。", "common_mistakes": null, - "name_zh": "横扫攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "thrust-attack", "name": "Thrust Attack", + "name_zh": "突刺攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "Perilous thrust attack variant, used more often here than in the Mibu Village fight.", + "description_zh": "突刺类的危攻击,这里比水生村那一战用得更频繁。", "telegraph": null, + "telegraph_zh": null, "counter": "Mikiri Counter.", + "counter_zh": "识破。", "common_mistakes": null, - "name_zh": "突刺攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "jump-attack-sweep", "name": "Jump Attack (Sweep Variant)", + "name_zh": "跳跃攻击(横扫)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Jumping attack that sweeps during the jump.", + "description_zh": "跳跃攻击,在跳跃过程中横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Sidestep and strike as she lands.", + "counter_zh": "向侧面闪避,在她落地时出手攻击。", "common_mistakes": null, - "name_zh": "跳跃攻击(横扫)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "jump-attack-overhead", "name": "Jump Attack (Overhead Variant)", + "name_zh": "跳跃攻击(下劈)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "overhead", "description": "Jumping attack that ends in an overhead slash. In Phase 3 it tends to be used as a finishing move.", + "description_zh": "跳跃攻击,最后以一记下劈收尾。第三阶段她常用这招收尾。", "telegraph": null, + "telegraph_zh": null, "counter": "Run below and attack her exposed back.", + "counter_zh": "跑到她身下,攻击她露出的背后。", "common_mistakes": null, - "name_zh": "跳跃攻击(下劈)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "upwards-slash", "name": "Upwards Slash", + "name_zh": "上挑斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "An upward slash that goes through Sekiro's normal defenses and cannot be blocked.", + "description_zh": "一记上挑斩,会穿透狼的常规防御,无法防御。", "telegraph": null, + "telegraph_zh": null, "counter": "Can be deflected, though the timing is very precise.", + "counter_zh": "可以弹刀,但时机要求非常精确。", "common_mistakes": null, - "name_zh": "上挑斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "mid-range-combo", "name": "Mid-Range Combo", + "name_zh": "中距离连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "One jumping air hit followed by two ground hits at mid-range.", + "description_zh": "先跳起在空中攻击一次,接着在中距离落地连续攻击两次。", "telegraph": null, + "telegraph_zh": null, "counter": "Let the landing cancel the air-attack lag, then jump away to mid-range.", + "counter_zh": "利用落地抵消空中攻击的硬直,然后跳开拉到中距离。", "common_mistakes": null, - "name_zh": "中距离连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] }, { "phase_number": 2, "name": "Phase 2", + "name_zh": "第二阶段", "moves": [ { "id": "illusion-summoning", "name": "Illusion Summoning", + "name_zh": "召唤幻影", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "summon", "description": "She disappears and the area fills with mist, then summons three shadow illusions of herself that each attack the player three times. The shadows disappear between attacks, making lock-on difficult. Standing near a wall before the shadows spawn limits them to one direction, repeating the same three moves.", + "description_zh": "她消失后场地充满雾气,接着召唤三个自己的影子幻影,每个都会攻击玩家三次。影子在攻击间隙会消失,很难锁定。影子出现前站在墙边,可以让它们只从一个方向出现,重复同样的三招。", "telegraph": null, + "telegraph_zh": null, "counter": "The shadows don't need to be defeated - she eventually ends the illusion and returns on her own. A Bite Down pill instantly dispels them too.", + "counter_zh": "不需要击败影子——她最终会自己结束幻影并回来。咬破药丸也能立即驱散它们。", "common_mistakes": null, - "name_zh": "召唤幻影", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] }, { "phase_number": 3, "name": "Phase 3", + "name_zh": "第三阶段", "moves": [ { "id": "sweep-attack", "name": "Sweep Attack", + "name_zh": "横扫攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Perilous sweep attack variant, used more often here than in the Mibu Village fight. More telegraphed and predictable in Phase 3.", + "description_zh": "横扫类的危攻击,这里比水生村那一战用得更频繁。第三阶段的预兆更明显,也更好预判。", "telegraph": null, + "telegraph_zh": null, "counter": "Jump over it.", + "counter_zh": "跳过去。", "common_mistakes": null, - "name_zh": "横扫攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "thrust-attack", "name": "Thrust Attack", + "name_zh": "突刺攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "Perilous thrust attack variant, used more often here than in the Mibu Village fight.", + "description_zh": "突刺类的危攻击,这里比水生村那一战用得更频繁。", "telegraph": null, + "telegraph_zh": null, "counter": "Mikiri Counter.", + "counter_zh": "识破。", "common_mistakes": null, - "name_zh": "突刺攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "jump-attack-sweep", "name": "Jump Attack (Sweep Variant)", + "name_zh": "跳跃攻击(横扫)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Jumping attack that sweeps during the jump.", + "description_zh": "跳跃攻击,在跳跃过程中横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Sidestep and strike as she lands.", + "counter_zh": "向侧面闪避,在她落地时出手攻击。", "common_mistakes": null, - "name_zh": "跳跃攻击(横扫)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "jump-attack-overhead", "name": "Jump Attack (Overhead Variant)", + "name_zh": "跳跃攻击(下劈)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "overhead", "description": "Jumping attack that ends in an overhead slash. In Phase 3 it tends to be used as a finishing move.", + "description_zh": "跳跃攻击,最后以一记下劈收尾。第三阶段她常用这招收尾。", "telegraph": null, + "telegraph_zh": null, "counter": "Run below and attack her exposed back.", + "counter_zh": "跑到她身下,攻击她露出的背后。", "common_mistakes": null, - "name_zh": "跳跃攻击(下劈)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "upwards-slash", "name": "Upwards Slash", + "name_zh": "上挑斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "An upward slash that goes through Sekiro's normal defenses and cannot be blocked.", + "description_zh": "一记上挑斩,会穿透狼的常规防御,无法防御。", "telegraph": null, + "telegraph_zh": null, "counter": "Can be deflected, though the timing is very precise.", + "counter_zh": "可以弹刀,但时机要求非常精确。", "common_mistakes": null, - "name_zh": "上挑斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "mid-range-combo", "name": "Mid-Range Combo", + "name_zh": "中距离连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "One jumping air hit followed by two ground hits at mid-range.", + "description_zh": "先跳起在空中攻击一次,接着在中距离落地连续攻击两次。", "telegraph": null, + "telegraph_zh": null, "counter": "Let the landing cancel the air-attack lag, then jump away to mid-range.", + "counter_zh": "利用落地抵消空中攻击的硬直,然后跳开拉到中距离。", "common_mistakes": null, - "name_zh": "中距离连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "whirlwind-attack", "name": "Whirlwind Attack", + "name_zh": "旋风攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Continuous spinning attack with the Naginata, regained in this phase after being absent since Phase 1. Very susceptible to the Shinobi Firecracker, especially during the windup.", + "description_zh": "挥舞薙刀持续旋转攻击,第一阶段之后消失,在这个阶段重新出现。非常怕爆竹,尤其是在起手时。", "telegraph": null, + "telegraph_zh": null, "counter": "Run away when she starts spinning, or interrupt the windup with a Shinobi Firecracker.", + "counter_zh": "她开始旋转时就跑开,或者在她起手时用爆竹打断。", "common_mistakes": null, - "name_zh": "旋风攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "centipede-toxic-goo", "name": "Centipede Toxic Goo", + "name_zh": "百足毒液", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Perilous attack where the demon centipede erupting from her neck spews toxic goo in front of her; can inflict Terror.", + "description_zh": "危攻击:从她脖子里钻出的蜈蚣向前方喷出毒液;可能造成怖。", "telegraph": null, + "telegraph_zh": null, "counter": "Sidestep or jump-kick when she uses this and retaliate - the attack has lengthy ending lag.", + "counter_zh": "她使用这招时向侧面闪避或跳起踩她,然后反击——这招收招硬直很长。", "common_mistakes": null, - "name_zh": "百足毒液", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "centipede-toxic-cloud", "name": "Centipede Toxic Cloud", + "name_zh": "百足毒雾", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "status", "description": "A non-perilous variant of the centipede's toxin attack.", + "description_zh": "蜈蚣毒液攻击的非危攻击版本。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "百足毒雾", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] } @@ -1493,210 +1960,277 @@ "name_zh": "巨型忍者 枭", "game": "sekiro", "location": "Ashina Castle", + "location_zh": "苇名城", "source_name": "Fextralife Sekiro Wiki", "source_url": "https://sekiroshadowsdietwice.wiki.fextralife.com/Great+Shinobi+Owl", "phases": [ { "phase_number": 1, "name": "Phase 1", + "name_zh": "第一阶段", "moves": [ { "id": "shinobi-charm", "name": "Shinobi Charm", + "name_zh": "忍符", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "status", "description": "Tosses a charm that releases a green mist, preventing healing item usage for 30 seconds.", + "description_zh": "扔出一张会释放绿色烟雾的符咒,30 秒内无法使用回复道具。", "telegraph": null, + "telegraph_zh": null, "counter": "Sidestep and then strike, taking care not to get debuffed.", + "counter_zh": "向侧面闪避后再攻击,注意不要被施加负面效果。", "common_mistakes": "Walking into the mist, which locks you out of healing items.", - "name_zh": "忍符", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "走进烟雾里,导致无法使用回复道具。" }, { "id": "backflip", "name": "Backflip", + "name_zh": "后空翻", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Leaps backward while throwing a shuriken.", + "description_zh": "向后跃开,同时投掷一枚手里剑。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect or dodge the projectile.", + "counter_zh": "弹开或闪避手里剑。", "common_mistakes": null, - "name_zh": "后空翻", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "running-slash", "name": "Running Slash", + "name_zh": "冲刺斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A single powerful slash performed while charging toward the player.", + "description_zh": "冲向玩家时发动的一记强力斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect or dodge depending on posture status.", + "counter_zh": "根据自己的躯干状况选择弹刀或闪避。", "common_mistakes": null, - "name_zh": "冲刺斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "four-slash-combo", "name": "4-Slash Combo", + "name_zh": "四连斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Four consecutive sword strikes, often followed by Shinobi Charm.", + "description_zh": "连续挥刀四次,之后常接忍符。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect or retreat.", + "counter_zh": "弹刀或后撤。", "common_mistakes": null, - "name_zh": "四连斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "shuriken-throw-flip", "name": "Shuriken Throw & Flip", + "name_zh": "投掷手里剑 + 翻身", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Two shuriken throws followed by an airborne downward strike.", + "description_zh": "投掷两枚手里剑,然后从空中向下攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Dodge diagonally forward through the flip strike to open up an opportunity to attack 3-4 times, or land two Mortal Draw hits.", + "counter_zh": "向斜前方闪避穿过空翻斩,就能获得攻击 3–4 次的机会,或者打出两次秘传·不死斩。", "common_mistakes": null, - "name_zh": "投掷手里剑 + 翻身", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "shuriken-slash", "name": "Shuriken Slash", + "name_zh": "手里剑 + 斩击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A quick shuriken throw immediately followed by a running slash.", + "description_zh": "快速投掷一枚手里剑,紧接着冲刺斩。", "telegraph": null, + "telegraph_zh": null, "counter": "Sidestep the projectile, then deflect or retreat.", + "counter_zh": "向侧面躲开手里剑,然后弹刀或后撤。", "common_mistakes": null, - "name_zh": "手里剑 + 斩击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "firecracker", "name": "Firecracker", + "name_zh": "爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Black gunpowder explosion attack dealing significant damage.", + "description_zh": "黑色火药爆炸攻击,伤害很高。", "telegraph": "Black smoke.", + "telegraph_zh": "黑烟。", "counter": "Run away when you see black smoke, or dodge in aggressively.", + "counter_zh": "看到黑烟就跑开,或者主动闪避贴近。", "common_mistakes": null, - "name_zh": "爆竹", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "stomp-grab", "name": "Stomp (Grab)", + "name_zh": "踩踏(投技)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Raises a foot then stomps down, pinning and stabbing the player. Deals massive damage, cannot be blocked, and gives no Perilous indicator.", + "description_zh": "抬起一只脚然后踩下,把玩家踩住并刺下。伤害极高,无法防御,也没有“危”字提示。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "踩踏(投技)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] }, { "phase_number": 2, "name": "Phase 2", + "name_zh": "第二阶段", "moves": [ { "id": "shinobi-charm", "name": "Shinobi Charm", + "name_zh": "忍符", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "status", "description": "Tosses a charm that releases a green mist, preventing healing item usage for 30 seconds.", + "description_zh": "扔出一张会释放绿色烟雾的符咒,30 秒内无法使用回复道具。", "telegraph": null, + "telegraph_zh": null, "counter": "Sidestep and then strike, taking care not to get debuffed.", + "counter_zh": "向侧面闪避后再攻击,注意不要被施加负面效果。", "common_mistakes": "Walking into the mist, which locks you out of healing items.", - "name_zh": "忍符", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "走进烟雾里,导致无法使用回复道具。" }, { "id": "backflip-poison", "name": "Backflip (Poison)", + "name_zh": "后空翻(毒)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Same motion as the Phase 1 backflip, but throws poison instead of a shuriken.", + "description_zh": "和第一阶段的后空翻动作相同,但扔出的是毒而不是手里剑。", "telegraph": null, + "telegraph_zh": null, "counter": "Dodge and maintain distance from the poisoned area.", + "counter_zh": "闪避并远离中毒区域。", "common_mistakes": null, - "name_zh": "后空翻(毒)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "running-slash", "name": "Running Slash", + "name_zh": "冲刺斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A single powerful slash performed while charging toward the player.", + "description_zh": "冲向玩家时发动的一记强力斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect or dodge depending on posture status.", + "counter_zh": "根据自己的躯干状况选择弹刀或闪避。", "common_mistakes": null, - "name_zh": "冲刺斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "four-slash-combo", "name": "4-Slash Combo", + "name_zh": "四连斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Four consecutive sword strikes, often followed by Shinobi Charm.", + "description_zh": "连续挥刀四次,之后常接忍符。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect or retreat.", + "counter_zh": "弹刀或后撤。", "common_mistakes": null, - "name_zh": "四连斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "shuriken-throw-flip", "name": "Shuriken Throw & Flip", + "name_zh": "投掷手里剑 + 翻身", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "Two shuriken throws followed by an airborne downward strike.", + "description_zh": "投掷两枚手里剑,然后从空中向下攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Dodge diagonally forward through the flip strike to open up an opportunity to attack 3-4 times, or land two Mortal Draw hits.", + "counter_zh": "向斜前方闪避穿过空翻斩,就能获得攻击 3–4 次的机会,或者打出两次秘传·不死斩。", "common_mistakes": null, - "name_zh": "投掷手里剑 + 翻身", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "shuriken-slash", "name": "Shuriken Slash", + "name_zh": "手里剑 + 斩击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A quick shuriken throw immediately followed by a running slash.", + "description_zh": "快速投掷一枚手里剑,紧接着冲刺斩。", "telegraph": null, + "telegraph_zh": null, "counter": "Sidestep the projectile, then deflect or retreat.", + "counter_zh": "向侧面躲开手里剑,然后弹刀或后撤。", "common_mistakes": null, - "name_zh": "手里剑 + 斩击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "firecracker", "name": "Firecracker", + "name_zh": "爆竹", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Black gunpowder explosion attack dealing significant damage.", + "description_zh": "黑色火药爆炸攻击,伤害很高。", "telegraph": "Black smoke.", + "telegraph_zh": "黑烟。", "counter": "Run away when you see black smoke, or dodge in aggressively.", + "counter_zh": "看到黑烟就跑开,或者主动闪避贴近。", "common_mistakes": null, - "name_zh": "爆竹", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "stomp-grab", "name": "Stomp (Grab)", + "name_zh": "踩踏(投技)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Raises a foot then stomps down, pinning and stabbing the player. Deals massive damage, cannot be blocked, and gives no Perilous indicator.", + "description_zh": "抬起一只脚然后踩下,把玩家踩住并刺下。伤害极高,无法防御,也没有“危”字提示。", "telegraph": null, + "telegraph_zh": null, "counter": null, + "counter_zh": null, "common_mistakes": null, - "name_zh": "踩踏(投技)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] } @@ -1708,528 +2242,700 @@ "name_zh": "剑圣 苇名一心", "game": "sekiro", "location": "Ashina Reservoir - Secret Passage", + "location_zh": "苇名城 贮水城区 - 秘密通道", "source_name": "Fextralife Sekiro Wiki", "source_url": "https://sekiroshadowsdietwice.wiki.fextralife.com/Isshin,+the+Sword+Saint", "phases": [ { "phase_number": 1, "name": "Phase 1", + "name_zh": "第一阶段", "moves": [ { "id": "dragon-flash", "name": "Dragon Flash", + "name_zh": "秘传·龙闪", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA", "move_type": "ranged", "description": "Strikes downward launching a force wave, followed by a second wave after a short delay.", + "description_zh": "向下斩击放出一道剑气,短暂延迟后再放出第二道。", "telegraph": "Sheathes his sword and readies a stance; a glint shows on the hilt.", + "telegraph_zh": "收刀入鞘摆出架势,刀柄上闪过一道光。", "counter": "Deflect the first wave, then dodge the second.", + "counter_zh": "弹开第一道剑气,然后闪避第二道。", "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage.", - "name_zh": "秘传·龙闪", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA" + "common_mistakes_zh": "防御剑气而不是弹开——防御仍然会受到伤害。" }, { "id": "ashina-cross", "name": "Ashina Cross", + "name_zh": "绝技·苇名十字斩", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9", "move_type": "combo", "description": "Steps forward with two rapid slashes.", + "description_zh": "向前踏步,快速斩击两次。", "telegraph": "Sheathes his sword; after a short delay there is a flash of light at his hand as he steps forward.", + "telegraph_zh": "收刀入鞘;短暂延迟后,他向前踏步时手上会闪过一道光。", "counter": "Press deflect twice without pausing, or run far away to avoid it entirely.", + "counter_zh": "连续按两次弹刀,中间不要停顿;或者跑远完全躲开。", "common_mistakes": null, - "name_zh": "绝技·苇名十字斩", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9" + "common_mistakes_zh": null }, { "id": "ashina-cross-cancel", "name": "Ashina Cross Cancel", + "name_zh": "苇名十字斩(取消)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "If the player is close when he readies Ashina Cross, he cancels it into a quick sword smack followed by a perilous sweep. He also does this after the player repeatedly deflects his attacks.", + "description_zh": "如果他准备苇名十字斩时玩家离得很近,他会中断改为快速一击,接一记危·下段横扫。玩家连续弹开他的攻击后,他也会这样做。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect the initial hit, then jump and stomp.", + "counter_zh": "弹开第一击,然后跳起踩他。", "common_mistakes": null, - "name_zh": "苇名十字斩(取消)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "ichimonji-double", "name": "Ichimonji Double", + "name_zh": "一字斩·二连", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E", "move_type": "overhead", "description": "Performs a double overhead strike.", + "description_zh": "连续两次下劈。", "telegraph": "Raises his blade into a ready stance.", + "telegraph_zh": "举刀摆出架势。", "counter": "Dodge or block; the attack can also be sidestepped. Deflecting triggers a stagger.", + "counter_zh": "闪避或防御;也可以向侧面躲开。弹刀会让他硬直。", "common_mistakes": null, - "name_zh": "一字斩·二连", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E" + "common_mistakes_zh": null }, { "id": "distance-slash", "name": "Distance Slash", + "name_zh": "远距离斩击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "If the player is far away or using an item, he quickly dashes in and slashes.", + "description_zh": "如果玩家离得很远或正在使用道具,他会迅速冲过来斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Maintain enough distance to react in time.", + "counter_zh": "保持足够的距离,以便及时反应。", "common_mistakes": null, - "name_zh": "远距离斩击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "charged-horizontal-force-wave", "name": "Charged Attack, Horizontal Force Wave", + "name_zh": "蓄力横向剑气", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Swings in a spinning motion, causing a large circular force wave.", + "description_zh": "旋转挥刀,放出一道巨大的环形剑气。", "telegraph": "Sheathes his sword and charges the attack.", + "telegraph_zh": "收刀入鞘,蓄力。", "counter": "Wait, then deflect as soon as he begins moving his arm, or sprint and jump toward him, then jump again onto his head.", + "counter_zh": "等待,在他开始挥动手臂时立刻弹刀;或者冲刺跳向他,再踩他的头跳起。", "common_mistakes": null, - "name_zh": "蓄力横向剑气", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "three-to-four-hit-combo", "name": "Three-to-Four Hit Combo", + "name_zh": "三至四连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A series of main slashes with delays between hits.", + "description_zh": "一连串主要斩击,每击之间有停顿。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect each hit; once he finishes slashing he's open for a quick two hits.", + "counter_zh": "逐一弹开;他砍完后会露出破绽,可以快速攻击两下。", "common_mistakes": null, - "name_zh": "三至四连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "thrust", "name": "Thrust", + "name_zh": "突刺", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "A telegraphed thrust attack.", + "description_zh": "有明显预兆的突刺攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Mikiri Counter - easy to react to once telegraphed.", + "counter_zh": "识破——看到预兆后很容易反应。", "common_mistakes": null, - "name_zh": "突刺", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "long-range-wind-attack", "name": "Long Range Wind Attack", + "name_zh": "远距离风压攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "A long-range wind-based attack.", + "description_zh": "远距离的风压攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Dodge by running sideways to gain a couple of free hits.", + "counter_zh": "向侧面跑动躲开,可以白打几下。", "common_mistakes": null, - "name_zh": "远距离风压攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "close-range-full-screen-sweep", "name": "Close Range Full Screen Sweep", + "name_zh": "近距离全屏横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "A close-range sweep that covers the full screen.", + "description_zh": "覆盖全屏的近距离横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Sprint backwards before it goes off.", + "counter_zh": "在它发动前向后冲刺。", "common_mistakes": null, - "name_zh": "近距离全屏横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] }, { "phase_number": 2, "name": "Phase 2", + "name_zh": "第二阶段", "moves": [ { "id": "dragon-flash", "name": "Dragon Flash", + "name_zh": "秘传·龙闪", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA", "move_type": "ranged", "description": "Strikes downward launching a force wave, followed by a second wave after a short delay.", + "description_zh": "向下斩击放出一道剑气,短暂延迟后再放出第二道。", "telegraph": "Sheathes his sword and readies a stance; a glint shows on the hilt.", + "telegraph_zh": "收刀入鞘摆出架势,刀柄上闪过一道光。", "counter": "Deflect the first wave, then dodge the second.", + "counter_zh": "弹开第一道剑气,然后闪避第二道。", "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage.", - "name_zh": "秘传·龙闪", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA" + "common_mistakes_zh": "防御剑气而不是弹开——防御仍然会受到伤害。" }, { "id": "ashina-cross", "name": "Ashina Cross", + "name_zh": "绝技·苇名十字斩", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9", "move_type": "combo", "description": "Steps forward with two rapid slashes.", + "description_zh": "向前踏步,快速斩击两次。", "telegraph": "Sheathes his sword; after a short delay there is a flash of light at his hand as he steps forward.", + "telegraph_zh": "收刀入鞘;短暂延迟后,他向前踏步时手上会闪过一道光。", "counter": "Press deflect twice without pausing, or run far away to avoid it entirely.", + "counter_zh": "连续按两次弹刀,中间不要停顿;或者跑远完全躲开。", "common_mistakes": null, - "name_zh": "绝技·苇名十字斩", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9" + "common_mistakes_zh": null }, { "id": "ashina-cross-cancel", "name": "Ashina Cross Cancel", + "name_zh": "苇名十字斩(取消)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "If the player is close when he readies Ashina Cross, he cancels it into a quick sword smack followed by a perilous sweep. He also does this after the player repeatedly deflects his attacks.", + "description_zh": "如果他准备苇名十字斩时玩家离得很近,他会中断改为快速一击,接一记危·下段横扫。玩家连续弹开他的攻击后,他也会这样做。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect the initial hit, then jump and stomp.", + "counter_zh": "弹开第一击,然后跳起踩他。", "common_mistakes": null, - "name_zh": "苇名十字斩(取消)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "ichimonji-double", "name": "Ichimonji Double", + "name_zh": "一字斩·二连", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E", "move_type": "overhead", "description": "Performs a double overhead strike.", + "description_zh": "连续两次下劈。", "telegraph": "Raises his blade into a ready stance.", + "telegraph_zh": "举刀摆出架势。", "counter": "Dodge or block; the attack can also be sidestepped. Deflecting triggers a stagger.", + "counter_zh": "闪避或防御;也可以向侧面躲开。弹刀会让他硬直。", "common_mistakes": null, - "name_zh": "一字斩·二连", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E" + "common_mistakes_zh": null }, { "id": "distance-slash", "name": "Distance Slash", + "name_zh": "远距离斩击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "If the player is far away or using an item, he quickly dashes in and slashes.", + "description_zh": "如果玩家离得很远或正在使用道具,他会迅速冲过来斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Maintain enough distance to react in time.", + "counter_zh": "保持足够的距离,以便及时反应。", "common_mistakes": null, - "name_zh": "远距离斩击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "charged-horizontal-force-wave", "name": "Charged Attack, Horizontal Force Wave", + "name_zh": "蓄力横向剑气", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Swings in a spinning motion, causing a large circular force wave.", + "description_zh": "旋转挥刀,放出一道巨大的环形剑气。", "telegraph": "Sheathes his sword and charges the attack.", + "telegraph_zh": "收刀入鞘,蓄力。", "counter": "Wait, then deflect as soon as he begins moving his arm, or sprint and jump toward him, then jump again onto his head.", + "counter_zh": "等待,在他开始挥动手臂时立刻弹刀;或者冲刺跳向他,再踩他的头跳起。", "common_mistakes": null, - "name_zh": "蓄力横向剑气", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "three-to-four-hit-combo", "name": "Three-to-Four Hit Combo", + "name_zh": "三至四连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A series of main slashes with delays between hits.", + "description_zh": "一连串主要斩击,每击之间有停顿。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect each hit; once he finishes slashing he's open for a quick two hits.", + "counter_zh": "逐一弹开;他砍完后会露出破绽,可以快速攻击两下。", "common_mistakes": null, - "name_zh": "三至四连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "thrust", "name": "Thrust", + "name_zh": "突刺", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "A telegraphed thrust attack.", + "description_zh": "有明显预兆的突刺攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Mikiri Counter - easy to react to once telegraphed.", + "counter_zh": "识破——看到预兆后很容易反应。", "common_mistakes": null, - "name_zh": "突刺", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "long-range-wind-attack", "name": "Long Range Wind Attack", + "name_zh": "远距离风压攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "A long-range wind-based attack.", + "description_zh": "远距离的风压攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Dodge by running sideways to gain a couple of free hits.", + "counter_zh": "向侧面跑动躲开,可以白打几下。", "common_mistakes": null, - "name_zh": "远距离风压攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "close-range-full-screen-sweep", "name": "Close Range Full Screen Sweep", + "name_zh": "近距离全屏横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "A close-range sweep that covers the full screen.", + "description_zh": "覆盖全屏的近距离横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Sprint backwards before it goes off.", + "counter_zh": "在它发动前向后冲刺。", "common_mistakes": null, - "name_zh": "近距离全屏横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "charged-double-projected-force", "name": "Charge Attack, Double Projected Force", + "name_zh": "蓄力双重剑气", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Makes two slashes that each launch a force disc. Deflecting the discs does not affect his posture.", + "description_zh": "斩击两次,每次放出一道圆盘状剑气。弹开剑气不会影响他的躯干。", "telegraph": "Sheathes his sword and charges the attack.", + "telegraph_zh": "收刀入鞘,蓄力。", "counter": "The discs deal damage through block if not deflected properly, but the attack is easy to dodge.", + "counter_zh": "如果没有正确弹开,剑气会穿透防御造成伤害,不过这招很容易闪避。", "common_mistakes": null, - "name_zh": "蓄力双重剑气", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "leaping-slam", "name": "Leaping Slam", + "name_zh": "跳跃砸击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "When far away or the player is using an item, he leaps and slams with his spear, followed by a sword strike or gunshots.", + "description_zh": "玩家离得很远或正在使用道具时,他会跃起用长枪砸下,之后接一记挥刀或枪击。", "telegraph": "Leaps into the air before slamming down with his spear.", + "telegraph_zh": "用长枪砸下前会先跃到空中。", "counter": "Deflect the slam, then counter-attack.", + "counter_zh": "弹开这一砸,然后反击。", "common_mistakes": null, - "name_zh": "跳跃砸击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "retreating-sweep", "name": "Retreating Sweep", + "name_zh": "后撤横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Leaps backward into the air and sweeps with his spear, often following several blocked player attacks. Comes out very quickly.", + "description_zh": "向后跃到空中并用长枪横扫,常在玩家连续几次攻击被他防住后出现。出招非常快。", "telegraph": "Leaps backward into the air.", + "telegraph_zh": "向后跃到空中。", "counter": "Deflect, or quickly dodge forward for a free hit.", + "counter_zh": "弹刀,或者迅速向前闪避白打一下。", "common_mistakes": null, - "name_zh": "后撤横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "quad-shot", "name": "Quad Shot", + "name_zh": "四连射击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "At a distance, fires several rapid pistol shots followed by a large thrust from the spear. The last bullet is slightly delayed.", + "description_zh": "在远处快速开几枪,然后用长枪大幅突刺。最后一发子弹会稍有延迟。", "telegraph": "Pulls out a pistol.", + "telegraph_zh": "掏出手枪。", "counter": "Learn to deflect the shots, then Mikiri counter the following thrust or jump-attack if it's a sweep. Simply blocking the bullets damages posture heavily.", + "counter_zh": "学会弹开子弹,然后识破接下来的突刺;如果是横扫,就跳起攻击。只防御子弹会受到大量躯干伤害。", "common_mistakes": "Missing the slightly delayed last bullet, which can break your posture.", - "name_zh": "四连射击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "漏掉稍有延迟的最后一发子弹,可能会被打破躯干。" } ] }, { "phase_number": 3, "name": "Phase 3", + "name_zh": "第三阶段", "moves": [ { "id": "dragon-flash", "name": "Dragon Flash", + "name_zh": "秘传·龙闪", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA", "move_type": "ranged", "description": "Strikes downward launching a force wave, followed by a second wave after a short delay.", + "description_zh": "向下斩击放出一道剑气,短暂延迟后再放出第二道。", "telegraph": "Sheathes his sword and readies a stance; a glint shows on the hilt.", + "telegraph_zh": "收刀入鞘摆出架势,刀柄上闪过一道光。", "counter": "Deflect the first wave, then dodge the second.", + "counter_zh": "弹开第一道剑气,然后闪避第二道。", "common_mistakes": "Blocking the waves instead of deflecting them - blocked waves still leak damage.", - "name_zh": "秘传·龙闪", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%A7%98%E4%BC%A0%C2%B7%E9%BE%99%E9%97%AA" + "common_mistakes_zh": "防御剑气而不是弹开——防御仍然会受到伤害。" }, { "id": "ashina-cross", "name": "Ashina Cross", + "name_zh": "绝技·苇名十字斩", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9", "move_type": "combo", "description": "Steps forward with two rapid slashes.", + "description_zh": "向前踏步,快速斩击两次。", "telegraph": "Sheathes his sword; after a short delay there is a flash of light at his hand as he steps forward.", + "telegraph_zh": "收刀入鞘;短暂延迟后,他向前踏步时手上会闪过一道光。", "counter": "Press deflect twice without pausing, or run far away to avoid it entirely.", + "counter_zh": "连续按两次弹刀,中间不要停顿;或者跑远完全躲开。", "common_mistakes": null, - "name_zh": "绝技·苇名十字斩", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E7%BB%9D%E6%8A%80%C2%B7%E8%8B%87%E5%90%8D%E5%8D%81%E5%AD%97%E6%96%A9" + "common_mistakes_zh": null }, { "id": "ashina-cross-cancel", "name": "Ashina Cross Cancel", + "name_zh": "苇名十字斩(取消)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "If the player is close when he readies Ashina Cross, he cancels it into a quick sword smack followed by a perilous sweep. He also does this after the player repeatedly deflects his attacks.", + "description_zh": "如果他准备苇名十字斩时玩家离得很近,他会中断改为快速一击,接一记危·下段横扫。玩家连续弹开他的攻击后,他也会这样做。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect the initial hit, then jump and stomp.", + "counter_zh": "弹开第一击,然后跳起踩他。", "common_mistakes": null, - "name_zh": "苇名十字斩(取消)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "ichimonji-double", "name": "Ichimonji Double", + "name_zh": "一字斩·二连", + "name_zh_source": "wiki", + "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E", "move_type": "overhead", "description": "Performs a double overhead strike.", + "description_zh": "连续两次下劈。", "telegraph": "Raises his blade into a ready stance.", + "telegraph_zh": "举刀摆出架势。", "counter": "Dodge or block; the attack can also be sidestepped. Deflecting triggers a stagger.", + "counter_zh": "闪避或防御;也可以向侧面躲开。弹刀会让他硬直。", "common_mistakes": null, - "name_zh": "一字斩·二连", - "name_zh_source": "wiki", - "name_zh_source_url": "https://wiki.biligame.com/sekiro/%E4%B8%80%E5%AD%97%E6%96%A9%C2%B7%E4%BA%8C%E8%BF%9E" + "common_mistakes_zh": null }, { "id": "distance-slash", "name": "Distance Slash", + "name_zh": "远距离斩击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "If the player is far away or using an item, he quickly dashes in and slashes.", + "description_zh": "如果玩家离得很远或正在使用道具,他会迅速冲过来斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Maintain enough distance to react in time.", + "counter_zh": "保持足够的距离,以便及时反应。", "common_mistakes": null, - "name_zh": "远距离斩击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "charged-horizontal-force-wave", "name": "Charged Attack, Horizontal Force Wave", + "name_zh": "蓄力横向剑气", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Swings in a spinning motion, causing a large circular force wave.", + "description_zh": "旋转挥刀,放出一道巨大的环形剑气。", "telegraph": "Sheathes his sword and charges the attack.", + "telegraph_zh": "收刀入鞘,蓄力。", "counter": "Wait, then deflect as soon as he begins moving his arm, or sprint and jump toward him, then jump again onto his head.", + "counter_zh": "等待,在他开始挥动手臂时立刻弹刀;或者冲刺跳向他,再踩他的头跳起。", "common_mistakes": null, - "name_zh": "蓄力横向剑气", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "three-to-four-hit-combo", "name": "Three-to-Four Hit Combo", + "name_zh": "三至四连击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "A series of main slashes with delays between hits.", + "description_zh": "一连串主要斩击,每击之间有停顿。", "telegraph": null, + "telegraph_zh": null, "counter": "Deflect each hit; once he finishes slashing he's open for a quick two hits.", + "counter_zh": "逐一弹开;他砍完后会露出破绽,可以快速攻击两下。", "common_mistakes": null, - "name_zh": "三至四连击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "thrust", "name": "Thrust", + "name_zh": "突刺", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "thrust", "description": "A telegraphed thrust attack.", + "description_zh": "有明显预兆的突刺攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Mikiri Counter - easy to react to once telegraphed.", + "counter_zh": "识破——看到预兆后很容易反应。", "common_mistakes": null, - "name_zh": "突刺", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "long-range-wind-attack", "name": "Long Range Wind Attack", + "name_zh": "远距离风压攻击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "A long-range wind-based attack.", + "description_zh": "远距离的风压攻击。", "telegraph": null, + "telegraph_zh": null, "counter": "Dodge by running sideways to gain a couple of free hits.", + "counter_zh": "向侧面跑动躲开,可以白打几下。", "common_mistakes": null, - "name_zh": "远距离风压攻击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "close-range-full-screen-sweep", "name": "Close Range Full Screen Sweep", + "name_zh": "近距离全屏横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "A close-range sweep that covers the full screen.", + "description_zh": "覆盖全屏的近距离横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Sprint backwards before it goes off.", + "counter_zh": "在它发动前向后冲刺。", "common_mistakes": null, - "name_zh": "近距离全屏横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "charged-double-projected-force", "name": "Charge Attack, Double Projected Force", + "name_zh": "蓄力双重剑气", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "Makes two slashes that each launch a force disc. Deflecting the discs does not affect his posture.", + "description_zh": "斩击两次,每次放出一道圆盘状剑气。弹开剑气不会影响他的躯干。", "telegraph": "Sheathes his sword and charges the attack.", + "telegraph_zh": "收刀入鞘,蓄力。", "counter": "The discs deal damage through block if not deflected properly, but the attack is easy to dodge.", + "counter_zh": "如果没有正确弹开,剑气会穿透防御造成伤害,不过这招很容易闪避。", "common_mistakes": null, - "name_zh": "蓄力双重剑气", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "leaping-slam", "name": "Leaping Slam", + "name_zh": "跳跃砸击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "When far away or the player is using an item, he leaps and slams with his spear, followed by a sword strike or gunshots.", + "description_zh": "玩家离得很远或正在使用道具时,他会跃起用长枪砸下,之后接一记挥刀或枪击。", "telegraph": "Leaps into the air before slamming down with his spear.", + "telegraph_zh": "用长枪砸下前会先跃到空中。", "counter": "Deflect the slam, then counter-attack.", + "counter_zh": "弹开这一砸,然后反击。", "common_mistakes": null, - "name_zh": "跳跃砸击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "retreating-sweep", "name": "Retreating Sweep", + "name_zh": "后撤横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "Leaps backward into the air and sweeps with his spear, often following several blocked player attacks. Comes out very quickly.", + "description_zh": "向后跃到空中并用长枪横扫,常在玩家连续几次攻击被他防住后出现。出招非常快。", "telegraph": "Leaps backward into the air.", + "telegraph_zh": "向后跃到空中。", "counter": "Deflect, or quickly dodge forward for a free hit.", + "counter_zh": "弹刀,或者迅速向前闪避白打一下。", "common_mistakes": null, - "name_zh": "后撤横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "quad-shot", "name": "Quad Shot", + "name_zh": "四连射击", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "ranged", "description": "At a distance, fires several rapid pistol shots followed by a large thrust from the spear. The last bullet is slightly delayed.", + "description_zh": "在远处快速开几枪,然后用长枪大幅突刺。最后一发子弹会稍有延迟。", "telegraph": "Pulls out a pistol.", + "telegraph_zh": "掏出手枪。", "counter": "Learn to deflect the shots, then Mikiri counter the following thrust or jump-attack if it's a sweep. Simply blocking the bullets damages posture heavily.", + "counter_zh": "学会弹开子弹,然后识破接下来的突刺;如果是横扫,就跳起攻击。只防御子弹会受到大量躯干伤害。", "common_mistakes": "Missing the slightly delayed last bullet, which can break your posture.", - "name_zh": "四连射击", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": "漏掉稍有延迟的最后一发子弹,可能会被打破躯干。" }, { "id": "lightning-slash", "name": "Lightning Slash", + "name_zh": "雷电斩", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "unblockable", "description": "Launches lightning at the player with a sword swing.", + "description_zh": "挥刀向玩家放出雷电。", "telegraph": "Jumps into the air as lightning converges on him.", + "telegraph_zh": "跃到空中,雷电向他聚集。", "counter": "Jump into the air and use Lightning Reversal, Mist Raven, or Sakura Dance to redirect the attack back at him and stun him.", + "counter_zh": "跳到空中,用雷反、雾鸦或秘传·樱舞把雷电返还给他,让他硬直。", "common_mistakes": null, - "name_zh": "雷电斩", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "lightning-overhead", "name": "Lightning-Enhanced Overhead Attacks", + "name_zh": "雷电强化下劈", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "overhead", "description": "Large overhead attacks with added lightning damage.", + "description_zh": "附带雷电伤害的大幅度下劈。", "telegraph": null, + "telegraph_zh": null, "counter": "Can be deflected, though the timing is difficult.", + "counter_zh": "可以弹刀,但时机很难把握。", "common_mistakes": null, - "name_zh": "雷电强化下劈", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "lightning-spear-sweep", "name": "Large Sweep with Lightning-Covered Spear", + "name_zh": "雷枪大横扫", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "sweep", "description": "A large swing with the spear covered in lightning.", + "description_zh": "用缠着雷电的长枪大幅横扫。", "telegraph": null, + "telegraph_zh": null, "counter": "Get close when he does this and jump to deflect it directly back at him for big vitality and posture damage, plus a flurry of free hits.", + "counter_zh": "他使用这招时贴近并跳起,把雷电直接返还给他,能造成大量体力和躯干伤害,还能白打一轮。", "common_mistakes": null, - "name_zh": "雷枪大横扫", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null }, { "id": "distance-slash-lightning", "name": "Distance Slash (Lightning Variant)", + "name_zh": "远距离斩击(雷电)", + "name_zh_source": "translation", + "name_zh_source_url": null, "move_type": "combo", "description": "More of a distance slash than the vertical Phase 1 wind attack.", + "description_zh": "与其说是第一阶段那种垂直的风压攻击,不如说更像远距离斩击。", "telegraph": null, + "telegraph_zh": null, "counter": "Similar avoidance strategy to the wind attacks - dodge by running sideways.", + "counter_zh": "躲法和风压攻击类似——向侧面跑动躲开。", "common_mistakes": null, - "name_zh": "远距离斩击(雷电)", - "name_zh_source": "translation", - "name_zh_source_url": null + "common_mistakes_zh": null } ] } diff --git a/backend/tests/test_auth.py b/backend/tests/test_auth.py index 47211d6..0103218 100644 --- a/backend/tests/test_auth.py +++ b/backend/tests/test_auth.py @@ -128,3 +128,22 @@ def test_boss_data_is_public(anon_client): def test_attempts_and_analytics_require_login(anon_client, method, path): kwargs = {"json": {"result": "victory"}} if method == "post" else {} assert getattr(anon_client, method)(path, **kwargs).status_code == 401 + + +def test_language_preference_starts_unset_and_is_saved_on_the_account(anon_client): + assert register(anon_client).json()["preferred_language"] is None + + resp = anon_client.patch("/api/auth/me", json={"preferred_language": "zh"}) + + assert resp.status_code == 200 + assert resp.json()["preferred_language"] == "zh" + anon_client.cookies.clear() + login = anon_client.post("/api/auth/login", json=CREDENTIALS) + assert login.json()["preferred_language"] == "zh" + + +def test_language_preference_rejects_unknown_languages_and_requires_login(anon_client): + assert anon_client.patch("/api/auth/me", json={"preferred_language": "zh"}).status_code == 401 + + register(anon_client) + assert anon_client.patch("/api/auth/me", json={"preferred_language": "fr"}).status_code == 422 diff --git a/backend/tests/test_move_chinese_names.py b/backend/tests/test_move_chinese_names.py index 1601b83..10def39 100644 --- a/backend/tests/test_move_chinese_names.py +++ b/backend/tests/test_move_chinese_names.py @@ -75,3 +75,46 @@ def test_boss_detail_returns_chinese_names_and_their_sources(anon_client): assert moves["floating-passage"]["name_zh_source_url"].startswith("https://wiki.biligame.com/") assert moves["perilous-thrust"]["name_zh_source"] == "translation" assert moves["perilous-thrust"]["name_zh_source_url"] is None + + +# --- Bilingual content (V3 Milestone 4) ------------------------------------------- + + +def test_every_english_text_field_has_a_chinese_version_and_vice_versa(): + for boss in load_boss_data(): + assert boss.location_zh, boss.id + for phase in boss.phases: + assert phase.name_zh, f"{boss.id} phase {phase.phase_number}" + for move in phase.moves: + for field in ("description", "telegraph", "counter", "common_mistakes"): + english, chinese = getattr(move, field), getattr(move, f"{field}_zh") + assert (english is None) == (chinese is None), f"{boss.id}/{move.id}.{field}" + + +def test_seed_rejects_english_text_without_its_chinese_version(session): + data = load_boss_data() + move = next(m for p in data[0].phases for m in p.moves if m.description) + move.description_zh = None + + with pytest.raises(SeedError, match="description and description_zh must both be set"): + sync_reference_data(session, data) + + +def test_seed_rejects_a_boss_without_a_chinese_location(session): + data = load_boss_data() + data[0].location_zh = None + + with pytest.raises(SeedError, match="has no Chinese location"): + sync_reference_data(session, data) + + +def test_boss_detail_and_list_return_both_languages(anon_client): + boss = anon_client.get("/api/bosses/genichiro-ashina").json() + summary = next(b for b in anon_client.get("/api/bosses").json() if b["id"] == "genichiro-ashina") + floating = next(m for m in boss["phases"][0]["moves"] if m["id"] == "floating-passage") + + assert boss["location"] and boss["location_zh"] + assert summary["location_zh"] == boss["location_zh"] + assert boss["phases"][0]["name_zh"] == "第一阶段" + assert floating["description"] and floating["description_zh"] + assert floating["common_mistakes_zh"] diff --git a/backend/tests/test_sekiro_analytics.py b/backend/tests/test_sekiro_analytics.py index 50ba399..2760963 100644 --- a/backend/tests/test_sekiro_analytics.py +++ b/backend/tests/test_sekiro_analytics.py @@ -93,6 +93,7 @@ def test_recent_attempts_are_newest_first_with_names_and_capped_at_ten(client): "attempt_id": last["id"], "boss_id": "guardian-ape", "boss_name": "Guardian Ape", + "boss_name_zh": "狮子猿", "timestamp": None, "result": "failed", "phase_reached": 2, diff --git a/frontend/integration-tests/attemptWorkflow.test.ts b/frontend/integration-tests/attemptWorkflow.test.ts index 2031e0e..26e0ec4 100644 --- a/frontend/integration-tests/attemptWorkflow.test.ts +++ b/frontend/integration-tests/attemptWorkflow.test.ts @@ -305,4 +305,21 @@ describe("frontend calling the real backend", () => { await login({ username: "integration_wolf", password: "kusabimaru" }); expect(await getBossAttempts("genichiro-ashina")).toHaveLength(2); }); + + it("saves the interface language on the account and serves boss content in both languages", async () => { + const { getCurrentUser, updatePreferredLanguage } = await import("../src/api/auth"); + const { getBossById } = await import("../src/api/bosses"); + + expect((await getCurrentUser())?.preferred_language).toBeNull(); + await updatePreferredLanguage("zh"); + expect((await getCurrentUser())?.preferred_language).toBe("zh"); + + const boss = await getBossById("genichiro-ashina"); + const floatingPassage = boss.phases[0].moves.find((m) => m.id === "floating-passage"); + expect(boss.location_zh).toBeTruthy(); + expect(boss.phases[0].name_zh).toBe("第一阶段"); + expect(floatingPassage?.name_zh).toBe("绝技·飞渡浮舟"); + expect(floatingPassage?.description).toBeTruthy(); + expect(floatingPassage?.description_zh).toBeTruthy(); + }); }); diff --git a/frontend/src/api/auth.test.ts b/frontend/src/api/auth.test.ts index 02ea224..4877f3b 100644 --- a/frontend/src/api/auth.test.ts +++ b/frontend/src/api/auth.test.ts @@ -19,11 +19,11 @@ describe("auth api", () => { }); it("posts credentials to the login endpoint", async () => { - const fetchMock = mockFetch(200, { id: "1", username: "wolf" }); + const fetchMock = mockFetch(200, { id: "1", username: "wolf", preferred_language: null }); const user = await login({ username: "wolf", password: "kusabimaru" }); - expect(user).toEqual({ id: "1", username: "wolf" }); + expect(user).toEqual({ id: "1", username: "wolf", preferred_language: null }); expect(fetchMock).toHaveBeenCalledWith( "/api/auth/login", expect.objectContaining({ method: "POST", body: JSON.stringify({ username: "wolf", password: "kusabimaru" }) }) diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index 0f8cb04..631622f 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -13,6 +13,10 @@ export function logout(): Promise { return apiRequest("/auth/logout", { method: "POST" }); } +export function updatePreferredLanguage(language: "en" | "zh"): Promise { + return apiRequest("/auth/me", { method: "PATCH", body: JSON.stringify({ preferred_language: language }) }); +} + /** The logged-in user, or null when there is no valid session. */ export async function getCurrentUser(): Promise { try { diff --git a/frontend/src/api/bosses.test.ts b/frontend/src/api/bosses.test.ts index cdb9057..fe40aa2 100644 --- a/frontend/src/api/bosses.test.ts +++ b/frontend/src/api/bosses.test.ts @@ -10,7 +10,7 @@ describe("bosses api", () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200, - json: async () => [{ id: "genichiro-ashina", name: "Genichiro Ashina", name_zh: "苇名弦一郎", location: "Ashina Castle" }], + json: async () => [{ id: "genichiro-ashina", name: "Genichiro Ashina", name_zh: "苇名弦一郎", location: "Ashina Castle", location_zh: null }], }); vi.stubGlobal("fetch", fetchMock); @@ -29,6 +29,7 @@ describe("bosses api", () => { name: "Genichiro Ashina", game: "sekiro", location: "Ashina Castle", + location_zh: null, phases: [], }), }); diff --git a/frontend/src/auth/AuthProvider.test.tsx b/frontend/src/auth/AuthProvider.test.tsx index 60e94f1..50c844f 100644 --- a/frontend/src/auth/AuthProvider.test.tsx +++ b/frontend/src/auth/AuthProvider.test.tsx @@ -30,7 +30,7 @@ function renderProbe() { describe("AuthProvider", () => { it("restores the logged-in user from the session cookie", async () => { - vi.mocked(authApi.getCurrentUser).mockResolvedValue({ id: "1", username: "wolf" }); + vi.mocked(authApi.getCurrentUser).mockResolvedValue({ id: "1", username: "wolf", preferred_language: null }); renderProbe(); @@ -41,7 +41,7 @@ describe("AuthProvider", () => { it("logs in and out", async () => { vi.mocked(authApi.getCurrentUser).mockResolvedValue(null); - vi.mocked(authApi.login).mockResolvedValue({ id: "1", username: "wolf" }); + vi.mocked(authApi.login).mockResolvedValue({ id: "1", username: "wolf", preferred_language: null }); vi.mocked(authApi.logout).mockResolvedValue(undefined); renderProbe(); await screen.findByText("status: ready"); @@ -54,7 +54,7 @@ describe("AuthProvider", () => { }); it("forgets the user when any request comes back 401", async () => { - vi.mocked(authApi.getCurrentUser).mockResolvedValue({ id: "1", username: "wolf" }); + vi.mocked(authApi.getCurrentUser).mockResolvedValue({ id: "1", username: "wolf", preferred_language: null }); renderProbe(); await screen.findByText("user: wolf"); diff --git a/frontend/src/auth/testAuth.tsx b/frontend/src/auth/testAuth.tsx index 93999cc..71b7f5e 100644 --- a/frontend/src/auth/testAuth.tsx +++ b/frontend/src/auth/testAuth.tsx @@ -4,7 +4,7 @@ import type { User } from "../types"; import { AuthContext } from "./authContext"; import type { AuthState } from "./authContext"; -export const testUser: User = { id: "1", username: "wolf" }; +export const testUser: User = { id: "1", username: "wolf", preferred_language: null }; /** Wraps `ui` in a fixed auth state for component tests, without touching the network. */ export function withAuth(ui: ReactElement, overrides: Partial = {}): ReactElement { diff --git a/frontend/src/components/AnalyticsPanel.test.tsx b/frontend/src/components/AnalyticsPanel.test.tsx index faf02be..6ffd989 100644 --- a/frontend/src/components/AnalyticsPanel.test.tsx +++ b/frontend/src/components/AnalyticsPanel.test.tsx @@ -8,19 +8,22 @@ const boss: Boss = { name: "Genichiro Ashina", game: "sekiro", location: "Ashina Castle", + location_zh: null, phases: [ { phase_number: 1, name: "Phase 1", + name_zh: null, moves: [ - { id: "perilous-thrust", name: "Perilous Thrust", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, + { id: "perilous-thrust", name: "Perilous Thrust", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null, description_zh: null, telegraph_zh: null, counter_zh: null, common_mistakes_zh: null }, ], }, { phase_number: 2, name: "Phase 2", + name_zh: null, moves: [ - { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, + { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null, description_zh: null, telegraph_zh: null, counter_zh: null, common_mistakes_zh: null }, ], }, ], diff --git a/frontend/src/components/AnalyticsPanel.tsx b/frontend/src/components/AnalyticsPanel.tsx index 59d66d7..1b6369d 100644 --- a/frontend/src/components/AnalyticsPanel.tsx +++ b/frontend/src/components/AnalyticsPanel.tsx @@ -1,3 +1,5 @@ +import { formatPhase, useLanguage } from "../i18n/language"; +import type { Translate } from "../i18n/language"; import type { Boss, BossAnalytics, RecentAnalytics } from "../types"; import { topKeys } from "../utils/analytics"; import { getMoveName } from "../utils/moves"; @@ -7,70 +9,74 @@ interface AnalyticsPanelProps { analytics: BossAnalytics; } -function describeTop(keys: string[], label: (key: string) => string, empty: string): string { +function describeTop(keys: string[], label: (key: string) => string, empty: string, t: Translate): string { if (keys.length === 0) return empty; - const names = keys.map(label).join(", "); - return keys.length > 1 ? `${names} (tied)` : names; + const names = keys.map(label).join(t("listSeparator")); + return keys.length > 1 ? t("tied", { names }) : names; } -function recentLabel(recent: RecentAnalytics): string { +function recentLabel(recent: RecentAnalytics, t: Translate): string { return recent.total_attempts < recent.window_size - ? `Last ${recent.total_attempts} (all so far)` - : `Last ${recent.window_size}`; + ? t("analytics.lastAllSoFar", { n: recent.total_attempts }) + : t("analytics.lastN", { n: recent.window_size }); } export default function AnalyticsPanel({ boss, analytics }: AnalyticsPanelProps) { + const { language, t } = useLanguage(); + if (analytics.total_attempts === 0) { return (
    -

    Your Analytics

    -

    Record an attempt to start seeing analytics.

    +

    {t("analytics.heading")}

    +

    {t("analytics.empty")}

    ); } const { recent } = analytics; - const recentHeader = recentLabel(recent); - const phaseLabel = (phase: string) => `Phase ${phase}`; - const moveLabel = (moveId: string) => getMoveName(boss, moveId) ?? moveId; + const recentHeader = recentLabel(recent, t); + const phaseLabel = (phase: string) => formatPhase(phase, language); + const moveLabel = (moveId: string) => getMoveName(boss, moveId, language) ?? moveId; const phaseRows = Object.keys(analytics.failure_by_phase).sort((a, b) => Number(a) - Number(b)); const moveRows = Object.keys(analytics.failure_by_move).sort( (a, b) => analytics.failure_by_move[b] - analytics.failure_by_move[a] ); + const notApplicable = t("analytics.notApplicable"); + const notEnoughData = t("analytics.notEnoughData"); return (
    -

    Your Analytics

    +

    {t("analytics.heading")}

    -

    Main Bottleneck

    +

    {t("analytics.mainBottleneck")}

    -
    All attempts
    -
    {describeTop(topKeys(analytics.failure_by_phase), phaseLabel, "N/A")}
    +
    {t("analytics.allAttempts")}
    +
    {describeTop(topKeys(analytics.failure_by_phase), phaseLabel, notApplicable, t)}
    {recentHeader}
    -
    {describeTop(topKeys(recent.failure_by_phase), phaseLabel, "N/A")}
    +
    {describeTop(topKeys(recent.failure_by_phase), phaseLabel, notApplicable, t)}
    -

    Most Common Failure

    +

    {t("analytics.mostCommonFailure")}

    -
    All attempts
    -
    {describeTop(topKeys(analytics.failure_by_move), moveLabel, "Not enough data")}
    +
    {t("analytics.allAttempts")}
    +
    {describeTop(topKeys(analytics.failure_by_move), moveLabel, notEnoughData, t)}
    {recentHeader}
    -
    {describeTop(topKeys(recent.failure_by_move), moveLabel, "Not enough data")}
    +
    {describeTop(topKeys(recent.failure_by_move), moveLabel, notEnoughData, t)}
    {phaseRows.length > 0 && ( - + - - + + @@ -88,11 +94,11 @@ export default function AnalyticsPanel({ boss, analytics }: AnalyticsPanelProps) {moveRows.length > 0 && (
    Failure Breakdown by Phase{t("analytics.byPhase")}
    PhaseAll attempts{t("analytics.phase")}{t("analytics.allAttempts")} {recentHeader}
    - + - - + + diff --git a/frontend/src/components/AttemptHistory.test.tsx b/frontend/src/components/AttemptHistory.test.tsx index c0b31fc..3622e5c 100644 --- a/frontend/src/components/AttemptHistory.test.tsx +++ b/frontend/src/components/AttemptHistory.test.tsx @@ -8,12 +8,14 @@ const boss: Boss = { name: "Genichiro Ashina", game: "sekiro", location: "Ashina Castle", + location_zh: null, phases: [ { phase_number: 2, name: "Phase 2", + name_zh: null, moves: [ - { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, + { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null, description_zh: null, telegraph_zh: null, counter_zh: null, common_mistakes_zh: null }, ], }, ], diff --git a/frontend/src/components/AttemptHistory.tsx b/frontend/src/components/AttemptHistory.tsx index bb9691b..d30eb5f 100644 --- a/frontend/src/components/AttemptHistory.tsx +++ b/frontend/src/components/AttemptHistory.tsx @@ -1,3 +1,4 @@ +import { formatPhase, useLanguage } from "../i18n/language"; import type { Attempt, Boss } from "../types"; import { getFailureLabel } from "../utils/moves"; @@ -7,22 +8,26 @@ interface AttemptHistoryProps { } export default function AttemptHistory({ boss, attempts }: AttemptHistoryProps) { + const { language, t } = useLanguage(); + return (
    -

    Recent Attempts

    +

    {t("history.heading")}

    {attempts.length === 0 ? ( -

    No attempts recorded yet.

    +

    {t("history.empty")}

    ) : (
      {attempts.map((attempt, index) => (
    • #{attempts.length - index} - {attempt.result === "victory" ? "Victory" : `Failed — Phase ${attempt.phase_reached}`} + {attempt.result === "victory" + ? t("result.victory") + : t("result.failedAt", { phase: formatPhase(attempt.phase_reached, language) })} {attempt.result === "failed" && ( - {getFailureLabel(boss, attempt)} + {getFailureLabel(boss, attempt, t, language)} )}
    • ))} diff --git a/frontend/src/components/BossList.tsx b/frontend/src/components/BossList.tsx index 93ce580..1d81007 100644 --- a/frontend/src/components/BossList.tsx +++ b/frontend/src/components/BossList.tsx @@ -1,11 +1,14 @@ import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { getBosses } from "../api/bosses"; +import { bossLocation, bossName } from "../i18n/content"; +import { useLanguage } from "../i18n/language"; import type { BossSummary } from "../types"; type LoadState = "loading" | "error" | "ready"; export default function BossList() { + const { language, t } = useLanguage(); const [bosses, setBosses] = useState([]); const [state, setState] = useState("loading"); @@ -29,18 +32,17 @@ export default function BossList() { }; }, []); - if (state === "loading") return

      Loading bosses…

      ; - if (state === "error") return

      Failed to load bosses.

      ; + if (state === "loading") return

      {t("bossList.loading")}

      ; + if (state === "error") return

      {t("bossList.error")}

      ; return (
        {bosses.map((boss) => (
      • -

        {boss.name}

        - {boss.name_zh &&

        {boss.name_zh}

        } -

        {boss.location}

        +

        {bossName(boss, language)}

        +

        {bossLocation(boss, language)}

        - View Boss + {t("bossList.view")}
      • ))} diff --git a/frontend/src/components/ChineseMoveName.tsx b/frontend/src/components/ChineseMoveName.tsx deleted file mode 100644 index bcd61c9..0000000 --- a/frontend/src/components/ChineseMoveName.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { BossMove } from "../types"; - -const TRANSLATION_NOTE = "Translated for this app; not an official in-game name."; - -/** - * A move's Chinese name next to its English one. Names taken from a Chinese wiki - * link to that page; translations are labelled so they are never mistaken for - * official names. - */ -export default function ChineseMoveName({ move }: { move: BossMove }) { - if (!move.name_zh) return null; - - if (move.name_zh_source === "wiki" && move.name_zh_source_url) { - return ( - - - {move.name_zh} - - - ); - } - - return ( - - {move.name_zh} - - 译名 - - - ); -} diff --git a/frontend/src/components/MovesetReference.test.tsx b/frontend/src/components/MovesetReference.test.tsx index 4341f24..56c1271 100644 --- a/frontend/src/components/MovesetReference.test.tsx +++ b/frontend/src/components/MovesetReference.test.tsx @@ -1,6 +1,7 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; import MovesetReference from "./MovesetReference"; +import { withLanguage } from "../i18n/testLanguage"; import type { Boss } from "../types"; const boss: Boss = { @@ -9,12 +10,14 @@ const boss: Boss = { name_zh: "剑圣 苇名一心", game: "sekiro", location: "Ashina Reservoir", + location_zh: null, source_name: "Fextralife Sekiro Wiki", source_url: "https://sekiroshadowsdietwice.wiki.fextralife.com/Isshin,+the+Sword+Saint", phases: [ { phase_number: 1, name: "Phase 1", + name_zh: null, moves: [ { id: "dragon-flash", @@ -27,6 +30,10 @@ const boss: Boss = { name_zh: "秘传·龙闪", name_zh_source: "wiki", name_zh_source_url: "https://wiki.biligame.com/sekiro/dragon-flash", + description_zh: "向下斩击放出一道剑气。", + telegraph_zh: "刀柄上闪过一道光。", + counter_zh: "弹开第一道剑气。", + common_mistakes_zh: "防御剑气而不是弹开。", }, { id: "thrust", @@ -39,6 +46,10 @@ const boss: Boss = { name_zh: "突刺", name_zh_source: "translation", name_zh_source_url: null, + description_zh: null, + telegraph_zh: null, + counter_zh: null, + common_mistakes_zh: null, }, ], }, @@ -60,7 +71,7 @@ describe("MovesetReference", () => { const thrust = screen.getByText("Thrust").closest("li") as HTMLElement; // Only the names: no description, telegraph, counter or common-mistake paragraphs. expect(thrust.querySelectorAll("p")).toHaveLength(0); - expect(thrust.textContent).toBe("Thrust突刺译名"); + expect(thrust.textContent).toBe("Thrust"); }); it("links to the boss's data source", () => { @@ -75,18 +86,34 @@ describe("MovesetReference", () => { expect(screen.queryByText(/source:/i)).not.toBeInTheDocument(); }); - it("links Chinese names taken from a wiki to their source page", () => { + it("shows only English in English mode", () => { render(); - const link = screen.getByRole("link", { name: "秘传·龙闪" }); - expect(link).toHaveAttribute("href", "https://wiki.biligame.com/sekiro/dragon-flash"); + expect(screen.getByText("Dragon Flash")).toBeInTheDocument(); + expect(screen.queryByText("秘传·龙闪")).not.toBeInTheDocument(); + expect(screen.queryByText(/向下斩击/)).not.toBeInTheDocument(); }); - it("marks translated Chinese names as translations", () => { - render(); + it("shows only Chinese in Chinese mode, with one note about translations", () => { + render(withLanguage(, "zh")); + + expect(screen.getByRole("heading", { name: "Boss 招式" })).toBeInTheDocument(); + expect(screen.getByText("向下斩击放出一道剑气。")).toBeInTheDocument(); + expect(screen.getByText("前摇:刀柄上闪过一道光。")).toBeInTheDocument(); + expect(screen.getByText("常见失误:防御剑气而不是弹开。")).toBeInTheDocument(); + expect(screen.queryByText("Dragon Flash")).not.toBeInTheDocument(); + expect(screen.queryByText(/Launches a force wave/)).not.toBeInTheDocument(); + expect(screen.getAllByText(/大多由本站译自/)).toHaveLength(1); + }); + + it("links a Chinese name taken from a wiki to its page, and leaves translations unlinked", () => { + render(withLanguage(, "zh")); + expect(screen.getByRole("link", { name: "秘传·龙闪" })).toHaveAttribute( + "href", + "https://wiki.biligame.com/sekiro/dragon-flash" + ); expect(screen.getByText("突刺")).toBeInTheDocument(); expect(screen.queryByRole("link", { name: "突刺" })).not.toBeInTheDocument(); - expect(screen.getByTitle(/not an official in-game name/)).toHaveTextContent("译名"); }); }); diff --git a/frontend/src/components/MovesetReference.tsx b/frontend/src/components/MovesetReference.tsx index ef4b316..4b3e83a 100644 --- a/frontend/src/components/MovesetReference.tsx +++ b/frontend/src/components/MovesetReference.tsx @@ -1,32 +1,58 @@ -import type { Boss } from "../types"; -import ChineseMoveName from "./ChineseMoveName"; +import { moveName, moveText, phaseName } from "../i18n/content"; +import { useLanguage } from "../i18n/language"; +import type { Boss, BossMove } from "../types"; + +function MoveTitle({ move }: { move: BossMove }) { + const { language, t } = useLanguage(); + const name = moveName(move, language); + + // In Chinese, a name taken from a Chinese wiki links to that page. + if (language === "zh" && move.name_zh_source === "wiki" && move.name_zh_source_url) { + return ( + + + {name} + + + ); + } + return {name}; +} export default function MovesetReference({ boss }: { boss: Boss }) { + const { language, t } = useLanguage(); + return (
        -

        Boss Moveset

        +

        {t("moveset.heading")}

        + {language === "zh" &&

        {t("moveset.translationNote")}

        } {boss.phases.map((phase) => (
        -

        {phase.name}

        +

        {phaseName(phase, language)}

          - {phase.moves.map((move) => ( -
        • - {move.name} - - {move.description &&

          {move.description}

          } - {move.telegraph &&

          Telegraph: {move.telegraph}

          } - {move.counter &&

          Counter: {move.counter}

          } - {move.common_mistakes &&

          Common mistake: {move.common_mistakes}

          } -
        • - ))} + {phase.moves.map((move) => { + const description = moveText(move, "description", language); + const telegraph = moveText(move, "telegraph", language); + const counter = moveText(move, "counter", language); + const mistakes = moveText(move, "common_mistakes", language); + return ( +
        • + + {description &&

          {description}

          } + {telegraph &&

          {t("moveset.telegraph", { text: telegraph })}

          } + {counter &&

          {t("moveset.counter", { text: counter })}

          } + {mistakes &&

          {t("moveset.commonMistake", { text: mistakes })}

          } +
        • + ); + })}
        ))} {boss.source_url && (

        - Source:{" "} + {t("moveset.source")}{" "} {boss.source_name ?? boss.source_url} diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx index 9dd3f3d..0a911d5 100644 --- a/frontend/src/components/NavBar.tsx +++ b/frontend/src/components/NavBar.tsx @@ -1,8 +1,10 @@ import { Link, useNavigate } from "react-router-dom"; import { useAuth } from "../auth/authContext"; +import { useLanguage } from "../i18n/language"; export default function NavBar() { const { status, user, logout } = useAuth(); + const { language, setLanguage, t } = useLanguage(); const navigate = useNavigate(); async function handleLogout() { @@ -10,33 +12,43 @@ export default function NavBar() { navigate("/"); } + const otherLanguage = language === "en" ? "zh" : "en"; + return (

        -
        ); } diff --git a/frontend/src/components/PasswordInput.tsx b/frontend/src/components/PasswordInput.tsx index d185eac..7b69466 100644 --- a/frontend/src/components/PasswordInput.tsx +++ b/frontend/src/components/PasswordInput.tsx @@ -1,4 +1,5 @@ import { useId, useState } from "react"; +import { useLanguage } from "../i18n/language"; interface PasswordInputProps { value: string; @@ -24,12 +25,14 @@ function EyeIcon({ crossed }: { crossed: boolean }) { /** A labelled password field with a button that shows or hides what was typed. */ export default function PasswordInput({ value, onChange, autoComplete }: PasswordInputProps) { + const { t } = useLanguage(); const id = useId(); const [visible, setVisible] = useState(false); + const toggleLabel = visible ? t("password.hide") : t("password.show"); return (
        - +
        setVisible((v) => !v)} - aria-label={visible ? "Hide password" : "Show password"} + aria-label={toggleLabel} aria-pressed={visible} - title={visible ? "Hide password" : "Show password"} + title={toggleLabel} > {/* Crossed-out eye while the password is visible: the button hides it again. */} diff --git a/frontend/src/components/ProgressionChart.test.tsx b/frontend/src/components/ProgressionChart.test.tsx index 99ad85a..965bf85 100644 --- a/frontend/src/components/ProgressionChart.test.tsx +++ b/frontend/src/components/ProgressionChart.test.tsx @@ -9,11 +9,12 @@ const boss: Boss = { name_zh: null, game: "sekiro", location: "Hirata Estate", + location_zh: null, source_name: null, source_url: null, phases: [ - { phase_number: 1, name: "Phase 1", moves: [] }, - { phase_number: 2, name: "Phase 2", moves: [] }, + { phase_number: 1, name: "Phase 1", name_zh: null, moves: [] }, + { phase_number: 2, name: "Phase 2", name_zh: null, moves: [] }, ], }; diff --git a/frontend/src/components/ProgressionChart.tsx b/frontend/src/components/ProgressionChart.tsx index 6c4db40..3dd6fdd 100644 --- a/frontend/src/components/ProgressionChart.tsx +++ b/frontend/src/components/ProgressionChart.tsx @@ -1,3 +1,4 @@ +import { formatPhase, useLanguage } from "../i18n/language"; import type { Boss, ProgressionPoint } from "../types"; const X_STEP = 28; @@ -12,11 +13,13 @@ interface ProgressionChartProps { } export default function ProgressionChart({ boss, points }: ProgressionChartProps) { + const { language, t } = useLanguage(); + if (points.length === 0) { return (
        -

        Progression

        -

        Record an attempt to see your progression.

        +

        {t("progression.heading")}

        +

        {t("progression.empty")}

        ); } @@ -32,23 +35,29 @@ export default function ProgressionChart({ boss, points }: ProgressionChartProps const labelEvery = Math.ceil(points.length / MAX_X_LABELS); const path = points.map((p, i) => `${i === 0 ? "M" : "L"}${x(p.attempt_number)},${y(p.phase_reached)}`).join(" "); const victories = points.filter((p) => p.result === "victory").length; + const pointLabel = (p: ProgressionPoint) => + t("progression.point", { + n: p.attempt_number, + phase: formatPhase(p.phase_reached, language), + result: p.result === "victory" ? t("progression.victory") : t("progression.failed"), + }); return (
        -

        Progression

        +

        {t("progression.heading")}

        {phaseNumbers.map((phase) => ( - P{phase} + {t("progression.axisPhase", { n: phase })} ))} @@ -73,9 +82,9 @@ export default function ProgressionChart({ boss, points }: ProgressionChartProps cx={x(p.attempt_number)} cy={y(p.phase_reached)} r={p.result === "victory" ? 7 : 5} - aria-label={`Attempt ${p.attempt_number}: Phase ${p.phase_reached}, ${p.result}`} + aria-label={pointLabel(p)} > - {`Attempt ${p.attempt_number}: Phase ${p.phase_reached}, ${p.result}`} + {pointLabel(p)} ))} diff --git a/frontend/src/components/RecordAttemptForm.test.tsx b/frontend/src/components/RecordAttemptForm.test.tsx index 706df1d..c5b3431 100644 --- a/frontend/src/components/RecordAttemptForm.test.tsx +++ b/frontend/src/components/RecordAttemptForm.test.tsx @@ -13,19 +13,22 @@ const boss: Boss = { name: "Genichiro Ashina", game: "sekiro", location: "Ashina Castle", + location_zh: null, phases: [ { phase_number: 1, name: "Phase 1", + name_zh: null, moves: [ - { id: "thrust-attack", name: "Thrust Attack", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, + { id: "thrust-attack", name: "Thrust Attack", move_type: "thrust", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null, description_zh: null, telegraph_zh: null, counter_zh: null, common_mistakes_zh: null }, ], }, { phase_number: 2, name: "Phase 2", + name_zh: null, moves: [ - { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null }, + { id: "floating-passage", name: "Floating Passage", move_type: "combo", description: null, telegraph: null, counter: null, common_mistakes: null, name_zh: null, name_zh_source: null, name_zh_source_url: null, description_zh: null, telegraph_zh: null, counter_zh: null, common_mistakes_zh: null }, ], }, ], diff --git a/frontend/src/components/RecordAttemptForm.tsx b/frontend/src/components/RecordAttemptForm.tsx index f4f7ce1..2ff9b11 100644 --- a/frontend/src/components/RecordAttemptForm.tsx +++ b/frontend/src/components/RecordAttemptForm.tsx @@ -3,7 +3,8 @@ import type { FormEvent } from "react"; import { createAttempt } from "../api/attempts"; import { ApiError } from "../api/client"; import type { AttemptResult, Boss, CreateAttemptRequest } from "../types"; -import { moveLabel } from "../utils/moves"; +import { bossName, moveName, phaseName } from "../i18n/content"; +import { useLanguage } from "../i18n/language"; const OTHER = "__other__"; const NOT_SURE = "__not_sure__"; @@ -15,6 +16,7 @@ interface RecordAttemptFormProps { } export default function RecordAttemptForm({ boss, onSuccess, onCancel }: RecordAttemptFormProps) { + const { language, t } = useLanguage(); const [result, setResult] = useState("failed"); const [phaseReached, setPhaseReached] = useState(boss.phases[0]?.phase_number ?? 1); const [failureChoice, setFailureChoice] = useState(""); @@ -55,16 +57,17 @@ export default function RecordAttemptForm({ boss, onSuccess, onCancel }: RecordA onSuccess(); } catch (err) { setSubmitting(false); - setError(err instanceof ApiError ? err.message : "Failed to save attempt."); + // Backend validation messages are English; Chinese mode shows a generic message instead. + setError(err instanceof ApiError && language === "en" ? err.message : t("form.saveError")); } } return (
        -

        Record Attempt — {boss.name}

        +

        {t("form.title", { boss: bossName(boss, language) })}

        - Result + {t("form.result")}
        {result === "failed" && (
    Failure Breakdown by Move{t("analytics.byMove")}
    MoveAll attempts{t("analytics.move")}{t("analytics.allAttempts")} {recentHeader}