feat: add Korean-English localization and light theme

Localize studio, curriculum, visualization and loading interfaces. Add persistent language and theme settings, refine module loading states, and normalize remote shell locales.
This commit is contained in:
2026-09-29 13:27:11 +09:00
parent 4125f71792
commit d44180db67
74 changed files with 2519 additions and 998 deletions
+19 -12
View File
@@ -3,6 +3,7 @@ import { checkDockerStatus } from './dockerStatus'
import { cameraRuntimeArtifacts } from './cameraRuntime'
import { remoteModuleFilesScript } from './remoteModuleFiles'
import { resolveContainerTerminal } from './containerTerminal'
import { remoteShellCommand, remoteShellEnvironment } from './remoteShell'
import { yahboomDevicePreflight, yahboomModuleCases, yahboomRuntimeImage, yahboomRuntimeArtifacts, yahboomSimulation } from './yahboomRuntime'
import { visualizationProgress, reportVisualizationProgress } from './visualizationProgress'
import { moduleProgress, reportModuleProgress, createModuleProgressParser } from './moduleProgress'
@@ -1723,10 +1724,11 @@ function handleModuleOperation(channel: string, handler: Parameters<typeof ipcMa
})
}
async function runCommandOutput(command: string, args: string[], cwd?: string, env?: NodeJS.ProcessEnv, timeoutMs = 120_000): Promise<string> {
async function runCommandOutput(command: string, args: string[], cwd?: string, env?: NodeJS.ProcessEnv, timeoutMs = 120_000, outputMode: 'combined' | 'stdout' = 'combined'): Promise<string> {
const readProgress = createModuleProgressParser()
return new Promise<string>((resolvePromise, rejectPromise) => {
let output = ''
let stdout = ''
let terminated = false
const child = spawn(command, args, {
cwd,
@@ -1756,6 +1758,7 @@ async function runCommandOutput(command: string, args: string[], cwd?: string, e
child.stdout.setEncoding('utf8')
child.stdout.on('data', (data: string) => {
stdout += data.toString()
output += data.toString()
readProgress(data.toString())
})
@@ -1769,7 +1772,7 @@ async function runCommandOutput(command: string, args: string[], cwd?: string, e
child.on('close', (code) => {
clearTimeout(timeout)
if (terminated) return
if (code === 0) resolvePromise(output)
if (code === 0) resolvePromise(outputMode === 'stdout' ? stdout : output)
else rejectPromise(new Error(output.trim() || `${command} ${args.join(' ')} failed with exit code ${code ?? 'unknown'}`))
})
})
@@ -2135,11 +2138,12 @@ async function runSshOutput(target: RemoteWorkspaceTarget, command: string, time
'-i',
normalized.identityFile,
`${normalized.user}@${normalized.host}`,
command
remoteShellCommand(command)
],
undefined,
undefined,
timeoutMs
remoteShellEnvironment(process.env),
timeoutMs,
'stdout'
))
}
@@ -2167,11 +2171,12 @@ async function runUnqueuedSshOutput(target: RemoteWorkspaceTarget, command: stri
'-i',
normalized.identityFile,
`${normalized.user}@${normalized.host}`,
command,
remoteShellCommand(command),
],
undefined,
undefined,
remoteShellEnvironment(process.env),
timeoutMs,
'stdout',
)
}
@@ -5704,7 +5709,7 @@ async function readRemoteWorkspaceModuleLogs(
if (!existsOutput.trim()) return { ok: true, logs: '', message: '생성된 컨테이너가 없습니다.' }
try {
const logs = await runSshOutput(target, `docker logs --timestamps --tail 300 ${quoteShellSingle(containerKey)}`, 10000)
const logs = await runSshOutput(target, `docker logs --timestamps --tail 300 ${quoteShellSingle(containerKey)} 2>&1`, 10000)
return { ok: true, logs }
} catch (error) {
return { ok: false, logs: '', message: error instanceof Error ? error.message : String(error) }
@@ -5980,7 +5985,8 @@ async function containerTerminalCommand(moduleRoot: string, moduleName: string |
const container = resolveContainerTerminal(output, moduleRoot)
const shell = buildWorkspaceShellCommand(moduleName, container.cwd, cols, rows, false)
const init = 'if [ -n "$ROS_DISTRO" ] && [ -f "/opt/ros/$ROS_DISTRO/setup.bash" ]; then source "/opt/ros/$ROS_DISTRO/setup.bash"; fi; if [ -f ./install/setup.bash ]; then source ./install/setup.bash; fi; exec ' + shell
return `exec docker exec -it -e TERM=xterm-256color -w ${quoteShellSingle(container.cwd)} ${quoteShellSingle(container.id)} /bin/bash --noprofile --norc -c ${quoteShellSingle(init)}`
const containerShell = target ? remoteShellCommand(init) : `/bin/bash --noprofile --norc -c ${quoteShellSingle(init)}`
return `exec docker exec -it -e TERM=xterm-256color -w ${quoteShellSingle(container.cwd)} ${quoteShellSingle(container.id)} ${containerShell}`
}
function registerTerminalHandlers(): void {
@@ -6079,14 +6085,15 @@ function registerTerminalHandlers(): void {
'-i',
target.identityFile,
`${target.user}@${target.host}`,
mode === 'container'
remoteShellCommand(mode === 'container'
? await containerTerminalCommand(resolved.moduleRoot, moduleName, safeCols, safeRows, target)
: buildWorkspaceShellCommand(moduleName, resolved.moduleRoot, safeCols, safeRows, true)
: buildWorkspaceShellCommand(moduleName, resolved.moduleRoot, safeCols, safeRows, true))
], {
name: 'xterm-256color',
cols: safeCols,
rows: safeRows,
env: terminalEnvironment(safeCols, safeRows)
env: Object.fromEntries(Object.entries(remoteShellEnvironment(terminalEnvironment(safeCols, safeRows)))
.filter((entry): entry is [string, string] => typeof entry[1] === 'string'))
})
attachTerminalProcess(event.sender, sessionId, terminalKey, proc)