diff --git a/AppImage/components/settings.tsx b/AppImage/components/settings.tsx index 4da56f8f..f51e82ae 100644 --- a/AppImage/components/settings.tsx +++ b/AppImage/components/settings.tsx @@ -19,7 +19,7 @@ import { SUPPORTED_LANGUAGES, useI18n } from "../lib/i18n/provider" import { useIsPbs } from "../lib/use-platform" import type { LanguageCode } from "../lib/i18n/languages" import { NavTabOrderCard } from "./nav-tab-order-card" -import { optimizationSourceError, type OptimizationSourceError, type OptimizationSourceResponse } from "../lib/optimization-source-errors" +import { optimizationSourceError, optimizationSourceName, type OptimizationSourceError, type OptimizationSourceResponse } from "../lib/optimization-source-errors" // GitHub Dark color palette for bash syntax highlighting const BASH_KEYWORDS = new Set([ @@ -355,6 +355,7 @@ export function Settings() { open: boolean loading: boolean toolName: string + toolKey: string version: string availableVersion: string functionName: string @@ -362,7 +363,7 @@ export function Settings() { script: string error: OptimizationSourceError | null deprecated: boolean - }>({ open: false, loading: false, toolName: '', version: '', availableVersion: '', functionName: '', source: '', script: '', error: null, deprecated: false }) + }>({ open: false, loading: false, toolName: '', toolKey: '', version: '', availableVersion: '', functionName: '', source: '', script: '', error: null, deprecated: false }) const [codeCopied, setCodeCopied] = useState(false) const codeRequest = useRef(0) const closeCodeModal = () => { @@ -685,6 +686,7 @@ export function Settings() { open: true, loading: true, toolName: tool.name, + toolKey: tool.key, version: tool.version || '1.0', availableVersion: tool.available_version || tool.version || '1.0', functionName: '', @@ -2182,7 +2184,7 @@ export function Settings() { className="relative bg-card border border-border rounded-xl shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col" role="dialog" aria-modal="true" - aria-label={t("settings.optimizations.sourceDialogTitle", { name: codeModal.toolName })} + aria-label={t("settings.optimizations.sourceDialogTitle", { name: optimizationSourceName(codeModal.toolKey, codeModal.toolName, t) })} onClick={e => e.stopPropagation()} > {/* Header */} @@ -2191,7 +2193,7 @@ export function Settings() {
-

{codeModal.toolName}

+

{optimizationSourceName(codeModal.toolKey, codeModal.toolName, t)}

{codeModal.deprecated && ( {t("settings.optimizations.legacy")} diff --git a/AppImage/lib/optimization-source-errors.ts b/AppImage/lib/optimization-source-errors.ts index e0d0cdf8..fffd13bc 100644 --- a/AppImage/lib/optimization-source-errors.ts +++ b/AppImage/lib/optimization-source-errors.ts @@ -1,5 +1,17 @@ type Translate = (key: string, params?: Record) => string +const SOURCE_TOOLS = new Set([ + "subscription_banner", "time_sync", "apt_languages", "journald", "logrotate", + "system_limits", "memory_settings", "zram_swap", "kernel_panic", "apt_ipv4", + "kexec", "rpc", "motd", "system_utils", "network_optimization", "bashrc_custom", + "figurine", "fastfetch", "log2ram", "zfs_autotrim", "amd_fixes", "persistent_network", + "vfio_iommu", "lvm_repair", "repo_cleanup", "proxmox_repos", "entropy", +]) + +export function optimizationSourceName(tool: string, fallback: string, t: Translate): string { + return SOURCE_TOOLS.has(tool) ? t(`settings.optimizations.sourceToolNames.${tool}`) : fallback +} + export interface OptimizationSourceError { kind: "unavailable" | "loadFailed" detail?: string diff --git a/AppImage/messages/en/common.json b/AppImage/messages/en/common.json index 5051c2c9..fcc71206 100644 --- a/AppImage/messages/en/common.json +++ b/AppImage/messages/en/common.json @@ -2375,6 +2375,35 @@ "sourceFunctionMissing": "The function was not found in the available scripts.", "sourceUnknownTool": "Unknown tool: {tool}", "sourceNoMapping": "No function is assigned to the tool {tool}.", + "sourceToolNames": { + "subscription_banner": "Subscription Banner Removal", + "time_sync": "Time Synchronization", + "apt_languages": "APT Language Skip", + "journald": "Journald Optimization", + "logrotate": "Logrotate Optimization", + "system_limits": "System Limits Increase", + "memory_settings": "Memory Settings Optimization", + "zram_swap": "ZRAM Swap Profile", + "kernel_panic": "Kernel Panic Configuration", + "apt_ipv4": "APT IPv4 Force", + "kexec": "kexec for quick reboots", + "rpc": "RPC / rpcbind Disable", + "motd": "Custom MOTD Banner", + "system_utils": "System Utilities", + "network_optimization": "Network Optimizations", + "bashrc_custom": "Bashrc Customization", + "figurine": "Figurine", + "fastfetch": "Fastfetch", + "log2ram": "Log2ram (SSD Protection)", + "zfs_autotrim": "ZFS Autotrim", + "amd_fixes": "AMD CPU (Ryzen/EPYC) fixes", + "persistent_network": "Setting persistent network interfaces", + "vfio_iommu": "VFIO/IOMMU Passthrough", + "lvm_repair": "LVM PV Headers Repair", + "repo_cleanup": "Repository Cleanup", + "proxmox_repos": "Proxmox APT Repositories", + "entropy": "Entropy Generation (haveged)" + }, "legacy": "legacy", "updateToolTitle": "Update {name} to v{version}", "availableUpdates": "Available updates", diff --git a/AppImage/messages/sk/common.json b/AppImage/messages/sk/common.json index 0b012632..b4bbfcf7 100644 --- a/AppImage/messages/sk/common.json +++ b/AppImage/messages/sk/common.json @@ -2300,6 +2300,35 @@ "sourceFunctionMissing": "Funkcia sa nenašla v dostupných skriptoch.", "sourceUnknownTool": "Neznámy nástroj: {tool}", "sourceNoMapping": "K nástroju {tool} nie je priradená funkcia.", + "sourceToolNames": { + "subscription_banner": "Odstránenie upozornenia na predplatné", + "time_sync": "Synchronizácia času", + "apt_languages": "Vynechanie jazykových balíkov APT", + "journald": "Optimalizácia journald", + "logrotate": "Optimalizácia logrotate", + "system_limits": "Zvýšenie systémových limitov", + "memory_settings": "Optimalizácia nastavení pamäte", + "zram_swap": "Profil swapu ZRAM", + "kernel_panic": "Nastavenie správania pri páde jadra", + "apt_ipv4": "Vynútenie IPv4 pre APT", + "kexec": "Rýchly reštart cez kexec", + "rpc": "Vypnutie RPC / rpcbind", + "motd": "Vlastná uvítacia správa MOTD", + "system_utils": "Systémové nástroje", + "network_optimization": "Optimalizácia siete", + "bashrc_custom": "Prispôsobenie bashrc", + "figurine": "Figurine", + "fastfetch": "Fastfetch", + "log2ram": "Log2ram (ochrana SSD)", + "zfs_autotrim": "ZFS Autotrim", + "amd_fixes": "Opravy pre procesory AMD (Ryzen/EPYC)", + "persistent_network": "Trvalé názvy sieťových rozhraní", + "vfio_iommu": "Priame priradenie zariadení (VFIO/IOMMU)", + "lvm_repair": "Oprava hlavičiek fyzických zväzkov LVM", + "repo_cleanup": "Vyčistenie repozitárov", + "proxmox_repos": "Repozitáre APT pre Proxmox", + "entropy": "Generovanie entropie (haveged)" + }, "legacy": "staršie", "updateToolTitle": "Aktualizovať {name} na v{version}", "availableUpdates": "Dostupné aktualizácie", diff --git a/tests/test_optimization_source.cjs b/tests/test_optimization_source.cjs index bb5927e1..224454e0 100644 --- a/tests/test_optimization_source.cjs +++ b/tests/test_optimization_source.cjs @@ -15,7 +15,7 @@ const source=process.argv.includes('--baseline')?execFileSync('git',['show','e82 const formatter={exports:{}};vm.runInNewContext(compile(fs.readFileSync(path.join(app,'lib/optimization-source-errors.ts'),'utf8')),formatter) const tools=['one','two'].map(key=>({key,name:'Demo '+key,enabled:true,has_source:true,version:'1.0',available_version:'1.1'})) const sourceBody={success:true,function:'demo',script:'demo.sh',source:'demo() {\n echo "& $HOME"\n}\n',deprecated:false} -function fixture(locale='sk'){ +function fixture(locale='sk',catalog=tools){ let cursor=0,states=[],refs=[],effects=[],memo=[],tree,language=locale,reply=()=>sourceBody const calls=[],events={},timers=[],copies=[];let fallbackCopies=0 const t=(key,params)=>translate(language)(key,params) @@ -29,7 +29,7 @@ function fixture(locale='sk'){ const endpoint=String(url).replace(/^https?:\/\/[^/]+/,'');calls.push({endpoint,method:options.method||'GET'}) assert.equal(options.method||'GET','GET','Source viewer may never mutate') if(endpoint.includes('/tool-source/')){const result=await reply(endpoint);if(result instanceof Error)throw result;return result?.http?response(result.body,result.http):response(result)} - if(endpoint.endsWith('/installed-tools'))return response({success:true,installed_tools:tools,updates_available_count:0}) + if(endpoint.endsWith('/installed-tools'))return response({success:true,installed_tools:catalog,updates_available_count:0}) return response({categories:[],storages:[],interfaces:[],dismissed:[],success:true,candidates:[],configured:false}) } const api={exports:{},fetch,process:{env:{}},console:{error(){}}};vm.runInNewContext(compile(fs.readFileSync(path.join(app,'lib/api-config.ts'),'utf8')),api) @@ -51,7 +51,7 @@ function fixture(locale='sk'){ vm.runInNewContext(compile(source),context) const render=()=>{cursor=0;effects=[];tree=context.exports.Settings();for(const effect of effects)effect();return tree} const settle=async()=>{for(let i=0;i<24;i++)await Promise.resolve();render()} - const tool=name=>flatten(tree).find(node=>node.props?.title===t('settings.optimizations.sourceTitle')&&text(node).includes('Demo '+name)) + const tool=name=>flatten(tree).find(node=>node.props?.title===t('settings.optimizations.sourceTitle')&&text(node).includes(catalog.find(tool=>tool.key===name)?.name||'Demo '+name)) const dialog=()=>flatten(tree).find(node=>node.props?.role==='dialog') const close=()=>flatten(tree).find(node=>node.props?.['aria-label']===t('actions.close')).props.onClick() return{render,settle,tool,dialog,close,calls,copies,timers,tree:()=>tree,setReply:value=>reply=value,t, @@ -84,6 +84,15 @@ async function main(){ f.win.isSecureContext=false;await copy.props.onClick();assert.equal(f.fallbackCopies(),1) const before=f.calls.length;f.close();f.render();assert.equal(f.dialog(),undefined);assert.equal(f.calls.length,before) } + const names=catalogs.en.settings.optimizations.sourceToolNames + for(const [key,name]of Object.entries(names)){ + assert.equal(formatter.exports.optimizationSourceName(key,name,translate('en')),name) + assert.equal(formatter.exports.optimizationSourceName(key,name,translate('sk')),catalogs.sk.settings.optimizations.sourceToolNames[key]) + assert.equal(formatter.exports.optimizationSourceName(key,name,translate('de')),name) + } + assert.equal(Object.keys(names).length,27) + assert.equal(formatter.exports.optimizationSourceName('future','Future tool',translate('sk')),'Future tool') + const known=fixture('sk',[{...tools[0],key:'time_sync',name:'Time Synchronization'}]);known.render();await known.settle();await known.tool('time_sync').props.onClick();known.render();assert.ok(text(known.dialog()).includes('Synchronizácia času'));known.language('en');assert.ok(text(known.dialog()).includes('Time Synchronization')) const f=fixture();f.render();await f.settle();let resolveOld;f.setReply(()=>new Promise(resolve=>resolveOld=resolve));const old=f.tool('one').props.onClick();f.render();assert.ok(flatten(f.dialog()).some(node=>node.props?.role==='status')) f.close();f.render();resolveOld(sourceBody);await old;f.render();assert.equal(f.dialog(),undefined,'late success never reopens closed viewer') let resolveStale;f.setReply(()=>new Promise(resolve=>resolveStale=resolve));const stale=f.tool('one').props.onClick();f.setReply(()=>({...sourceBody,source:'two() { :; }'}));await f.tool('two').props.onClick();f.render();resolveStale({http:404,body:{error:'Function not found in available scripts'}});await stale;f.render();assert.ok(text(f.dialog()).includes('Demo two'));assert.equal(flatten(f.dialog()).some(node=>node.props?.role==='alert'),false)