mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 19:02:31 +00:00
i18n: translate optimization names in source viewer headings
This commit is contained in:
@@ -19,7 +19,7 @@ import { SUPPORTED_LANGUAGES, useI18n } from "../lib/i18n/provider"
|
|||||||
import { useIsPbs } from "../lib/use-platform"
|
import { useIsPbs } from "../lib/use-platform"
|
||||||
import type { LanguageCode } from "../lib/i18n/languages"
|
import type { LanguageCode } from "../lib/i18n/languages"
|
||||||
import { NavTabOrderCard } from "./nav-tab-order-card"
|
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
|
// GitHub Dark color palette for bash syntax highlighting
|
||||||
const BASH_KEYWORDS = new Set([
|
const BASH_KEYWORDS = new Set([
|
||||||
@@ -355,6 +355,7 @@ export function Settings() {
|
|||||||
open: boolean
|
open: boolean
|
||||||
loading: boolean
|
loading: boolean
|
||||||
toolName: string
|
toolName: string
|
||||||
|
toolKey: string
|
||||||
version: string
|
version: string
|
||||||
availableVersion: string
|
availableVersion: string
|
||||||
functionName: string
|
functionName: string
|
||||||
@@ -362,7 +363,7 @@ export function Settings() {
|
|||||||
script: string
|
script: string
|
||||||
error: OptimizationSourceError | null
|
error: OptimizationSourceError | null
|
||||||
deprecated: boolean
|
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 [codeCopied, setCodeCopied] = useState(false)
|
||||||
const codeRequest = useRef(0)
|
const codeRequest = useRef(0)
|
||||||
const closeCodeModal = () => {
|
const closeCodeModal = () => {
|
||||||
@@ -685,6 +686,7 @@ export function Settings() {
|
|||||||
open: true,
|
open: true,
|
||||||
loading: true,
|
loading: true,
|
||||||
toolName: tool.name,
|
toolName: tool.name,
|
||||||
|
toolKey: tool.key,
|
||||||
version: tool.version || '1.0',
|
version: tool.version || '1.0',
|
||||||
availableVersion: tool.available_version || tool.version || '1.0',
|
availableVersion: tool.available_version || tool.version || '1.0',
|
||||||
functionName: '',
|
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"
|
className="relative bg-card border border-border rounded-xl shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
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()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -2191,7 +2193,7 @@ export function Settings() {
|
|||||||
<Code className={`h-5 w-5 flex-shrink-0 ${codeModal.deprecated ? 'text-amber-500' : 'text-orange-500'}`} />
|
<Code className={`h-5 w-5 flex-shrink-0 ${codeModal.deprecated ? 'text-amber-500' : 'text-orange-500'}`} />
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<h3 className="text-sm font-semibold truncate">{codeModal.toolName}</h3>
|
<h3 className="text-sm font-semibold truncate">{optimizationSourceName(codeModal.toolKey, codeModal.toolName, t)}</h3>
|
||||||
{codeModal.deprecated && (
|
{codeModal.deprecated && (
|
||||||
<span className="text-[9px] uppercase tracking-wider text-amber-500 bg-amber-500/10 border border-amber-500/30 px-1.5 py-0.5 rounded flex-shrink-0">
|
<span className="text-[9px] uppercase tracking-wider text-amber-500 bg-amber-500/10 border border-amber-500/30 px-1.5 py-0.5 rounded flex-shrink-0">
|
||||||
{t("settings.optimizations.legacy")}
|
{t("settings.optimizations.legacy")}
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
type Translate = (key: string, params?: Record<string, string | number>) => string
|
type Translate = (key: string, params?: Record<string, string | number>) => 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 {
|
export interface OptimizationSourceError {
|
||||||
kind: "unavailable" | "loadFailed"
|
kind: "unavailable" | "loadFailed"
|
||||||
detail?: string
|
detail?: string
|
||||||
|
|||||||
@@ -2375,6 +2375,35 @@
|
|||||||
"sourceFunctionMissing": "The function was not found in the available scripts.",
|
"sourceFunctionMissing": "The function was not found in the available scripts.",
|
||||||
"sourceUnknownTool": "Unknown tool: {tool}",
|
"sourceUnknownTool": "Unknown tool: {tool}",
|
||||||
"sourceNoMapping": "No function is assigned to the 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",
|
"legacy": "legacy",
|
||||||
"updateToolTitle": "Update {name} to v{version}",
|
"updateToolTitle": "Update {name} to v{version}",
|
||||||
"availableUpdates": "Available updates",
|
"availableUpdates": "Available updates",
|
||||||
|
|||||||
@@ -2300,6 +2300,35 @@
|
|||||||
"sourceFunctionMissing": "Funkcia sa nenašla v dostupných skriptoch.",
|
"sourceFunctionMissing": "Funkcia sa nenašla v dostupných skriptoch.",
|
||||||
"sourceUnknownTool": "Neznámy nástroj: {tool}",
|
"sourceUnknownTool": "Neznámy nástroj: {tool}",
|
||||||
"sourceNoMapping": "K nástroju {tool} nie je priradená funkcia.",
|
"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",
|
"legacy": "staršie",
|
||||||
"updateToolTitle": "Aktualizovať {name} na v{version}",
|
"updateToolTitle": "Aktualizovať {name} na v{version}",
|
||||||
"availableUpdates": "Dostupné aktualizácie",
|
"availableUpdates": "Dostupné aktualizácie",
|
||||||
|
|||||||
@@ -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 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 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 "<tag>& $HOME"\n}\n',deprecated:false}
|
const sourceBody={success:true,function:'demo',script:'demo.sh',source:'demo() {\n echo "<tag>& $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
|
let cursor=0,states=[],refs=[],effects=[],memo=[],tree,language=locale,reply=()=>sourceBody
|
||||||
const calls=[],events={},timers=[],copies=[];let fallbackCopies=0
|
const calls=[],events={},timers=[],copies=[];let fallbackCopies=0
|
||||||
const t=(key,params)=>translate(language)(key,params)
|
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'})
|
const endpoint=String(url).replace(/^https?:\/\/[^/]+/,'');calls.push({endpoint,method:options.method||'GET'})
|
||||||
assert.equal(options.method||'GET','GET','Source viewer may never mutate')
|
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.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})
|
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)
|
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)
|
vm.runInNewContext(compile(source),context)
|
||||||
const render=()=>{cursor=0;effects=[];tree=context.exports.Settings();for(const effect of effects)effect();return tree}
|
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 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 dialog=()=>flatten(tree).find(node=>node.props?.role==='dialog')
|
||||||
const close=()=>flatten(tree).find(node=>node.props?.['aria-label']===t('actions.close')).props.onClick()
|
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,
|
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)
|
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 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'))
|
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')
|
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)
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user