Skip to content

Commit 83d544f

Browse files
committed
Add inlang/paraglide-js localization
Remove the temporary directory after extracting buildkit Localize the extension popovers. Update package and fix tsconfig.json Expand development directory guide Move messages under localization Popovers and sidebar Update Chinese translations Accidentally lost the changes that @ym provided, brought them back File formatting pass Localized all components, hooks, providers, hooks Localize all pages except Settings Bump packages Settings Access page Settings local auth page Fix ref lint warning Settings Advanced page Fix UI lint warnings there were a bunch of ref and useEffect violations. Settings appearance page Settings general pages Settings hardware page Settings keyboard page Settings macros pages Settings mouse page Settings page Settings video page Settings network page Fix compilation issues Ran machine translate Use getLocale for date, relative time, and money formatting Fix eslint Delete unused messages Added setting to choose locale Merged in dev hotfix Fix update status rendering
1 parent 403141c commit 83d544f

File tree

126 files changed

+10853
-2128
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+10853
-2128
lines changed

.devcontainer/docker/devcontainer.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
}
99
},
1010
"mounts": [
11-
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached"
11+
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached",
12+
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
1213
],
1314
"onCreateCommand": ".devcontainer/install-deps.sh",
1415
"customizations": {
@@ -31,8 +32,11 @@
3132
// Frontend
3233
"esbenp.prettier-vscode",
3334
"dbaeumer.vscode-eslint",
34-
"bradlc.vscode-tailwindcss"
35+
"bradlc.vscode-tailwindcss",
36+
"codeandstuff.package-json-upgrade",
37+
// Localization
38+
"inlang.vs-code-extension"
3539
]
3640
}
3741
}
38-
}
42+
}

.vscode/extensions.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"recommendations": [
3+
// coding styles
4+
"chrislajoie.vscode-modelines",
5+
"editorconfig.editorconfig",
6+
// GitHub
7+
"GitHub.vscode-pull-request-github",
8+
"github.vscode-github-actions",
9+
// Golang
10+
"golang.go",
11+
// C / C++
12+
"ms-vscode.cpptools",
13+
"ms-vscode.cpptools-extension-pack",
14+
// CMake / Makefile
15+
"ms-vscode.makefile-tools",
16+
"ms-vscode.cmake-tools",
17+
// Frontend
18+
"esbenp.prettier-vscode",
19+
"dbaeumer.vscode-eslint",
20+
"bradlc.vscode-tailwindcss",
21+
"codeandstuff.package-json-upgrade",
22+
// Localization
23+
"inlang.vs-code-extension"
24+
]
25+
}

DEVELOPMENT.md

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -97,38 +97,42 @@ tail -f /var/log/jetkvm.log
9797

9898
```
9999
/kvm/
100-
├── main.go # App entry point
101-
├── config.go # Settings & configuration
102-
├── display.go # Device UI control
103-
├── web.go # API endpoints
104-
├── cmd/ # Command line main
105-
├── internal/ # Internal Go packages
106-
│ ├── confparser/ # Configuration file implementation
107-
│ ├── hidrpc/ # HIDRPC implementation for HID devices (keyboard, mouse, etc.)
108-
│ ├── logging/ # Logging implementation
109-
│ ├── mdns/ # mDNS implementation
110-
│ ├── native/ # CGO / Native code glue layer (on-device hardware)
111-
│ │ ├── cgo/ # C files for the native library (HDMI, Touchscreen, etc.)
112-
│ │ └── eez/ # EEZ Studio Project files (for Touchscreen)
113-
│ ├── network/ # Network implementation
114-
│ ├── timesync/ # Time sync/NTP implementation
115-
│ ├── tzdata/ # Timezone data and generation
116-
│ ├── udhcpc/ # DHCP implementation
117-
│ ├── usbgadget/ # USB gadget
118-
│ ├── utils/ # SSH handling
119-
│ └── websecure/ # TLS certificate management
120-
├── resource/ # netboot iso and other resources
121-
├── scripts/ # Bash shell scripts for building and deploying
122-
└── static/ # (react client build output)
123-
└── ui/ # React frontend
124-
├── public/ # UI website static images and fonts
125-
└── src/ # Client React UI
126-
├── assets/ # UI in-page images
127-
├── components/ # UI components
128-
├── hooks/ # Hooks (stores, RPC handling, virtual devices)
129-
├── keyboardLayouts/ # Keyboard layout definitions
130-
├── providers/ # Feature flags
131-
└── routes/ # Pages (login, settings, etc.)
100+
├── main.go # App entry point
101+
├── config.go # Settings & configuration
102+
├── display.go # Device UI control
103+
├── web.go # API endpoints
104+
├── cmd/ # Command line main
105+
├── internal/ # Internal Go packages
106+
│ ├── confparser/ # Configuration file implementation
107+
│ ├── hidrpc/ # HIDRPC implementation for HID devices (keyboard, mouse, etc.)
108+
│ ├── logging/ # Logging implementation
109+
│ ├── mdns/ # mDNS implementation
110+
│ ├── native/ # CGO / Native code glue layer (on-device hardware)
111+
│ │ ├── cgo/ # C files for the native library (HDMI, Touchscreen, etc.)
112+
│ │ └── eez/ # EEZ Studio Project files (for Touchscreen)
113+
│ ├── network/ # Network implementation
114+
│ ├── timesync/ # Time sync/NTP implementation
115+
│ ├── tzdata/ # Timezone data and generation
116+
│ ├── udhcpc/ # DHCP implementation
117+
│ ├── usbgadget/ # USB gadget
118+
│ ├── utils/ # SSH handling
119+
│ └── websecure/ # TLS certificate management
120+
├── resource/ # netboot iso and other resources
121+
├── scripts/ # Bash shell scripts for building and deploying
122+
└── static/ # (react client build output)
123+
└── ui/ # React frontend
124+
├── localization/ # Client UI localization (i18n)
125+
│ ├── jetKVM.UI.inlang/ # Settings for inlang
126+
│ └── messages/ # Messages localized
127+
├── public/ # UI website static images and fonts
128+
└── src/ # Client React UI
129+
├── assets/ # UI in-page images
130+
├── components/ # UI components
131+
├── hooks/ # Hooks (stores, RPC handling, virtual devices)
132+
├── keyboardLayouts/ # Keyboard layout definitions
133+
│ ├── paraglide/ # (localization compiled messages output)
134+
├── providers/ # Feature flags
135+
└── routes/ # Pages (login, settings, etc.)
132136
```
133137

134138
**Key files for beginners:**
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import json
4+
import os
5+
import re
6+
from datetime import datetime
7+
from pathlib import Path
8+
9+
def flatten_strings(obj, prefix=""):
10+
if isinstance(obj, dict):
11+
for k, v in obj.items():
12+
key = f"{prefix}.{k}" if prefix else k
13+
yield from flatten_strings(v, key)
14+
else:
15+
# only consider scalar strings for translation targets
16+
if isinstance(obj, str):
17+
yield prefix, obj
18+
19+
def normalize(s, ignore_case=False, trim=False, collapse_ws=False):
20+
if collapse_ws:
21+
s = re.sub(r"\s+", " ", s)
22+
if trim:
23+
s = s.strip()
24+
if ignore_case:
25+
s = s.lower()
26+
return s
27+
28+
def main():
29+
p = argparse.ArgumentParser(description="Find identical translation targets with different keys in en.json")
30+
p.add_argument("--en", default="../ui/localization/messages/en.json", help="path to en.json")
31+
p.add_argument("--out", default="../reports/duplicate_translations.json", help="output report path (JSON)")
32+
p.add_argument("--ignore-case", action="store_true", help="ignore case when comparing values")
33+
p.add_argument("--trim", action="store_true", help="trim surrounding whitespace before comparing")
34+
p.add_argument("--collapse-ws", action="store_true", help="collapse internal whitespace before comparing")
35+
args = p.parse_args()
36+
37+
en_path = Path(args.en)
38+
if not en_path.is_file():
39+
print(f"en.json not found: {en_path}")
40+
raise SystemExit(2)
41+
42+
with en_path.open(encoding="utf-8") as f:
43+
payload = json.load(f)
44+
45+
entries = list(flatten_strings(payload))
46+
total_keys = len(entries)
47+
48+
groups = {}
49+
original_values = {}
50+
for key, val in entries:
51+
norm = normalize(val, ignore_case=args.ignore_case, trim=args.trim, collapse_ws=args.collapse_ws)
52+
groups.setdefault(norm, []).append(key)
53+
# keep the first seen original for reporting
54+
original_values.setdefault(norm, val)
55+
56+
duplicates = []
57+
for norm, keys in groups.items():
58+
if len(keys) > 1:
59+
duplicates.append({
60+
"normalized_value": norm,
61+
"original_value": original_values.get(norm),
62+
"keys": sorted(keys),
63+
"count": len(keys)
64+
})
65+
66+
report = {
67+
"generated_at": datetime.utcnow().isoformat() + "Z",
68+
"en_json": str(en_path),
69+
"total_string_keys": total_keys,
70+
"duplicate_groups": sorted(duplicates, key=lambda d: (-d["count"], d["normalized_value"])),
71+
"duplicate_count": len(duplicates)
72+
}
73+
74+
out_path = Path(args.out)
75+
out_path.parent.mkdir(parents=True, exist_ok=True)
76+
with out_path.open("w", encoding="utf-8") as f:
77+
json.dump(report, f, indent=2, ensure_ascii=False)
78+
79+
print(f"Wrote {out_path} — total keys: {total_keys}, duplicate groups: {len(duplicates)}")
80+
81+
if __name__ == "__main__":
82+
main()

scripts/find_unused_messages.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import json
4+
import os
5+
import re
6+
from datetime import datetime
7+
from pathlib import Path
8+
9+
def flatten(d, prefix=""):
10+
for k, v in d.items():
11+
key = f"{prefix}.{k}" if prefix else k
12+
if isinstance(v, dict):
13+
yield from flatten(v, key)
14+
else:
15+
yield key
16+
17+
def gather_files(src_dir, exts=(".ts", ".tsx", ".js", ".jsx", ".html", ".vue", ".json")):
18+
for root, _, files in os.walk(src_dir):
19+
parts = root.split(os.sep)
20+
if "node_modules" in parts or ".git" in parts:
21+
continue
22+
for fn in files:
23+
if fn.endswith(exts):
24+
yield Path(root) / fn
25+
26+
def find_usages(keys, files):
27+
usages = {k: [] for k in keys}
28+
# Precompile patterns for speed
29+
patterns = {k: re.compile(r"\bm\." + re.escape(k) + r"\s*\(") for k in keys}
30+
for file in files:
31+
try:
32+
text = file.read_text(encoding="utf-8")
33+
except Exception:
34+
continue
35+
lines = text.splitlines()
36+
for i, line in enumerate(lines, start=1):
37+
for k, pat in patterns.items():
38+
if pat.search(line):
39+
usages[k].append({
40+
"file": str(file),
41+
"line": i,
42+
"text": line.strip()
43+
})
44+
return usages
45+
46+
def main():
47+
p = argparse.ArgumentParser(description="Generate JSON report of localization key usage (m.key_name_here()).")
48+
p.add_argument("--en", default="../ui/localization/messages/en.json", help="path to en.json")
49+
p.add_argument("--src", default="../ui", help="root source directory to scan")
50+
p.add_argument("--out", default="../reports/localization_report.json", help="output report file")
51+
args = p.parse_args()
52+
53+
en_path = Path(args.en)
54+
if not en_path.is_file():
55+
print(f"en.json not found: {en_path}", flush=True)
56+
raise SystemExit(2)
57+
58+
with en_path.open(encoding="utf-8") as f:
59+
payload = json.load(f)
60+
61+
keys = sorted(list(flatten(payload)))
62+
files = list(gather_files(args.src))
63+
usages = find_usages(keys, files)
64+
65+
report = {
66+
"generated_at": datetime.utcnow().isoformat() + "Z",
67+
"en_json": str(en_path),
68+
"src_root": args.src,
69+
"total_keys": len(keys),
70+
"keys": {}
71+
}
72+
73+
unused_count = 0
74+
for k in keys:
75+
occ = usages.get(k, [])
76+
used = bool(occ)
77+
if not used:
78+
unused_count += 1
79+
report["keys"][k] = {
80+
"used": used,
81+
"occurrences": occ
82+
}
83+
84+
report["unused_count"] = unused_count
85+
report["unused_keys"] = [k for k, v in report["keys"].items() if not v["used"]]
86+
87+
out_path = Path(args.out)
88+
out_path.parent.mkdir(parents=True, exist_ok=True)
89+
with out_path.open("w", encoding="utf-8") as f:
90+
json.dump(report, f, indent=2, ensure_ascii=False)
91+
92+
print(f"Report written to {out_path}")
93+
print(f"Total keys: {report['total_keys']}, Unused: {report['unused_count']}")
94+
95+
if __name__ == "__main__":
96+
main()

ui/eslint.config.cjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ const {
99
fixupConfigRules,
1010
} = require("@eslint/compat");
1111

12-
const tsParser = require("@typescript-eslint/parser");
13-
const reactRefresh = require("eslint-plugin-react-refresh");
1412
const js = require("@eslint/js");
1513

1614
const {
@@ -23,6 +21,9 @@ const compat = new FlatCompat({
2321
allConfig: js.configs.all
2422
});
2523

24+
const tsParser = require("@typescript-eslint/parser");
25+
const reactRefresh = require("eslint-plugin-react-refresh");
26+
2627
module.exports = defineConfig([{
2728
languageOptions: {
2829
globals: {
@@ -66,7 +67,7 @@ module.exports = defineConfig([{
6667
groups: ["builtin", "external", "internal", "parent", "sibling"],
6768
"newlines-between": "always",
6869
}],
69-
70+
7071
"@typescript-eslint/no-unused-vars": ["warn", {
7172
"argsIgnorePattern": "^_", "varsIgnorePattern": "^_"
7273
}],
@@ -81,7 +82,10 @@ module.exports = defineConfig([{
8182
map: [
8283
["@components", "./src/components"],
8384
["@routes", "./src/routes"],
85+
["@hooks", "./src/hooks"],
86+
["@providers", "./src/providers"],
8487
["@assets", "./src/assets"],
88+
["@localizations", "./localization/paraglide"],
8589
["@", "./src"],
8690
],
8791

ui/index.html

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,39 @@
4545
<link rel="shortcut icon" href="/favicon.ico" />
4646
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
4747
<meta name="apple-mobile-web-app-title" content="JetKVM" />
48-
<link rel="manifest" href="/site.webmanifest" />
48+
<link rel="manifest" href="/public/site.webmanifest" />
4949
<meta name="theme-color" content="#051946" />
50-
<meta name="description" content="A web-based KVM console for managing remote servers." />
50+
<meta
51+
name="description"
52+
content="A web-based KVM console for managing remote servers."
53+
/>
5154
<script>
5255
function applyThemeFromPreference() {
5356
// dark theme setup
54-
var darkDesired = localStorage.theme === "dark" ||
57+
var darkDesired =
58+
localStorage.theme === "dark" ||
5559
(!("theme" in localStorage) &&
56-
window.matchMedia("(prefers-color-scheme: dark)").matches)
60+
window.matchMedia("(prefers-color-scheme: dark)").matches);
5761

58-
document.documentElement.classList.toggle("dark", darkDesired)
62+
document.documentElement.classList.toggle("dark", darkDesired);
5963
}
6064

6165
// initial theme application
6266
applyThemeFromPreference();
6367

6468
// Listen for system theme changes
65-
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyThemeFromPreference);
66-
window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", applyThemeFromPreference);
69+
window
70+
.matchMedia("(prefers-color-scheme: dark)")
71+
.addEventListener("change", applyThemeFromPreference);
72+
window
73+
.matchMedia("(prefers-color-scheme: light)")
74+
.addEventListener("change", applyThemeFromPreference);
6775
</script>
6876
</head>
6977
<body
70-
class="h-full w-full bg-[#f3f9ff] font-sans text-sm antialiased dark:bg-slate-900 md:text-base"
78+
class="h-full w-full bg-[#f3f9ff] font-sans text-sm antialiased md:text-base dark:bg-slate-900"
7179
>
72-
<div id="root" class="w-full h-full"></div>
80+
<div id="root" class="h-full w-full"></div>
7381
<script type="module" src="/src/main.tsx"></script>
7482
</body>
7583
</html>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cache

0 commit comments

Comments
 (0)