perf(app): limit initial sidebar workspace rendering

Large project, status, and pinned groups now render 20 rows until expanded. Newly discovered workspaces are prepended without disturbing the saved order.
This commit is contained in:
Mohamed Boudra
2026-07-21 23:56:22 +02:00
parent 309672c8e5
commit 9603397aed
14 changed files with 328 additions and 47 deletions
@@ -94,6 +94,8 @@ import type { StatusGroup } from "@/hooks/sidebar-status-view-model";
import { SidebarWorkspaceMenu } from "@/components/sidebar/sidebar-workspace-menu"; import { SidebarWorkspaceMenu } from "@/components/sidebar/sidebar-workspace-menu";
import { useLongPressDragInteraction } from "@/components/sidebar/use-long-press-drag-interaction"; import { useLongPressDragInteraction } from "@/components/sidebar/use-long-press-drag-interaction";
import { PinnedSectionHeader } from "@/components/sidebar/pinned-section-header"; import { PinnedSectionHeader } from "@/components/sidebar/pinned-section-header";
import { SidebarGroupToggleRow } from "@/components/sidebar/sidebar-group-toggle-row";
import { useLimitedSidebarGroup } from "@/components/sidebar/use-limited-sidebar-group";
import { import {
SidebarWorkspaceRowFrame, SidebarWorkspaceRowFrame,
SidebarWorkspaceRowContent, SidebarWorkspaceRowContent,
@@ -1632,6 +1634,12 @@ function ProjectBlock({
supportsPinningByServerId: ReadonlyMap<string, boolean>; supportsPinningByServerId: ReadonlyMap<string, boolean>;
onToggleWorkspacePin: ToggleSidebarWorkspacePin; onToggleWorkspacePin: ToggleSidebarWorkspacePin;
}) { }) {
const {
visibleItems: visibleWorkspaces,
expanded: workspacesExpanded,
canToggle: canToggleWorkspaces,
toggleExpanded: toggleWorkspacesExpanded,
} = useLimitedSidebarGroup(project.workspaces);
const rowModel = useMemo( const rowModel = useMemo(
() => () =>
buildSidebarProjectRowModel({ buildSidebarProjectRowModel({
@@ -1784,19 +1792,28 @@ function ProjectBlock({
if (!collapsed) { if (!collapsed) {
if (project.workspaces.length > 0) { if (project.workspaces.length > 0) {
projectChildren = ( projectChildren = (
<DraggableList <>
testID={`sidebar-workspace-list-${project.projectKey}`} <DraggableList
data={project.workspaces} testID={`sidebar-workspace-list-${project.projectKey}`}
keyExtractor={workspaceKeyExtractor} data={visibleWorkspaces}
renderItem={renderWorkspace} keyExtractor={workspaceKeyExtractor}
onDragEnd={handleWorkspaceDragEnd} renderItem={renderWorkspace}
extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)} onDragEnd={handleWorkspaceDragEnd}
scrollEnabled={false} extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)}
useDragHandle scrollEnabled={false}
nestable={useNestable} useDragHandle
simultaneousGestureRef={parentGestureRef} nestable={useNestable}
containerStyle={styles.workspaceListContainer} simultaneousGestureRef={parentGestureRef}
/> containerStyle={styles.workspaceListContainer}
/>
{canToggleWorkspaces ? (
<SidebarGroupToggleRow
expanded={workspacesExpanded}
onPress={toggleWorkspacesExpanded}
testID={`sidebar-project-show-more-${project.projectKey}`}
/>
) : null}
</>
); );
} else if (rowModel.trailingAction.kind === "new_workspace") { } else if (rowModel.trailingAction.kind === "new_workspace") {
projectChildren = ( projectChildren = (
@@ -2073,6 +2090,12 @@ function ProjectModeList({
const selectionEnabled = isWorkspaceRoute; const selectionEnabled = isWorkspaceRoute;
const activeWorkspaceSelection = useActiveWorkspaceSelection(); const activeWorkspaceSelection = useActiveWorkspaceSelection();
const { pinnedChats, unpinnedProjects } = pinnedGroups; const { pinnedChats, unpinnedProjects } = pinnedGroups;
const {
visibleItems: visiblePinnedChats,
expanded: pinnedChatsExpanded,
canToggle: canTogglePinnedChats,
toggleExpanded: togglePinnedChatsExpanded,
} = useLimitedSidebarGroup(pinnedChats);
const projectIconTargets = useMemo( const projectIconTargets = useMemo(
() => () =>
projects.flatMap((project) => { projects.flatMap((project) => {
@@ -2330,7 +2353,18 @@ function ProjectModeList({
{pinnedChats.length > 0 ? ( {pinnedChats.length > 0 ? (
<View style={styles.pinnedSection} testID="sidebar-pinned-section"> <View style={styles.pinnedSection} testID="sidebar-pinned-section">
<PinnedSectionHeader collapsed={pinnedCollapsed} onToggle={togglePinnedCollapsed} /> <PinnedSectionHeader collapsed={pinnedCollapsed} onToggle={togglePinnedCollapsed} />
{pinnedCollapsed ? null : pinnedChats.map(renderPinnedChat)} {pinnedCollapsed ? null : (
<>
{visiblePinnedChats.map(renderPinnedChat)}
{canTogglePinnedChats ? (
<SidebarGroupToggleRow
expanded={pinnedChatsExpanded}
onPress={togglePinnedChatsExpanded}
testID="sidebar-pinned-show-more"
/>
) : null}
</>
)}
</View> </View>
) : null} ) : null}
{unpinnedProjects.length > 0 || hasActiveHostFilter ? listHeaderComponent : null} {unpinnedProjects.length > 0 || hasActiveHostFilter ? listHeaderComponent : null}
@@ -0,0 +1,106 @@
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { Pressable, Text, View, type PressableStateCallbackType } from "react-native";
import { ChevronDown, ChevronUp } from "lucide-react-native";
import { StyleSheet, withUnistyles } from "react-native-unistyles";
import { isWeb } from "@/constants/platform";
import type { Theme } from "@/styles/theme";
const foregroundMutedColorMapping = (theme: Theme) => ({
color: theme.colors.foregroundMuted,
});
const foregroundColorMapping = (theme: Theme) => ({
color: theme.colors.foreground,
});
const ThemedChevronDown = withUnistyles(ChevronDown);
const ThemedChevronUp = withUnistyles(ChevronUp);
export function SidebarGroupToggleRow({
expanded,
onPress,
testID,
}: {
expanded: boolean;
onPress: () => void;
testID: string;
}) {
const { t } = useTranslation();
const label = t(
expanded ? "sidebar.workspace.actions.showLess" : "sidebar.workspace.actions.showMore",
);
const rowStyle = useCallback(
({ hovered = false, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
styles.row,
(Boolean(hovered) || pressed) && styles.rowHovered,
],
[],
);
return (
<Pressable
accessibilityRole={isWeb ? undefined : "button"}
accessibilityLabel={label}
onPress={onPress}
style={rowStyle}
testID={testID}
>
{({ hovered, pressed }) => (
<>
<View style={styles.iconSlot}>
{expanded ? (
<ThemedChevronUp
size={14}
uniProps={hovered || pressed ? foregroundColorMapping : foregroundMutedColorMapping}
/>
) : (
<ThemedChevronDown
size={14}
uniProps={hovered || pressed ? foregroundColorMapping : foregroundMutedColorMapping}
/>
)}
</View>
<Text style={hovered || pressed ? styles.textHovered : styles.text} numberOfLines={1}>
{label}
</Text>
</>
)}
</Pressable>
);
}
const styles = StyleSheet.create((theme) => ({
row: {
minHeight: 32,
marginLeft: theme.spacing[6],
marginRight: theme.spacing[1],
paddingVertical: theme.spacing[1],
paddingHorizontal: theme.spacing[2],
borderRadius: theme.borderRadius.md,
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
userSelect: "none",
},
rowHovered: {
backgroundColor: theme.colors.surfaceSidebarHover,
},
iconSlot: {
width: theme.iconSize.sm,
height: theme.iconSize.sm,
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
},
text: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
minWidth: 0,
flexShrink: 1,
},
textHovered: {
color: theme.colors.foreground,
fontSize: theme.fontSize.sm,
minWidth: 0,
flexShrink: 1,
},
}));
@@ -42,6 +42,8 @@ import {
import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store"; import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store";
import { SidebarWorkspaceMenu } from "@/components/sidebar/sidebar-workspace-menu"; import { SidebarWorkspaceMenu } from "@/components/sidebar/sidebar-workspace-menu";
import { PinnedSectionHeader } from "@/components/sidebar/pinned-section-header"; import { PinnedSectionHeader } from "@/components/sidebar/pinned-section-header";
import { SidebarGroupToggleRow } from "@/components/sidebar/sidebar-group-toggle-row";
import { useLimitedSidebarGroup } from "@/components/sidebar/use-limited-sidebar-group";
import type { ToggleSidebarWorkspacePin } from "@/hooks/use-sidebar-workspace-pin"; import type { ToggleSidebarWorkspacePin } from "@/hooks/use-sidebar-workspace-pin";
// Themed icon wrappers // Themed icon wrappers
@@ -93,6 +95,12 @@ export function SidebarStatusWorkspaceList({
const togglePinnedCollapsed = useSidebarCollapsedSectionsStore( const togglePinnedCollapsed = useSidebarCollapsedSectionsStore(
(state) => state.togglePinnedCollapsed, (state) => state.togglePinnedCollapsed,
); );
const {
visibleItems: visiblePinnedWorkspaces,
expanded: pinnedWorkspacesExpanded,
canToggle: canTogglePinnedWorkspaces,
toggleExpanded: togglePinnedWorkspacesExpanded,
} = useLimitedSidebarGroup(pinnedWorkspaces);
const statusShortcutIndex = showShortcutBadges ? shortcutIndexByWorkspaceKey : new Map(); const statusShortcutIndex = showShortcutBadges ? shortcutIndexByWorkspaceKey : new Map();
const content = ( const content = (
@@ -100,9 +108,9 @@ export function SidebarStatusWorkspaceList({
{pinnedWorkspaces.length > 0 ? ( {pinnedWorkspaces.length > 0 ? (
<View style={styles.pinnedSection} testID="sidebar-pinned-section"> <View style={styles.pinnedSection} testID="sidebar-pinned-section">
<PinnedSectionHeader collapsed={pinnedCollapsed} onToggle={togglePinnedCollapsed} /> <PinnedSectionHeader collapsed={pinnedCollapsed} onToggle={togglePinnedCollapsed} />
{pinnedCollapsed {pinnedCollapsed ? null : (
? null <>
: pinnedWorkspaces.map((workspace) => ( {visiblePinnedWorkspaces.map((workspace) => (
<StatusWorkspaceRow <StatusWorkspaceRow
key={workspace.workspaceKey} key={workspace.workspaceKey}
workspace={workspace} workspace={workspace}
@@ -119,6 +127,15 @@ export function SidebarStatusWorkspaceList({
onWorkspacePress={onWorkspacePress} onWorkspacePress={onWorkspacePress}
/> />
))} ))}
{canTogglePinnedWorkspaces ? (
<SidebarGroupToggleRow
expanded={pinnedWorkspacesExpanded}
onPress={togglePinnedWorkspacesExpanded}
testID="sidebar-pinned-show-more"
/>
) : null}
</>
)}
</View> </View>
) : null} ) : null}
{listHeaderComponent} {listHeaderComponent}
@@ -188,38 +205,92 @@ function StatusGroupList({
return ( return (
<> <>
{groups.map((group) => ( {groups.map((group) => (
<View key={group.bucket} style={styles.statusGroupBlock}> <StatusGroupRows
<StatusGroupHeader group={group} collapsed={collapsedStatusGroupKeys.has(group.bucket)} /> key={group.bucket}
{!collapsedStatusGroupKeys.has(group.bucket) ? ( group={group}
<View collapsed={collapsedStatusGroupKeys.has(group.bucket)}
style={styles.statusWorkspaceListContainer} projectNamesByKey={projectNamesByKey}
testID={`sidebar-status-group-rows-${group.bucket}`} shortcutIndex={shortcutIndex}
> showShortcutBadges={showShortcutBadges}
{group.rows.map((workspace) => ( onWorkspacePress={onWorkspacePress}
<StatusWorkspaceRow hostLabelByServerId={hostLabelByServerId}
key={workspace.workspaceKey} showHostLabels={showHostLabels}
workspace={workspace} supportsPinningByServerId={supportsPinningByServerId}
subtitle={buildStatusRowSubtitle({ onToggleWorkspacePin={onToggleWorkspacePin}
projectName: projectNamesByKey.get(workspace.projectKey) ?? "", />
hostLabel: showHostLabels
? (hostLabelByServerId.get(workspace.serverId) ?? workspace.serverId)
: null,
})}
shortcutNumber={shortcutIndex.get(workspace.workspaceKey) ?? null}
showShortcutBadge={showShortcutBadges}
canPin={supportsPinningByServerId.get(workspace.serverId) === true}
onToggleWorkspacePin={onToggleWorkspacePin}
onWorkspacePress={onWorkspacePress}
/>
))}
</View>
) : null}
</View>
))} ))}
</> </>
); );
} }
function StatusGroupRows({
group,
collapsed,
projectNamesByKey,
shortcutIndex,
showShortcutBadges,
onWorkspacePress,
hostLabelByServerId,
showHostLabels,
supportsPinningByServerId,
onToggleWorkspacePin,
}: {
group: StatusGroup;
collapsed: boolean;
projectNamesByKey: Map<string, string>;
shortcutIndex: Map<string, number>;
showShortcutBadges: boolean;
onWorkspacePress?: () => void;
hostLabelByServerId: ReadonlyMap<string, string>;
showHostLabels: boolean;
supportsPinningByServerId: ReadonlyMap<string, boolean>;
onToggleWorkspacePin: ToggleSidebarWorkspacePin;
}) {
const {
visibleItems: visibleWorkspaces,
expanded: workspacesExpanded,
canToggle: canToggleWorkspaces,
toggleExpanded: toggleWorkspacesExpanded,
} = useLimitedSidebarGroup(group.rows);
return (
<View style={styles.statusGroupBlock}>
<StatusGroupHeader group={group} collapsed={collapsed} />
{!collapsed ? (
<View
style={styles.statusWorkspaceListContainer}
testID={`sidebar-status-group-rows-${group.bucket}`}
>
{visibleWorkspaces.map((workspace) => (
<StatusWorkspaceRow
key={workspace.workspaceKey}
workspace={workspace}
subtitle={buildStatusRowSubtitle({
projectName: projectNamesByKey.get(workspace.projectKey) ?? "",
hostLabel: showHostLabels
? (hostLabelByServerId.get(workspace.serverId) ?? workspace.serverId)
: null,
})}
shortcutNumber={shortcutIndex.get(workspace.workspaceKey) ?? null}
showShortcutBadge={showShortcutBadges}
canPin={supportsPinningByServerId.get(workspace.serverId) === true}
onToggleWorkspacePin={onToggleWorkspacePin}
onWorkspacePress={onWorkspacePress}
/>
))}
{canToggleWorkspaces ? (
<SidebarGroupToggleRow
expanded={workspacesExpanded}
onPress={toggleWorkspacesExpanded}
testID={`sidebar-status-show-more-${group.bucket}`}
/>
) : null}
</View>
) : null}
</View>
);
}
// Status mode breaks the project grouping, so the row needs the project name to stay // Status mode breaks the project grouping, so the row needs the project name to stay
// legible; the host is appended after a middle dot once labels are active. // legible; the host is appended after a middle dot once labels are active.
function buildStatusRowSubtitle({ function buildStatusRowSubtitle({
@@ -0,0 +1,15 @@
import { useCallback, useMemo, useState } from "react";
const INITIAL_VISIBLE_ITEMS = 20;
export function useLimitedSidebarGroup<T>(items: readonly T[]) {
const [expanded, setExpanded] = useState(false);
const visibleItems = useMemo(
() => (expanded ? items.slice() : items.slice(0, INITIAL_VISIBLE_ITEMS)),
[expanded, items],
);
const canToggle = items.length > INITIAL_VISIBLE_ITEMS;
const toggleExpanded = useCallback(() => setExpanded((current) => !current), []);
return { visibleItems, expanded, canToggle, toggleExpanded };
}
@@ -471,7 +471,7 @@ describe("computeSidebarOrderUpdates", () => {
expect(updates).toEqual({ projectOrder: null, workspaceOrders: [] }); expect(updates).toEqual({ projectOrder: null, workspaceOrders: [] });
}); });
it("appends unseen projects and workspaces to the persisted orders", () => { it("appends unseen projects while putting unseen workspaces before the saved order", () => {
const projects = [ const projects = [
sidebarProject({ projectKey: "project-a", workspaceKeys: ["ws-1", "ws-2"] }), sidebarProject({ projectKey: "project-a", workspaceKeys: ["ws-1", "ws-2"] }),
sidebarProject({ projectKey: "project-b", workspaceKeys: ["ws-3"] }), sidebarProject({ projectKey: "project-b", workspaceKeys: ["ws-3"] }),
@@ -485,11 +485,33 @@ describe("computeSidebarOrderUpdates", () => {
expect(updates.projectOrder).toEqual(["project-a", "project-b"]); expect(updates.projectOrder).toEqual(["project-a", "project-b"]);
expect(updates.workspaceOrders).toEqual([ expect(updates.workspaceOrders).toEqual([
{ projectKey: "project-a", order: ["srv:ws-1", "srv:ws-2"] }, { projectKey: "project-a", order: ["srv:ws-2", "srv:ws-1"] },
{ projectKey: "project-b", order: ["srv:ws-3"] }, { projectKey: "project-b", order: ["srv:ws-3"] },
]); ]);
}); });
it("preserves the saved workspace order behind multiple newly discovered workspaces", () => {
const projects = [
sidebarProject({
projectKey: "project-a",
workspaceKeys: ["newest", "newer", "old-a", "old-b"],
}),
];
const updates = computeSidebarOrderUpdates({
projects,
persistedProjectOrder: ["project-a"],
getWorkspaceOrder: () => ["srv:old-b", "srv:old-a"],
});
expect(updates.workspaceOrders).toEqual([
{
projectKey: "project-a",
order: ["srv:newest", "srv:newer", "srv:old-b", "srv:old-a"],
},
]);
});
it("returns no project-order update when persisted order already covers visible keys", () => { it("returns no project-order update when persisted order already covers visible keys", () => {
const projects = [ const projects = [
sidebarProject({ projectKey: "project-a", workspaceKeys: ["ws-1"] }), sidebarProject({ projectKey: "project-a", workspaceKeys: ["ws-1"] }),
@@ -477,6 +477,23 @@ export function appendMissingOrderKeys(input: {
return [...input.currentOrder, ...missingKeys]; return [...input.currentOrder, ...missingKeys];
} }
export function prependMissingOrderKeys(input: {
currentOrder: string[];
visibleKeys: string[];
}): string[] {
if (input.visibleKeys.length === 0) {
return input.currentOrder;
}
const existingKeys = new Set(input.currentOrder);
const missingKeys = input.visibleKeys.filter((key) => !existingKeys.has(key));
if (missingKeys.length === 0) {
return input.currentOrder;
}
return [...missingKeys, ...input.currentOrder];
}
export interface SidebarOrderUpdates { export interface SidebarOrderUpdates {
projectOrder: string[] | null; projectOrder: string[] | null;
workspaceOrders: Array<{ projectKey: string; order: string[] }>; workspaceOrders: Array<{ projectKey: string; order: string[] }>;
@@ -500,7 +517,7 @@ export function computeSidebarOrderUpdates(input: {
const workspaceOrders: Array<{ projectKey: string; order: string[] }> = []; const workspaceOrders: Array<{ projectKey: string; order: string[] }> = [];
for (const project of input.projects) { for (const project of input.projects) {
const persistedWorkspaceOrder = input.getWorkspaceOrder(project.projectKey); const persistedWorkspaceOrder = input.getWorkspaceOrder(project.projectKey);
const nextWorkspaceOrder = appendMissingOrderKeys({ const nextWorkspaceOrder = prependMissingOrderKeys({
currentOrder: persistedWorkspaceOrder, currentOrder: persistedWorkspaceOrder,
visibleKeys: project.workspaces.map((workspace) => workspace.workspaceKey), visibleKeys: project.workspaces.map((workspace) => workspace.workspaceKey),
}); });
+2
View File
@@ -926,6 +926,8 @@ export const ar: TranslationResources = {
actions: { actions: {
menu: "إجراءات Workspace", menu: "إجراءات Workspace",
newWorkspace: "مساحة عمل جديدة", newWorkspace: "مساحة عمل جديدة",
showMore: "عرض المزيد",
showLess: "عرض أقل",
createWorkspaceFor: "قم بإنشاء مساحة عمل جديدة لـ{{projectName}}", createWorkspaceFor: "قم بإنشاء مساحة عمل جديدة لـ{{projectName}}",
copyPath: "نسخ المسار", copyPath: "نسخ المسار",
copyBranchName: "انسخ اسم الفرع", copyBranchName: "انسخ اسم الفرع",
+2
View File
@@ -936,6 +936,8 @@ export const en = {
actions: { actions: {
menu: "Workspace actions", menu: "Workspace actions",
newWorkspace: "New workspace", newWorkspace: "New workspace",
showMore: "Show more",
showLess: "Show less",
createWorkspaceFor: "Create a new workspace for {{projectName}}", createWorkspaceFor: "Create a new workspace for {{projectName}}",
copyPath: "Copy path", copyPath: "Copy path",
copyBranchName: "Copy branch name", copyBranchName: "Copy branch name",
+2
View File
@@ -957,6 +957,8 @@ export const es: TranslationResources = {
actions: { actions: {
menu: "AccionesWorkspace", menu: "AccionesWorkspace",
newWorkspace: "Nuevo espacio de trabajo", newWorkspace: "Nuevo espacio de trabajo",
showMore: "Mostrar más",
showLess: "Mostrar menos",
createWorkspaceFor: "Crea un nuevo espacio de trabajo para{{projectName}}", createWorkspaceFor: "Crea un nuevo espacio de trabajo para{{projectName}}",
copyPath: "Copiar ruta", copyPath: "Copiar ruta",
copyBranchName: "Copiar nombre de sucursal", copyBranchName: "Copiar nombre de sucursal",
+2
View File
@@ -956,6 +956,8 @@ export const fr: TranslationResources = {
actions: { actions: {
menu: "ActionsWorkspace", menu: "ActionsWorkspace",
newWorkspace: "Nouvel espace de travail", newWorkspace: "Nouvel espace de travail",
showMore: "Afficher plus",
showLess: "Afficher moins",
createWorkspaceFor: "Créer un nouvel espace de travail pour{{projectName}}", createWorkspaceFor: "Créer un nouvel espace de travail pour{{projectName}}",
copyPath: "Copier le chemin", copyPath: "Copier le chemin",
copyBranchName: "Copier le nom de la branche", copyBranchName: "Copier le nom de la branche",
+2
View File
@@ -937,6 +937,8 @@ export const ja: TranslationResources = {
actions: { actions: {
menu: "ワークスペースアクション", menu: "ワークスペースアクション",
newWorkspace: "新しいワークスペース", newWorkspace: "新しいワークスペース",
showMore: "さらに表示",
showLess: "表示を減らす",
createWorkspaceFor: "{{projectName}}の新しいワークスペースを作成", createWorkspaceFor: "{{projectName}}の新しいワークスペースを作成",
copyPath: "パスをコピー", copyPath: "パスをコピー",
copyBranchName: "ブランチ名をコピー", copyBranchName: "ブランチ名をコピー",
+2
View File
@@ -948,6 +948,8 @@ export const ptBR: TranslationResources = {
actions: { actions: {
menu: "Ações do workspace", menu: "Ações do workspace",
newWorkspace: "Novo workspace", newWorkspace: "Novo workspace",
showMore: "Mostrar mais",
showLess: "Mostrar menos",
createWorkspaceFor: "Criar um novo workspace para {{projectName}}", createWorkspaceFor: "Criar um novo workspace para {{projectName}}",
copyPath: "Copiar caminho", copyPath: "Copiar caminho",
copyBranchName: "Copiar nome da branch", copyBranchName: "Copiar nome da branch",
+2
View File
@@ -948,6 +948,8 @@ export const ru: TranslationResources = {
actions: { actions: {
menu: "Действия Workspace", menu: "Действия Workspace",
newWorkspace: "Новое рабочее пространство", newWorkspace: "Новое рабочее пространство",
showMore: "Показать ещё",
showLess: "Показать меньше",
createWorkspaceFor: "Создайте новое рабочее пространство для{{projectName}}.", createWorkspaceFor: "Создайте новое рабочее пространство для{{projectName}}.",
copyPath: "Копировать путь", copyPath: "Копировать путь",
copyBranchName: "Скопировать название ветки", copyBranchName: "Скопировать название ветки",
+2
View File
@@ -916,6 +916,8 @@ export const zhCN: TranslationResources = {
actions: { actions: {
menu: "Workspace 操作", menu: "Workspace 操作",
newWorkspace: "新建 workspace", newWorkspace: "新建 workspace",
showMore: "显示更多",
showLess: "收起",
createWorkspaceFor: "为 {{projectName}} 新建 workspace", createWorkspaceFor: "为 {{projectName}} 新建 workspace",
copyPath: "复制路径", copyPath: "复制路径",
copyBranchName: "复制分支名称", copyBranchName: "复制分支名称",