FN-6913: localize right dock labels
Route right-dock accessibility labels through the app i18n namespace.\n\n- Replace hard-coded right dock and pop-out aria/title copy with translated keys and English fallbacks.\n- Add rightDock locale entries across app locale catalogs and typed resources.\n- Extend RightDock tests to cover localized dock and expanded modal affordance labels.\n\nFiles changed:\n packages/dashboard/app/components/RightDock.tsx | 16 +++++++++++-----\n .../dashboard/app/components/RightDockExpandModal.tsx | 12 +++++++++---\n .../app/components/__tests__/RightDock.test.tsx | 15 ++++++++++++++-\n packages/i18n/locales/en/app.json | 11 +++++++++++\n packages/i18n/locales/es/app.json | 11 +++++++++++\n packages/i18n/locales/fr/app.json | 11 +++++++++++\n packages/i18n/locales/ko/app.json | 11 +++++++++++\n packages/i18n/locales/zh-CN/app.json | 11 +++++++++++\n packages/i18n/locales/zh-TW/app.json | 11 +++++++++++\n packages/i18n/src/resources.d.ts | 11 +++++++++++\n 10 files changed, 111 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-6913 Fusion-Task-Lineage: 1c07da14-d8a3-42ba-8c93-843c3fe1fa63
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from "react";
|
||||
import { Maximize2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
findOverflowViewEntry,
|
||||
getVisibleOverflowViewEntries,
|
||||
@@ -89,6 +90,9 @@ FN-6882 splits right-dock entries into launcher actions and inline views. Action
|
||||
|
||||
FNXC:Navigation 2026-06-22-09:00:
|
||||
The right dock is visible by default on tablet/desktop project screens. Show/hide is owned solely by the canonical Header right-sidebar toggle (the in-dock collapse toggle was removed); the dock takes only `open` and renders null when closed so the main content reclaims the space.
|
||||
|
||||
FNXC:i18n 2026-06-22-00:00:
|
||||
Right-dock affordance labels are user-facing accessibility copy, so route them through the app namespace and keep English defaults colocated with the component for tests and fallback rendering.
|
||||
*/
|
||||
export function RightDock({
|
||||
open,
|
||||
@@ -97,6 +101,7 @@ export function RightDock({
|
||||
onExpand,
|
||||
footerVisible = false,
|
||||
}: RightDockProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const entries = useMemo(() => getVisibleOverflowViewEntries(visibilityOptions), [visibilityOptions]);
|
||||
const [selectedKey, setSelectedKey] = useState<OverflowViewKey>(() => readStoredRightDockView(visibilityOptions));
|
||||
const [width, setWidth] = useState(readStoredRightDockWidth);
|
||||
@@ -198,12 +203,13 @@ export function RightDock({
|
||||
|
||||
const SelectedIcon = selectedEntry.icon;
|
||||
const dockWidth = `${width}px`;
|
||||
const expandSelectedViewLabel = t("rightDock.expandView", "Expand {{label}}", { label: selectedEntry.label });
|
||||
|
||||
return (
|
||||
<aside
|
||||
className={`right-dock${open ? "" : " right-dock--collapsed"}${footerVisible ? " right-dock--with-footer" : ""}`}
|
||||
style={dockWidth ? { width: dockWidth } : undefined}
|
||||
aria-label="Right dock"
|
||||
aria-label={t("rightDock.label", "Right dock")}
|
||||
data-testid="right-dock"
|
||||
>
|
||||
{open ? (
|
||||
@@ -214,7 +220,7 @@ export function RightDock({
|
||||
aria-valuemin={RIGHT_DOCK_MIN_WIDTH}
|
||||
aria-valuemax={RIGHT_DOCK_MAX_WIDTH}
|
||||
aria-valuenow={width}
|
||||
aria-label="Resize right dock"
|
||||
aria-label={t("rightDock.resize", "Resize right dock")}
|
||||
tabIndex={0}
|
||||
data-testid="right-dock-resize-handle"
|
||||
onPointerDown={handleResizeStart}
|
||||
@@ -222,7 +228,7 @@ export function RightDock({
|
||||
/>
|
||||
) : null}
|
||||
<div className="right-dock__toolbar">
|
||||
<div className="right-dock__tabs" role="tablist" aria-label="Right dock views">
|
||||
<div className="right-dock__tabs" role="tablist" aria-label={t("rightDock.views", "Right dock views")}>
|
||||
{entries.map((entry) => {
|
||||
const Icon = entry.icon;
|
||||
const selected = Boolean(entry.render && entry.key === selectedEntry.key);
|
||||
@@ -248,8 +254,8 @@ export function RightDock({
|
||||
<button
|
||||
type="button"
|
||||
className="btn-icon right-dock__expand"
|
||||
aria-label={`Expand ${selectedEntry.label}`}
|
||||
title={`Expand ${selectedEntry.label}`}
|
||||
aria-label={expandSelectedViewLabel}
|
||||
title={expandSelectedViewLabel}
|
||||
data-testid="right-dock-expand"
|
||||
onClick={() => onExpand?.(selectedEntry.key)}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState, type CSSProperties, type PointerEvent as ReactPointerEvent, type RefObject } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Maximize2, X } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { findOverflowViewEntry, type OverflowViewEntry, type OverflowViewKey, type OverflowViewRenderProps, type OverflowViewVisibilityOptions } from "./overflowViewRegistry";
|
||||
import { nextFloatingZ, currentFloatingZ } from "./floatingWindowStack";
|
||||
import "./RightDock.css";
|
||||
@@ -113,6 +114,9 @@ Expanded right-dock views reuse the same overflow registry render function as th
|
||||
|
||||
FNXC:Navigation 2026-06-21-20:16:
|
||||
FN-6882 makes most right-dock entries launcher actions. The expand modal is restricted to inline view entries so action-only tools cannot open an empty modal body.
|
||||
|
||||
FNXC:i18n 2026-06-22-00:00:
|
||||
Expanded right-dock modal affordance labels are accessibility copy and must use the app namespace so locale catalogs and fallback tests cover the modal surface with the dock controls.
|
||||
*/
|
||||
export function RightDockExpandModal({
|
||||
viewKey,
|
||||
@@ -121,6 +125,7 @@ export function RightDockExpandModal({
|
||||
onClose,
|
||||
returnFocusRef,
|
||||
}: RightDockExpandModalProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const resolvedEntry = viewKey ? findOverflowViewEntry(viewKey, visibilityOptions) : undefined;
|
||||
const entry: RenderableOverflowViewEntry | undefined = resolvedEntry?.render ? { ...resolvedEntry, render: resolvedEntry.render } : undefined;
|
||||
|
||||
@@ -295,6 +300,7 @@ export function RightDockExpandModal({
|
||||
}
|
||||
|
||||
const Icon = entry.icon;
|
||||
const expandedViewLabel = t("rightDock.viewExpanded", "{{label}} expanded", { label: entry.label });
|
||||
|
||||
const panelStyle = {
|
||||
left: `${position.x}px`,
|
||||
@@ -306,7 +312,7 @@ export function RightDockExpandModal({
|
||||
|
||||
// FNXC:FloatingWindow 2026-06-22-22:30: Portaled to document.body so this floating modal shares the ONE root stacking context with the other floating modals (FloatingWindow/terminal/New Task) — the shared 10100+ z stack only orders correctly across types when they all live at the document root.
|
||||
return createPortal(
|
||||
<div className="modal-overlay open right-dock-expand-modal-overlay" role="dialog" aria-modal="false" aria-label={`${entry.label} expanded`} data-testid="right-dock-expand-modal" style={{ zIndex }}>
|
||||
<div className="modal-overlay open right-dock-expand-modal-overlay" role="dialog" aria-modal="false" aria-label={expandedViewLabel} data-testid="right-dock-expand-modal" style={{ zIndex }}>
|
||||
<div
|
||||
className="modal right-dock-expand-modal right-dock-expand-modal--floating"
|
||||
style={panelStyle}
|
||||
@@ -319,7 +325,7 @@ export function RightDockExpandModal({
|
||||
className={`right-dock-expand-resize-handle right-dock-expand-resize-handle--${direction}`}
|
||||
data-testid={`right-dock-expand-resize-${direction}`}
|
||||
role="separator"
|
||||
aria-label="Resize expanded right dock window"
|
||||
aria-label={t("rightDock.resizeExpandedView", "Resize expanded right dock window")}
|
||||
onPointerDown={(event) => handleFloatingResizePointerDown(event, direction)}
|
||||
/>
|
||||
))}
|
||||
@@ -333,7 +339,7 @@ export function RightDockExpandModal({
|
||||
<Icon size={16} />
|
||||
<span>{entry.label}</span>
|
||||
</div>
|
||||
<button className="modal-close" onClick={closeAndRestoreFocus} aria-label="Close expanded right dock view" data-testid="right-dock-expand-close">
|
||||
<button className="modal-close" onClick={closeAndRestoreFocus} aria-label={t("rightDock.closeExpandedView", "Close expanded right dock view")} data-testid="right-dock-expand-close">
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -99,6 +99,17 @@ describe("RightDock", () => {
|
||||
expect(screen.queryByTestId("right-dock-tab-documents")).toBeNull();
|
||||
});
|
||||
|
||||
it("exposes localized right-dock affordance labels without an in-dock collapse shell", () => {
|
||||
render(<RightDock open={true} renderProps={renderProps} />);
|
||||
|
||||
expect(screen.getByTestId("right-dock")).toHaveAttribute("aria-label", "Right dock");
|
||||
expect(screen.getByTestId("right-dock-resize-handle")).toHaveAttribute("aria-label", "Resize right dock");
|
||||
expect(screen.getByRole("tablist", { name: "Right dock views" })).toBeInTheDocument();
|
||||
expect(screen.getByTestId("right-dock-expand")).toHaveAttribute("aria-label", "Expand Files");
|
||||
expect(screen.getByTestId("right-dock-expand")).toHaveAttribute("title", "Expand Files");
|
||||
expect(screen.queryByTestId("right-dock-collapse-toggle")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders exactly the current right-dock tool entries and no removed content-view tabs", () => {
|
||||
render(
|
||||
<RightDock
|
||||
@@ -239,6 +250,7 @@ describe("RightDock", () => {
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("right-dock-expand-modal")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("right-dock-expand-modal")).toHaveAttribute("aria-label", "Files expanded");
|
||||
expect(screen.getByTestId("right-dock-expand-body")).toBeInTheDocument();
|
||||
/*
|
||||
FNXC:RightDock 2026-06-22-17:40:
|
||||
@@ -248,7 +260,8 @@ describe("RightDock", () => {
|
||||
expect(screen.getByTestId("right-dock-expand-modal")).toHaveAttribute("aria-modal", "false");
|
||||
expect(screen.getByTestId("right-dock-expand-drag-handle")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("right-dock-expand-modal").querySelector(".right-dock-expand-modal--floating")).not.toBeNull();
|
||||
expect(screen.getByTestId("right-dock-expand-resize-se")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("right-dock-expand-resize-se")).toHaveAttribute("aria-label", "Resize expanded right dock window");
|
||||
expect(screen.getByTestId("right-dock-expand-close")).toHaveAttribute("aria-label", "Close expanded right dock view");
|
||||
fireEvent.click(screen.getByTestId("right-dock-expand-close"));
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 0));
|
||||
|
||||
@@ -5107,6 +5107,17 @@
|
||||
"unavailable": "Research is unavailable for this project.",
|
||||
"viewLabel": "Research view"
|
||||
},
|
||||
"rightDock": {
|
||||
"closeExpandedView": "Close expanded right dock view",
|
||||
"collapse": "Collapse right dock",
|
||||
"expand": "Expand right dock",
|
||||
"expandView": "Expand {{label}}",
|
||||
"label": "Right dock",
|
||||
"resize": "Resize right dock",
|
||||
"viewExpanded": "{{label}} expanded",
|
||||
"views": "Right dock views",
|
||||
"resizeExpandedView": "Resize expanded right dock window"
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "…and {{count}} more",
|
||||
"andMore_other": "…and {{count}} more",
|
||||
|
||||
@@ -5106,6 +5106,17 @@
|
||||
"unavailable": "La investigación no está disponible para este proyecto.",
|
||||
"viewLabel": "Vista de investigación"
|
||||
},
|
||||
"rightDock": {
|
||||
"closeExpandedView": "",
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"expandView": "",
|
||||
"label": "",
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
"andMore_other": "",
|
||||
|
||||
@@ -5106,6 +5106,17 @@
|
||||
"unavailable": "La recherche est indisponible pour ce projet.",
|
||||
"viewLabel": "Affichage de la recherche"
|
||||
},
|
||||
"rightDock": {
|
||||
"closeExpandedView": "",
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"expandView": "",
|
||||
"label": "",
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
"andMore_other": "",
|
||||
|
||||
@@ -5106,6 +5106,17 @@
|
||||
"unavailable": "이 프로젝트에서는 리서치를 사용할 수 없습니다.",
|
||||
"viewLabel": "리서치 보기"
|
||||
},
|
||||
"rightDock": {
|
||||
"closeExpandedView": "",
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"expandView": "",
|
||||
"label": "",
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
"andMore_other": "",
|
||||
|
||||
@@ -5106,6 +5106,17 @@
|
||||
"unavailable": "此项目不支持研究功能。",
|
||||
"viewLabel": "研究视图"
|
||||
},
|
||||
"rightDock": {
|
||||
"closeExpandedView": "",
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"expandView": "",
|
||||
"label": "",
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
"andMore_other": "",
|
||||
|
||||
@@ -5106,6 +5106,17 @@
|
||||
"unavailable": "此專案不支援研究功能。",
|
||||
"viewLabel": "研究檢視"
|
||||
},
|
||||
"rightDock": {
|
||||
"closeExpandedView": "",
|
||||
"collapse": "",
|
||||
"expand": "",
|
||||
"expandView": "",
|
||||
"label": "",
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
"andMore_other": "",
|
||||
|
||||
11
packages/i18n/src/resources.d.ts
vendored
11
packages/i18n/src/resources.d.ts
vendored
@@ -5108,6 +5108,17 @@ export default interface Resources {
|
||||
"unavailable": "Research is unavailable for this project.",
|
||||
"viewLabel": "Research view"
|
||||
},
|
||||
"rightDock": {
|
||||
"closeExpandedView": "Close expanded right dock view",
|
||||
"collapse": "Collapse right dock",
|
||||
"expand": "Expand right dock",
|
||||
"expandView": "Expand {{label}}",
|
||||
"label": "Right dock",
|
||||
"resize": "Resize right dock",
|
||||
"resizeExpandedView": "Resize expanded right dock window",
|
||||
"viewExpanded": "{{label}} expanded",
|
||||
"views": "Right dock views"
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "…and {{count}} more",
|
||||
"andMore_other": "…and {{count}} more",
|
||||
|
||||
Reference in New Issue
Block a user