terminal panel keyed (564 -> 553)
Tmux, Neovim and Claude Code are product names and stay English, wrapped in <Untranslated reason="product-name"> so the decision is visible where it is made rather than looking like a string somebody missed. Registry names still pending the AppRegistryEntry.name decision.
This commit is contained in:
@@ -2,6 +2,8 @@ import { TerminalSquare, Monitor, Columns2, PenLine, Sparkles, Circle, Loader2 }
|
|||||||
import { useWorkspace } from '../../components/Workspace';
|
import { useWorkspace } from '../../components/Workspace';
|
||||||
import { useGlobal } from 'hooks/useGlobal';
|
import { useGlobal } from 'hooks/useGlobal';
|
||||||
import type { TerminalConnectionState } from './Terminal';
|
import type { TerminalConnectionState } from './Terminal';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
import { Untranslated } from '@/components/Untranslated';
|
||||||
|
|
||||||
const ConnectionIndicator = ({ panelId }: { panelId: string }) => {
|
const ConnectionIndicator = ({ panelId }: { panelId: string }) => {
|
||||||
const [state] = useGlobal<TerminalConnectionState>(`terminal-conn-${panelId}`, 'disconnected');
|
const [state] = useGlobal<TerminalConnectionState>(`terminal-conn-${panelId}`, 'disconnected');
|
||||||
@@ -18,12 +20,13 @@ const ConnectionIndicator = ({ panelId }: { panelId: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const TerminalHeader = ({ panelId }: { panelId: string }) => {
|
export const TerminalHeader = ({ panelId }: { panelId: string }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { cwd } = useWorkspace();
|
const { cwd } = useWorkspace();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TerminalSquare className="h-3.5 w-3.5 shrink-0" />
|
<TerminalSquare className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Terminal</span>
|
<span className="text-xs font-medium shrink-0">{t('terminal.title')}</span>
|
||||||
<ConnectionIndicator panelId={panelId} />
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
@@ -31,11 +34,12 @@ export const TerminalHeader = ({ panelId }: { panelId: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const HostTerminalHeader = ({ panelId }: { panelId: string }) => {
|
export const HostTerminalHeader = ({ panelId }: { panelId: string }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { cwd } = useWorkspace();
|
const { cwd } = useWorkspace();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Monitor className="h-3.5 w-3.5 shrink-0" />
|
<Monitor className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Terminal</span>
|
<span className="text-xs font-medium shrink-0">{t('terminal.title')}</span>
|
||||||
<ConnectionIndicator panelId={panelId} />
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
@@ -47,7 +51,9 @@ export const TmuxHeader = ({ panelId }: { panelId: string }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Columns2 className="h-3.5 w-3.5 shrink-0" />
|
<Columns2 className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Tmux</span>
|
<span className="text-xs font-medium shrink-0">
|
||||||
|
<Untranslated reason="product-name">Tmux</Untranslated>
|
||||||
|
</span>
|
||||||
<ConnectionIndicator panelId={panelId} />
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
@@ -59,7 +65,9 @@ export const NvimHeader = ({ panelId }: { panelId: string }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PenLine className="h-3.5 w-3.5 shrink-0" />
|
<PenLine className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Neovim</span>
|
<span className="text-xs font-medium shrink-0">
|
||||||
|
<Untranslated reason="product-name">Neovim</Untranslated>
|
||||||
|
</span>
|
||||||
<ConnectionIndicator panelId={panelId} />
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
@@ -71,7 +79,9 @@ export const ClaudeCodeHeader = ({ panelId }: { panelId: string }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sparkles className="h-3.5 w-3.5 shrink-0" />
|
<Sparkles className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium shrink-0">Claude Code</span>
|
<span className="text-xs font-medium shrink-0">
|
||||||
|
<Untranslated reason="product-name">Claude Code</Untranslated>
|
||||||
|
</span>
|
||||||
<ConnectionIndicator panelId={panelId} />
|
<ConnectionIndicator panelId={panelId} />
|
||||||
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
<span className="text-[10px] font-mono truncate opacity-60">{cwd}</span>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
|||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { TerminalSquare, Trash2, RefreshCw } from 'lucide-react';
|
import { TerminalSquare, Trash2, RefreshCw } from 'lucide-react';
|
||||||
import { useClient } from 'hooks/useClient';
|
import { useClient } from 'hooks/useClient';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
|
||||||
// Every shell the pty sidecar is holding, whether or not a panel is pointing at it.
|
// Every shell the pty sidecar is holding, whether or not a panel is pointing at it.
|
||||||
//
|
//
|
||||||
@@ -44,6 +45,7 @@ const since = (ts: number): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RunningShells = () => {
|
export const RunningShells = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
@@ -69,12 +71,12 @@ export const RunningShells = () => {
|
|||||||
<div className="flex h-full w-full flex-col overflow-hidden text-sm">
|
<div className="flex h-full w-full flex-col overflow-hidden text-sm">
|
||||||
<div className="flex shrink-0 items-center gap-2 border-b border-white/10 px-3 py-2">
|
<div className="flex shrink-0 items-center gap-2 border-b border-white/10 px-3 py-2">
|
||||||
<TerminalSquare className="h-3.5 w-3.5 shrink-0" />
|
<TerminalSquare className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="text-xs font-medium">Running shells</span>
|
<span className="text-xs font-medium">{t('terminal.runningShells')}</span>
|
||||||
<span className="text-[10px] opacity-60">{sessions.length}</span>
|
<span className="text-[10px] opacity-60">{sessions.length}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
title="Refresh"
|
title={t('terminal.refresh')}
|
||||||
className="ml-auto cursor-pointer opacity-60 hover:opacity-100"
|
className="ml-auto cursor-pointer opacity-60 hover:opacity-100"
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-3.5 w-3.5" />
|
<RefreshCw className="h-3.5 w-3.5" />
|
||||||
@@ -82,9 +84,9 @@ export const RunningShells = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{isLoading && <p className="px-3 py-3 text-xs opacity-60">Loading…</p>}
|
{isLoading && <p className="px-3 py-3 text-xs opacity-60">{t('terminal.loading')}</p>}
|
||||||
{!isLoading && sessions.length === 0 && (
|
{!isLoading && sessions.length === 0 && (
|
||||||
<p className="px-3 py-3 text-xs opacity-60">No shells running.</p>
|
<p className="px-3 py-3 text-xs opacity-60">{t('terminal.noShells')}</p>
|
||||||
)}
|
)}
|
||||||
{sessions.map((s) => (
|
{sessions.map((s) => (
|
||||||
<div key={s.sessionId} className="flex items-center gap-2 border-b border-white/5 px-3 py-2">
|
<div key={s.sessionId} className="flex items-center gap-2 border-b border-white/5 px-3 py-2">
|
||||||
@@ -94,9 +96,7 @@ export const RunningShells = () => {
|
|||||||
<p className="truncate text-xs font-medium">
|
<p className="truncate text-xs font-medium">
|
||||||
{s.title || 'shell'}
|
{s.title || 'shell'}
|
||||||
{s.clients === 0 && (
|
{s.clients === 0 && (
|
||||||
<span className="ml-1.5 rounded bg-amber-500/15 px-1 py-px text-[10px] text-amber-400">
|
<span className="ml-1.5 rounded bg-amber-500/15 px-1 py-px text-[10px] text-amber-400">orphan</span>
|
||||||
orphan
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="truncate font-mono text-[10px] opacity-50">
|
<p className="truncate font-mono text-[10px] opacity-50">
|
||||||
@@ -109,7 +109,7 @@ export const RunningShells = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => kill(s.sessionId)}
|
onClick={() => kill(s.sessionId)}
|
||||||
title="Kill this shell"
|
title={t('terminal.kill')}
|
||||||
className="shrink-0 cursor-pointer p-1 opacity-60 hover:text-red-400 hover:opacity-100"
|
className="shrink-0 cursor-pointer p-1 opacity-60 hover:text-red-400 hover:opacity-100"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { SearchAddon } from '@xterm/addon-search';
|
|||||||
import '@xterm/xterm/css/xterm.css';
|
import '@xterm/xterm/css/xterm.css';
|
||||||
import { useMounted } from 'hooks/useMounted';
|
import { useMounted } from 'hooks/useMounted';
|
||||||
import { copyToClipboard } from 'helpers/clipboard';
|
import { copyToClipboard } from 'helpers/clipboard';
|
||||||
|
import { useTranslation } from '../../locales';
|
||||||
|
|
||||||
// Scrollback the browser keeps. The sidecar keeps its own replay buffer for re-attach; this is what you
|
// Scrollback the browser keeps. The sidecar keeps its own replay buffer for re-attach; this is what you
|
||||||
// can scroll back to within a live session, and 1000 (xterm's default) is about one long agent turn.
|
// can scroll back to within a live session, and 1000 (xterm's default) is about one long agent turn.
|
||||||
@@ -93,6 +94,7 @@ export const TerminalView = ({
|
|||||||
onTitleChange,
|
onTitleChange,
|
||||||
onBell,
|
onBell,
|
||||||
}: TerminalViewProps) => {
|
}: TerminalViewProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const termRef = useRef<XTerm | null>(null);
|
const termRef = useRef<XTerm | null>(null);
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
@@ -535,7 +537,7 @@ export const TerminalView = ({
|
|||||||
<input
|
<input
|
||||||
autoFocus
|
autoFocus
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
placeholder="Find"
|
placeholder={t('terminal.find')}
|
||||||
onChange={(ev) => {
|
onChange={(ev) => {
|
||||||
setSearchTerm(ev.target.value);
|
setSearchTerm(ev.target.value);
|
||||||
runSearch(ev.target.value, 'next');
|
runSearch(ev.target.value, 'next');
|
||||||
|
|||||||
@@ -23,5 +23,14 @@
|
|||||||
"calendar": "Calendar",
|
"calendar": "Calendar",
|
||||||
"contacts": "Contacts",
|
"contacts": "Contacts",
|
||||||
"readFailed": "Could not read {{name}}: {{reason}}"
|
"readFailed": "Could not read {{name}}: {{reason}}"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"title": "Terminal",
|
||||||
|
"find": "Find",
|
||||||
|
"runningShells": "Running shells",
|
||||||
|
"loading": "Loading…",
|
||||||
|
"noShells": "No shells running.",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"kill": "Kill this shell"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,5 +23,14 @@
|
|||||||
"calendar": "Calendário",
|
"calendar": "Calendário",
|
||||||
"contacts": "Contactos",
|
"contacts": "Contactos",
|
||||||
"readFailed": "Não foi possível ler {{name}}: {{reason}}"
|
"readFailed": "Não foi possível ler {{name}}: {{reason}}"
|
||||||
|
},
|
||||||
|
"terminal": {
|
||||||
|
"title": "Terminal",
|
||||||
|
"find": "Procurar",
|
||||||
|
"runningShells": "Shells em execução",
|
||||||
|
"loading": "A carregar…",
|
||||||
|
"noShells": "Nenhuma shell em execução.",
|
||||||
|
"refresh": "Atualizar",
|
||||||
|
"kill": "Terminar esta shell"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user