Skip to content

Commit 791bed3

Browse files
Maigeclaude
authored andcommitted
Auto-translate, proxy-aware network, update check, uninstall cleanup, zh installer
Renderer / plugin: - Auto-translate new messages only (per-channel baseline; never retroactive), with built-in skip-same-language (CJK/Cyrillic script detection in shared/language.js). - Move all toggles into the settings page; remove the floating ⚙ menu entirely. - Persistent translation cache (loads on start, debounced save, size-capped). - Single-pass DOM maintenance: query visible messages once per tick and share. Main process: - Route provider/update requests through Electron net.fetch so they honor the system/Discord proxy (fixes "TypeError: fetch failed" for proxied users); fall back to global fetch. Clear Chinese network-failure messages. - 30s translate timeout with a friendly auto-cancel message. - GitHub release update check + in-app notice with a download link. Uninstall: - Wipe the stored config (settings.json, api-keys.json, cache.json) on uninstall across the JS path, the Go installer, and the PowerShell uninstaller; tests use a temp config dir so they never touch real user data. Windows installer: - Chinese UI with Microsoft YaHei font; larger type, separators, blurple primary button. Co-Authored-By: Claude Opus 4.8 <[email protected]>
1 parent 8cd203a commit 791bed3

16 files changed

Lines changed: 1194 additions & 84 deletions

installer/internal/core/core.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ var (
2828
vanillaRe = regexp.MustCompile(`^\s*module\.exports\s*=\s*require\(["']\./core\.asar["']\);\s*$`)
2929
)
3030

31-
// DiscordBaseDir returns the platform Discord user-data directory.
31+
// DiscordBaseDir returns the platform Discord modules directory (Local on Windows).
3232
func DiscordBaseDir() string {
3333
if v := os.Getenv("LOCALAPPDATA"); v != "" {
3434
return filepath.Join(v, "Discord")
@@ -37,6 +37,20 @@ func DiscordBaseDir() string {
3737
return filepath.Join(home, "AppData", "Local", "Discord")
3838
}
3939

40+
// ConfigStoreDir returns the plugin's config directory (settings.json, api-keys.json,
41+
// cache.json). It lives under Electron's userData dir (Roaming on Windows), which is
42+
// NOT the same as the Discord modules dir (Local) used for patching.
43+
func ConfigStoreDir() string {
44+
if v := os.Getenv("BABEL_CONFIG_DIR"); v != "" {
45+
return v
46+
}
47+
if v := os.Getenv("APPDATA"); v != "" {
48+
return filepath.Join(v, "discord", PayloadDirName)
49+
}
50+
home, _ := os.UserHomeDir()
51+
return filepath.Join(home, "AppData", "Roaming", "discord", PayloadDirName)
52+
}
53+
4054
func fileExists(p string) bool {
4155
_, err := os.Stat(p)
4256
return err == nil
@@ -231,5 +245,13 @@ func Uninstall(baseDir string) (string, error) {
231245
}
232246
}
233247
}
248+
249+
// Wipe the user's stored config (settings, API keys, cache) so nothing is left behind.
250+
if store := ConfigStoreDir(); fileExists(store) {
251+
if err := os.RemoveAll(store); err == nil {
252+
changed++
253+
}
254+
}
255+
234256
return fmt.Sprintf("Removed Babel from %d desktop core item(s).", changed), nil
235257
}

installer/internal/core/core_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ func TestResolveCoreDirPicksNewestVersion(t *testing.T) {
5454

5555
func TestInstallThenUninstallRoundTrips(t *testing.T) {
5656
base := fakeDiscord(t, "1.0.0")
57+
// Redirect the config store to a temp dir so the test never deletes the real
58+
// user config, and so we can assert uninstall wipes it.
59+
configStore := filepath.Join(t.TempDir(), "babel-store")
60+
t.Setenv("BABEL_CONFIG_DIR", configStore)
61+
if err := os.MkdirAll(configStore, 0o755); err != nil {
62+
t.Fatal(err)
63+
}
64+
write(t, filepath.Join(configStore, "api-keys.json"), "{}")
65+
5766
core, _ := ResolveCoreDir(base)
5867
indexPath := filepath.Join(core, "index.js")
5968

@@ -88,6 +97,9 @@ func TestInstallThenUninstallRoundTrips(t *testing.T) {
8897
if _, err := os.Stat(filepath.Join(core, PayloadDirName)); err == nil {
8998
t.Fatal("payload not removed")
9099
}
100+
if _, err := os.Stat(configStore); err == nil {
101+
t.Fatal("config store (api keys / settings) not removed on uninstall")
102+
}
91103
}
92104

93105
func TestInstallRefusesUnknownIndex(t *testing.T) {

installer/main.go

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
// the Vencord installer approach. The plugin payload is embedded into the exe, so
33
// it is a single self-contained file with no Node.js requirement.
44
//
5-
// The GUI text is English on purpose: Dear ImGui's default font has no CJK glyphs.
5+
// The UI is Chinese; we load Microsoft YaHei (msyh.ttc, shipped with Windows) so
6+
// Dear ImGui can render CJK glyphs — its default font cannot.
67
package main
78

89
import (
910
"embed"
11+
"image/color"
1012
"io/fs"
1113

1214
"github.com/AllenDang/giu"
@@ -17,8 +19,8 @@ import (
1719
//go:embed all:payload
1820
var payloadFS embed.FS
1921

20-
var status = "Click Install to add the Babel plugin to Discord.\n" +
21-
"After installing or removing, fully quit Discord (tray icon > Quit Discord) and reopen it."
22+
var status = "点击「安装」即可把 Babel 翻译插件装进 Discord\n" +
23+
"安装或卸载后,请彻底退出 Discord(托盘图标 > 退出 Discord)再重新打开。"
2224

2325
func payloadRoot() fs.FS {
2426
sub, err := fs.Sub(payloadFS, "payload")
@@ -31,38 +33,60 @@ func payloadRoot() fs.FS {
3133
func runInstall() {
3234
msg, err := core.Install(core.DiscordBaseDir(), payloadRoot())
3335
if err != nil {
34-
status = "Install failed:\n" + err.Error()
36+
status = "安装失败:\n" + err.Error()
3537
} else {
36-
status = msg + "\n\nNow fully quit Discord (tray > Quit Discord) and reopen it."
38+
status = msg + "\n\n现在请彻底退出 Discord(托盘 > 退出 Discord)再重新打开。"
3739
}
3840
giu.Update()
3941
}
4042

4143
func runUninstall() {
4244
msg, err := core.Uninstall(core.DiscordBaseDir())
4345
if err != nil {
44-
status = "Uninstall failed:\n" + err.Error()
46+
status = "卸载失败:\n" + err.Error()
4547
} else {
46-
status = msg + "\n\nRestart Discord."
48+
status = msg + "\n\n请重启 Discord"
4749
}
4850
giu.Update()
4951
}
5052

53+
// Discord brand blurple, used for the primary (Install) button.
54+
var (
55+
blurple = color.RGBA{R: 0x58, G: 0x65, B: 0xF2, A: 0xff}
56+
blurpleHovered = color.RGBA{R: 0x4a, G: 0x55, B: 0xd0, A: 0xff}
57+
blurpleActive = color.RGBA{R: 0x40, G: 0x49, B: 0xb8, A: 0xff}
58+
)
59+
5160
func loop() {
5261
giu.SingleWindow().Layout(
53-
giu.Label("Babel · Discord Translator"),
54-
giu.Label("Install or remove the plugin. No Node.js required."),
5562
giu.Dummy(0, 8),
63+
giu.Label("Babel · Discord 翻译插件"),
64+
giu.Label("一键为 Discord 安装 / 卸载翻译插件,无需 Node.js。").Wrapped(true),
65+
giu.Dummy(0, 12),
66+
giu.Separator(),
67+
giu.Dummy(0, 16),
5668
giu.Row(
57-
giu.Button("Install").OnClick(runInstall).Size(170, 38),
58-
giu.Button("Uninstall").OnClick(runUninstall).Size(170, 38),
69+
giu.Style().
70+
SetColor(giu.StyleColorButton, blurple).
71+
SetColor(giu.StyleColorButtonHovered, blurpleHovered).
72+
SetColor(giu.StyleColorButtonActive, blurpleActive).
73+
To(
74+
giu.Button("安装").OnClick(runInstall).Size(190, 46),
75+
),
76+
giu.Button("卸载").OnClick(runUninstall).Size(190, 46),
5977
),
78+
giu.Dummy(0, 16),
79+
giu.Separator(),
6080
giu.Dummy(0, 10),
6181
giu.Label(status).Wrapped(true),
6282
)
6383
}
6484

6585
func main() {
66-
wnd := giu.NewMasterWindow("Babel Installer", 500, 340, giu.MasterWindowFlagsNotResizable)
86+
wnd := giu.NewMasterWindow("Babel 安装器", 500, 360, giu.MasterWindowFlagsNotResizable)
87+
// Microsoft YaHei ships with Windows and carries the CJK glyphs ImGui's default
88+
// font lacks. giu auto-registers the strings used below, so the needed glyphs
89+
// are rasterized; the larger size also makes the UI look cleaner.
90+
giu.Context.FontAtlas.SetDefaultFont("msyh.ttc", 18)
6791
wnd.Run(loop)
6892
}

scripts/desktop-core-action.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,46 @@ function uninstallDiscordMod(options = {}) {
6060
}
6161
}
6262

63+
// Also wipe the user's stored config (settings.json, api-keys.json, cache.json) so
64+
// uninstalling leaves nothing behind — no saved API keys, no preferences. Opt out
65+
// with { keepConfig: true }.
66+
let configRemoved = false;
67+
if (!options.keepConfig) {
68+
const storeDir = configStoreDir(options);
69+
if (fs.existsSync(storeDir)) {
70+
fs.rmSync(storeDir, { recursive: true, force: true });
71+
configRemoved = true;
72+
changed += 1;
73+
}
74+
}
75+
6376
return {
6477
coreDirs,
65-
changed
78+
changed,
79+
configRemoved
6680
};
6781
}
6882

83+
// The plugin stores its config under Electron's userData dir, which is NOT the same
84+
// as the Discord modules dir on Windows (Roaming vs Local), so resolve it separately.
85+
function discordUserDataDir(options = {}) {
86+
if (options.userDataDir) {
87+
return path.resolve(options.userDataDir);
88+
}
89+
if (process.platform === "win32") {
90+
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
91+
return path.join(appData, "discord");
92+
}
93+
if (process.platform === "darwin") {
94+
return path.join(os.homedir(), "Library", "Application Support", "discord");
95+
}
96+
return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "discord");
97+
}
98+
99+
function configStoreDir(options = {}) {
100+
return path.join(discordUserDataDir(options), PAYLOAD_DIR_NAME);
101+
}
102+
69103
function resolveDiscordCoreDir(options = {}) {
70104
const coreDirs = resolveAllDiscordCoreDirs(options);
71105
if (coreDirs.length === 0) {
@@ -283,5 +317,6 @@ module.exports = {
283317
installDiscordMod,
284318
uninstallDiscordMod,
285319
resolveAllDiscordCoreDirs,
286-
resolveDiscordCoreDir
320+
resolveDiscordCoreDir,
321+
configStoreDir
287322
};

scripts/windows/Uninstall-Babel.ps1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,12 @@ if (Test-Path $BaseDir) {
5454
}
5555
}
5656

57+
# Wipe stored config (settings, API keys, cache) so nothing is left behind. This
58+
# lives under Electron's userData dir (Roaming), not the modules dir (Local).
59+
$configStore = Join-Path (Join-Path $env:APPDATA "discord") $PayloadDirName
60+
if (Test-Path $configStore) {
61+
Remove-Item -Recurse -Force $configStore
62+
$script:changed++
63+
}
64+
5765
Write-Host "Removed Babel from $changed desktop core item(s). Restart Discord."

0 commit comments

Comments
 (0)