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:
@@ -94,6 +94,8 @@ import type { StatusGroup } from "@/hooks/sidebar-status-view-model";
|
||||
import { SidebarWorkspaceMenu } from "@/components/sidebar/sidebar-workspace-menu";
|
||||
import { useLongPressDragInteraction } from "@/components/sidebar/use-long-press-drag-interaction";
|
||||
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 {
|
||||
SidebarWorkspaceRowFrame,
|
||||
SidebarWorkspaceRowContent,
|
||||
@@ -1632,6 +1634,12 @@ function ProjectBlock({
|
||||
supportsPinningByServerId: ReadonlyMap<string, boolean>;
|
||||
onToggleWorkspacePin: ToggleSidebarWorkspacePin;
|
||||
}) {
|
||||
const {
|
||||
visibleItems: visibleWorkspaces,
|
||||
expanded: workspacesExpanded,
|
||||
canToggle: canToggleWorkspaces,
|
||||
toggleExpanded: toggleWorkspacesExpanded,
|
||||
} = useLimitedSidebarGroup(project.workspaces);
|
||||
const rowModel = useMemo(
|
||||
() =>
|
||||
buildSidebarProjectRowModel({
|
||||
@@ -1784,19 +1792,28 @@ function ProjectBlock({
|
||||
if (!collapsed) {
|
||||
if (project.workspaces.length > 0) {
|
||||
projectChildren = (
|
||||
<DraggableList
|
||||
testID={`sidebar-workspace-list-${project.projectKey}`}
|
||||
data={project.workspaces}
|
||||
keyExtractor={workspaceKeyExtractor}
|
||||
renderItem={renderWorkspace}
|
||||
onDragEnd={handleWorkspaceDragEnd}
|
||||
extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)}
|
||||
scrollEnabled={false}
|
||||
useDragHandle
|
||||
nestable={useNestable}
|
||||
simultaneousGestureRef={parentGestureRef}
|
||||
containerStyle={styles.workspaceListContainer}
|
||||
/>
|
||||
<>
|
||||
<DraggableList
|
||||
testID={`sidebar-workspace-list-${project.projectKey}`}
|
||||
data={visibleWorkspaces}
|
||||
keyExtractor={workspaceKeyExtractor}
|
||||
renderItem={renderWorkspace}
|
||||
onDragEnd={handleWorkspaceDragEnd}
|
||||
extraData={activeWorkspaceSelectionKey(activeWorkspaceSelection)}
|
||||
scrollEnabled={false}
|
||||
useDragHandle
|
||||
nestable={useNestable}
|
||||
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") {
|
||||
projectChildren = (
|
||||
@@ -2073,6 +2090,12 @@ function ProjectModeList({
|
||||
const selectionEnabled = isWorkspaceRoute;
|
||||
const activeWorkspaceSelection = useActiveWorkspaceSelection();
|
||||
const { pinnedChats, unpinnedProjects } = pinnedGroups;
|
||||
const {
|
||||
visibleItems: visiblePinnedChats,
|
||||
expanded: pinnedChatsExpanded,
|
||||
canToggle: canTogglePinnedChats,
|
||||
toggleExpanded: togglePinnedChatsExpanded,
|
||||
} = useLimitedSidebarGroup(pinnedChats);
|
||||
const projectIconTargets = useMemo(
|
||||
() =>
|
||||
projects.flatMap((project) => {
|
||||
@@ -2330,7 +2353,18 @@ function ProjectModeList({
|
||||
{pinnedChats.length > 0 ? (
|
||||
<View style={styles.pinnedSection} testID="sidebar-pinned-section">
|
||||
<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>
|
||||
) : 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 { SidebarWorkspaceMenu } from "@/components/sidebar/sidebar-workspace-menu";
|
||||
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";
|
||||
|
||||
// Themed icon wrappers
|
||||
@@ -93,6 +95,12 @@ export function SidebarStatusWorkspaceList({
|
||||
const togglePinnedCollapsed = useSidebarCollapsedSectionsStore(
|
||||
(state) => state.togglePinnedCollapsed,
|
||||
);
|
||||
const {
|
||||
visibleItems: visiblePinnedWorkspaces,
|
||||
expanded: pinnedWorkspacesExpanded,
|
||||
canToggle: canTogglePinnedWorkspaces,
|
||||
toggleExpanded: togglePinnedWorkspacesExpanded,
|
||||
} = useLimitedSidebarGroup(pinnedWorkspaces);
|
||||
|
||||
const statusShortcutIndex = showShortcutBadges ? shortcutIndexByWorkspaceKey : new Map();
|
||||
const content = (
|
||||
@@ -100,9 +108,9 @@ export function SidebarStatusWorkspaceList({
|
||||
{pinnedWorkspaces.length > 0 ? (
|
||||
<View style={styles.pinnedSection} testID="sidebar-pinned-section">
|
||||
<PinnedSectionHeader collapsed={pinnedCollapsed} onToggle={togglePinnedCollapsed} />
|
||||
{pinnedCollapsed
|
||||
? null
|
||||
: pinnedWorkspaces.map((workspace) => (
|
||||
{pinnedCollapsed ? null : (
|
||||
<>
|
||||
{visiblePinnedWorkspaces.map((workspace) => (
|
||||
<StatusWorkspaceRow
|
||||
key={workspace.workspaceKey}
|
||||
workspace={workspace}
|
||||
@@ -119,6 +127,15 @@ export function SidebarStatusWorkspaceList({
|
||||
onWorkspacePress={onWorkspacePress}
|
||||
/>
|
||||
))}
|
||||
{canTogglePinnedWorkspaces ? (
|
||||
<SidebarGroupToggleRow
|
||||
expanded={pinnedWorkspacesExpanded}
|
||||
onPress={togglePinnedWorkspacesExpanded}
|
||||
testID="sidebar-pinned-show-more"
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
) : null}
|
||||
{listHeaderComponent}
|
||||
@@ -188,38 +205,92 @@ function StatusGroupList({
|
||||
return (
|
||||
<>
|
||||
{groups.map((group) => (
|
||||
<View key={group.bucket} style={styles.statusGroupBlock}>
|
||||
<StatusGroupHeader group={group} collapsed={collapsedStatusGroupKeys.has(group.bucket)} />
|
||||
{!collapsedStatusGroupKeys.has(group.bucket) ? (
|
||||
<View
|
||||
style={styles.statusWorkspaceListContainer}
|
||||
testID={`sidebar-status-group-rows-${group.bucket}`}
|
||||
>
|
||||
{group.rows.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}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
<StatusGroupRows
|
||||
key={group.bucket}
|
||||
group={group}
|
||||
collapsed={collapsedStatusGroupKeys.has(group.bucket)}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
shortcutIndex={shortcutIndex}
|
||||
showShortcutBadges={showShortcutBadges}
|
||||
onWorkspacePress={onWorkspacePress}
|
||||
hostLabelByServerId={hostLabelByServerId}
|
||||
showHostLabels={showHostLabels}
|
||||
supportsPinningByServerId={supportsPinningByServerId}
|
||||
onToggleWorkspacePin={onToggleWorkspacePin}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
// legible; the host is appended after a middle dot once labels are active.
|
||||
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: [] });
|
||||
});
|
||||
|
||||
it("appends unseen projects and workspaces to the persisted orders", () => {
|
||||
it("appends unseen projects while putting unseen workspaces before the saved order", () => {
|
||||
const projects = [
|
||||
sidebarProject({ projectKey: "project-a", workspaceKeys: ["ws-1", "ws-2"] }),
|
||||
sidebarProject({ projectKey: "project-b", workspaceKeys: ["ws-3"] }),
|
||||
@@ -485,11 +485,33 @@ describe("computeSidebarOrderUpdates", () => {
|
||||
|
||||
expect(updates.projectOrder).toEqual(["project-a", "project-b"]);
|
||||
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"] },
|
||||
]);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const projects = [
|
||||
sidebarProject({ projectKey: "project-a", workspaceKeys: ["ws-1"] }),
|
||||
|
||||
@@ -477,6 +477,23 @@ export function appendMissingOrderKeys(input: {
|
||||
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 {
|
||||
projectOrder: string[] | null;
|
||||
workspaceOrders: Array<{ projectKey: string; order: string[] }>;
|
||||
@@ -500,7 +517,7 @@ export function computeSidebarOrderUpdates(input: {
|
||||
const workspaceOrders: Array<{ projectKey: string; order: string[] }> = [];
|
||||
for (const project of input.projects) {
|
||||
const persistedWorkspaceOrder = input.getWorkspaceOrder(project.projectKey);
|
||||
const nextWorkspaceOrder = appendMissingOrderKeys({
|
||||
const nextWorkspaceOrder = prependMissingOrderKeys({
|
||||
currentOrder: persistedWorkspaceOrder,
|
||||
visibleKeys: project.workspaces.map((workspace) => workspace.workspaceKey),
|
||||
});
|
||||
|
||||
@@ -926,6 +926,8 @@ export const ar: TranslationResources = {
|
||||
actions: {
|
||||
menu: "إجراءات Workspace",
|
||||
newWorkspace: "مساحة عمل جديدة",
|
||||
showMore: "عرض المزيد",
|
||||
showLess: "عرض أقل",
|
||||
createWorkspaceFor: "قم بإنشاء مساحة عمل جديدة لـ{{projectName}}",
|
||||
copyPath: "نسخ المسار",
|
||||
copyBranchName: "انسخ اسم الفرع",
|
||||
|
||||
@@ -936,6 +936,8 @@ export const en = {
|
||||
actions: {
|
||||
menu: "Workspace actions",
|
||||
newWorkspace: "New workspace",
|
||||
showMore: "Show more",
|
||||
showLess: "Show less",
|
||||
createWorkspaceFor: "Create a new workspace for {{projectName}}",
|
||||
copyPath: "Copy path",
|
||||
copyBranchName: "Copy branch name",
|
||||
|
||||
@@ -957,6 +957,8 @@ export const es: TranslationResources = {
|
||||
actions: {
|
||||
menu: "AccionesWorkspace",
|
||||
newWorkspace: "Nuevo espacio de trabajo",
|
||||
showMore: "Mostrar más",
|
||||
showLess: "Mostrar menos",
|
||||
createWorkspaceFor: "Crea un nuevo espacio de trabajo para{{projectName}}",
|
||||
copyPath: "Copiar ruta",
|
||||
copyBranchName: "Copiar nombre de sucursal",
|
||||
|
||||
@@ -956,6 +956,8 @@ export const fr: TranslationResources = {
|
||||
actions: {
|
||||
menu: "ActionsWorkspace",
|
||||
newWorkspace: "Nouvel espace de travail",
|
||||
showMore: "Afficher plus",
|
||||
showLess: "Afficher moins",
|
||||
createWorkspaceFor: "Créer un nouvel espace de travail pour{{projectName}}",
|
||||
copyPath: "Copier le chemin",
|
||||
copyBranchName: "Copier le nom de la branche",
|
||||
|
||||
@@ -937,6 +937,8 @@ export const ja: TranslationResources = {
|
||||
actions: {
|
||||
menu: "ワークスペースアクション",
|
||||
newWorkspace: "新しいワークスペース",
|
||||
showMore: "さらに表示",
|
||||
showLess: "表示を減らす",
|
||||
createWorkspaceFor: "{{projectName}}の新しいワークスペースを作成",
|
||||
copyPath: "パスをコピー",
|
||||
copyBranchName: "ブランチ名をコピー",
|
||||
|
||||
@@ -948,6 +948,8 @@ export const ptBR: TranslationResources = {
|
||||
actions: {
|
||||
menu: "Ações do workspace",
|
||||
newWorkspace: "Novo workspace",
|
||||
showMore: "Mostrar mais",
|
||||
showLess: "Mostrar menos",
|
||||
createWorkspaceFor: "Criar um novo workspace para {{projectName}}",
|
||||
copyPath: "Copiar caminho",
|
||||
copyBranchName: "Copiar nome da branch",
|
||||
|
||||
@@ -948,6 +948,8 @@ export const ru: TranslationResources = {
|
||||
actions: {
|
||||
menu: "Действия Workspace",
|
||||
newWorkspace: "Новое рабочее пространство",
|
||||
showMore: "Показать ещё",
|
||||
showLess: "Показать меньше",
|
||||
createWorkspaceFor: "Создайте новое рабочее пространство для{{projectName}}.",
|
||||
copyPath: "Копировать путь",
|
||||
copyBranchName: "Скопировать название ветки",
|
||||
|
||||
@@ -916,6 +916,8 @@ export const zhCN: TranslationResources = {
|
||||
actions: {
|
||||
menu: "Workspace 操作",
|
||||
newWorkspace: "新建 workspace",
|
||||
showMore: "显示更多",
|
||||
showLess: "收起",
|
||||
createWorkspaceFor: "为 {{projectName}} 新建 workspace",
|
||||
copyPath: "复制路径",
|
||||
copyBranchName: "复制分支名称",
|
||||
|
||||
Reference in New Issue
Block a user