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:
@@ -0,0 +1,85 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { test } = require('node:test')
|
||||
const { readFileSync, mkdtempSync, writeFileSync, rmSync } = require('node:fs')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { join } = require('node:path')
|
||||
const { spawn } = require('node:child_process')
|
||||
const vm = require('node:vm')
|
||||
const ts = require('typescript')
|
||||
|
||||
const transpile = (source) => ts.transpileModule(source, {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
|
||||
}).outputText
|
||||
const helpers = { exports: {} }
|
||||
vm.runInNewContext(transpile(readFileSync(join(__dirname, '../src/main/remoteShell.ts'), 'utf8')), helpers)
|
||||
const { remoteShellCommand, remoteShellEnvironment } = helpers.exports
|
||||
const source = readFileSync(join(__dirname, '../src/main/index.ts'), 'utf8')
|
||||
const start = source.indexOf('async function runCommandOutput(')
|
||||
const end = source.indexOf('\nasync function runCommand(', start)
|
||||
const context = { spawn, process, setTimeout, clearTimeout, createModuleProgressParser: () => () => {} }
|
||||
vm.runInNewContext(transpile(source.slice(start, end)), context)
|
||||
const run = context.runCommandOutput
|
||||
|
||||
test('SSH data output excludes locale warnings even when command succeeds', async () => {
|
||||
const result = await run('/bin/bash', ['--noprofile', '--norc', '-c', 'printf %s "/tmp/module with spaces"'], undefined,
|
||||
{ LC_ALL: 'missing_TEST.UTF-8' }, 5000, 'stdout')
|
||||
assert.equal(result, '/tmp/module with spaces')
|
||||
})
|
||||
|
||||
test('failed commands retain stderr diagnostics and stdout', async () => {
|
||||
await assert.rejects(run('/bin/sh', ['-c', 'printf context; printf "permission denied" >&2; exit 7'], undefined, undefined, 5000, 'stdout'),
|
||||
error => error.message.includes('context') && error.message.includes('permission denied'))
|
||||
})
|
||||
|
||||
test('non-SSH combined output stays compatible and explicit log merging works', async () => {
|
||||
const command = 'printf output; printf diagnostic >&2'
|
||||
const combined = await run('/bin/sh', ['-c', command])
|
||||
assert.match(combined, /output/)
|
||||
assert.match(combined, /diagnostic/)
|
||||
const logs = await run('/bin/sh', ['-c', `{ ${command}; } 2>&1`], undefined, undefined, 5000, 'stdout')
|
||||
assert.match(logs, /diagnostic/)
|
||||
})
|
||||
|
||||
test('SSH environment neutralizes locale forwarding without losing other settings', () => {
|
||||
const original = { LANG: 'ko_KR.UTF-8', LC_ALL: 'en_US.UTF-8', LC_CTYPE: 'bad', LANGUAGE: 'ko:en', PATH: '/bin', TERM: 'xterm-256color' }
|
||||
const env = remoteShellEnvironment(original)
|
||||
assert.equal(env.LANG, 'C')
|
||||
assert.equal(env.LC_ALL, 'C')
|
||||
assert.equal(env.LC_CTYPE, undefined)
|
||||
assert.equal(env.LANGUAGE, undefined)
|
||||
assert.equal(env.PATH, '/bin')
|
||||
assert.equal(env.TERM, 'xterm-256color')
|
||||
assert.equal(original.LC_CTYPE, 'bad')
|
||||
})
|
||||
|
||||
test('remote wrapper preserves multiline commands, Unicode, quotes and exit status', async () => {
|
||||
const command = `cat <<'END'
|
||||
/tmp/한글 module's folder
|
||||
END`
|
||||
const result = await run('/bin/sh', ['-c', remoteShellCommand(command)], undefined, remoteShellEnvironment(process.env), 5000, 'stdout')
|
||||
assert.equal(result, "/tmp/한글 module's folder\n")
|
||||
await assert.rejects(run('/bin/sh', ['-c', remoteShellCommand('echo remote-failure >&2; exit 9')], undefined, undefined, 5000, 'stdout'), /remote-failure/)
|
||||
})
|
||||
|
||||
test('locale selection prefers C UTF-8, supports other UTF-8 locales and falls back to C', async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'lms-locale-test-'))
|
||||
try {
|
||||
for (const [locales, expected] of [['en_GB.utf8 C.utf8', 'C.utf8'], ['C en_GB.utf8', 'en_GB.utf8'], ['C POSIX', 'C']]) {
|
||||
writeFileSync(join(dir, 'locale'), `#!/bin/sh\nprintf '%s\\n' ${locales}\n`, { mode: 0o755 })
|
||||
const result = await run('/bin/sh', ['-c', remoteShellCommand('printf "%s|%s" "$LANG" "$LC_ALL"')], undefined,
|
||||
{ PATH: `${dir}:${process.env.PATH}`, LC_ALL: 'C' }, 5000, 'stdout')
|
||||
assert.equal(result, `${expected}|${expected}`)
|
||||
}
|
||||
} finally { rmSync(dir, { recursive: true, force: true }) }
|
||||
})
|
||||
|
||||
test('queued SSH, stats probes and remote PTY use normalization; container logs keep stderr', () => {
|
||||
const ssh = source.slice(source.indexOf('async function runSshOutput('), source.indexOf('async function readRemoteDockerStatsOutput('))
|
||||
assert.equal((ssh.match(/remoteShellCommand\(command\)/g) || []).length, 2)
|
||||
assert.equal((ssh.match(/'stdout'/g) || []).length, 2)
|
||||
const terminal = source.slice(source.indexOf("ipcMain.handle('remote-workspace:terminal:start'"), source.indexOf("ipcMain.handle('terminal-resize'"))
|
||||
assert.match(terminal, /remoteShellCommand\(mode === 'container'/)
|
||||
assert.match(terminal, /remoteShellEnvironment\(terminalEnvironment/)
|
||||
assert.match(source, /target \? remoteShellCommand\(init\)/)
|
||||
assert.match(source, /docker logs --timestamps --tail 300.*2>&1/)
|
||||
})
|
||||
Reference in New Issue
Block a user