Parcourir la source

Localize task scheduler UI using arozos locale system

Adds scheduler.json with translations for all 6 supported languages
(zh-tw, zh-hk, zh-cn, en-us, ja-jp, ko-kr) covering all static UI
strings, form labels, dynamic JS messages, and input placeholders.

Updates scheduler.html to load applocale.js, apply locale attributes
to all static elements, and use applocale.getString() via a t() helper
for dynamically generated content (empty states, toasts, alerts,
confirm dialogs, and the remove button).

https://claude.ai/code/session_01RH5mdJBjDjUZGKaVkHbKq6
Claude il y a 2 semaines
Parent
commit
fff70d64ae
2 fichiers modifiés avec 561 ajouts et 95 suppressions
  1. 111 95
      src/web/SystemAO/arsm/scheduler.html
  2. 450 0
      src/web/SystemAO/locale/scheduler.json

+ 111 - 95
src/web/SystemAO/arsm/scheduler.html

@@ -6,6 +6,7 @@
     <title>Task Scheduler</title>
     <script src="../../script/jquery.min.js"></script>
     <script src="../../script/ao_module.js"></script>
+    <script src="../../script/applocale.js"></script>
     <script src="js/moment.min.js"></script>
     <style>
         *, *::before, *::after { box-sizing: border-box; }
@@ -360,24 +361,28 @@
     <div id="sidebar">
         <div id="sidebar-title">
             <img src="img/small_icon.png" onerror="this.style.display='none'">
-            Scheduler
+            <span locale="sidebar/title">Scheduler</span>
         </div>
         <div id="sidebar-nav">
             <div class="nav-item active" id="nav-mine"  onclick="showView('mine')">
-                <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M5 4h7M5 8h7M5 12h7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="2.5" cy="4" r="0.75" fill="currentColor"/><circle cx="2.5" cy="8" r="0.75" fill="currentColor"/><circle cx="2.5" cy="12" r="0.75" fill="currentColor"/></svg></span> My Tasks
+                <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M5 4h7M5 8h7M5 12h7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="2.5" cy="4" r="0.75" fill="currentColor"/><circle cx="2.5" cy="8" r="0.75" fill="currentColor"/><circle cx="2.5" cy="12" r="0.75" fill="currentColor"/></svg></span>
+                <span locale="nav/mine">My Tasks</span>
             </div>
             <div class="nav-item" id="nav-all"   onclick="showView('all')">
-                <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="2" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="2" y="9" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="9" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/></svg></span> All Tasks
+                <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><rect x="2" y="2" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="2" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="2" y="9" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="9" width="5" height="5" rx="1" stroke="currentColor" stroke-width="1.4"/></svg></span>
+                <span locale="nav/all">All Tasks</span>
             </div>
             <div class="nav-item" id="nav-new"   onclick="showView('new')">
-                <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span> New Task
+                <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span>
+                <span locale="nav/new">New Task</span>
             </div>
             <div class="nav-item" id="nav-remove" onclick="showView('remove')">
-                <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M3 5h10M6 5V4h4v1M6.5 8v4M9.5 8v4M4 5l.9 9h6.2L12 5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span> Remove Task
+                <span class="nav-icon"><svg viewBox="0 0 16 16" fill="none"><path d="M3 5h10M6 5V4h4v1M6.5 8v4M9.5 8v4M4 5l.9 9h6.2L12 5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
+                <span locale="nav/remove">Remove Task</span>
             </div>
         </div>
         <div id="sidebar-filter">
-            <input id="filter-input" type="text" placeholder="Filter tasks…"
+            <input id="filter-input" type="text" placeholder="filter/placeholder"
                    oninput="applyFilter()" onkeydown="if(event.key==='Enter')applyFilter()">
         </div>
     </div>
@@ -387,102 +392,102 @@
 
         <!-- My Tasks -->
         <div id="view-mine" class="view active">
-            <div class="section-title">My Scheduled Tasks</div>
+            <div class="section-title" locale="view/mine/title">My Scheduled Tasks</div>
             <div class="card" id="card-mine">
                 <div class="task-row header">
-                    <span>Task / Script</span>
-                    <span>Path</span>
-                    <span>App</span>
-                    <span>Interval</span>
+                    <span locale="header/task-script">Task / Script</span>
+                    <span locale="header/path">Path</span>
+                    <span locale="header/app">App</span>
+                    <span locale="header/interval">Interval</span>
                 </div>
-                <div id="list-mine"><div class="empty-state">Loading…</div></div>
+                <div id="list-mine"><div class="empty-state" locale="js/loading">Loading…</div></div>
             </div>
         </div>
 
         <!-- All Tasks (admin) -->
         <div id="view-all" class="view">
-            <div class="section-title">All Scheduled Tasks</div>
+            <div class="section-title" locale="view/all/title">All Scheduled Tasks</div>
             <div class="card">
                 <div class="task-row all-cols header">
-                    <span>Task Name</span>
-                    <span>Creator</span>
-                    <span>Script Path</span>
-                    <span>App</span>
-                    <span>Interval</span>
-                    <span>Base Time</span>
+                    <span locale="header/taskname">Task Name</span>
+                    <span locale="header/creator">Creator</span>
+                    <span locale="header/scriptpath">Script Path</span>
+                    <span locale="header/app">App</span>
+                    <span locale="header/interval">Interval</span>
+                    <span locale="header/basetime">Base Time</span>
                 </div>
-                <div id="list-all"><div class="empty-state">Loading…</div></div>
+                <div id="list-all"><div class="empty-state" locale="js/loading">Loading…</div></div>
             </div>
         </div>
 
         <!-- New Task -->
         <div id="view-new" class="view">
-            <div class="section-title">New Scheduled Task</div>
+            <div class="section-title" locale="view/new/title">New Scheduled Task</div>
             <div id="noPermMessage">
                 <svg width="14" height="14" viewBox="0 0 16 16" fill="none" style="flex-shrink:0;margin-right:6px;vertical-align:-2px"><path d="M8 2L1.5 14h13L8 2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 7v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="12" r="0.8" fill="currentColor"/></svg>
-                Your account does not have permission to create scheduled tasks.
-                Ask an administrator to grant cron job access via <strong>System Settings &rarr; Tasks Scheduler</strong>.
+                <span locale="noperm/message">Your account does not have permission to create scheduled tasks.
+                Ask an administrator to grant cron job access via <strong>System Settings &rarr; Tasks Scheduler</strong>.</span>
             </div>
             <div id="newTaskForm">
                 <div class="form-card">
                     <div class="form-row">
-                        <label class="form-label">Task Name <span class="form-req">*</span></label>
+                        <label class="form-label"><span locale="form/taskname/label">Task Name</span> <span class="form-req">*</span></label>
                         <input class="form-input" id="taskname" type="text"
-                               placeholder="My Daily Task" maxlength="32" autocomplete="off">
-                        <div class="form-hint">Max 32 characters, must be unique.</div>
+                               placeholder="taskname/placeholder" maxlength="32" autocomplete="off">
+                        <div class="form-hint" locale="form/taskname/hint">Max 32 characters, must be unique.</div>
                     </div>
                     <div class="form-row">
-                        <label class="form-label">Description</label>
+                        <label class="form-label" locale="form/desc/label">Description</label>
                         <input class="form-input" id="desc" type="text"
-                               placeholder="What does this task do?" autocomplete="off">
+                               placeholder="desc/placeholder" autocomplete="off">
                     </div>
                     <div class="form-row">
-                        <label class="form-label">Script Path <span class="form-req">*</span></label>
+                        <label class="form-label"><span locale="form/scriptpath/label">Script Path</span> <span class="form-req">*</span></label>
                         <div class="input-with-btn">
                             <input class="form-input" id="scriptpath" type="text"
-                                   placeholder="user:/path/to/script.agi" autocomplete="off"
+                                   placeholder="scriptpath/placeholder" autocomplete="off"
                                    oninput="checkExt(this.value)">
-                            <button class="btn" onclick="openFileSelector()">Browse…</button>
+                            <button class="btn" onclick="openFileSelector()" locale="form/browse">Browse…</button>
                         </div>
                         <div class="warn-bar" id="extWarn">
                             <svg width="13" height="13" viewBox="0 0 16 16" fill="none" style="flex-shrink:0;vertical-align:-2px;margin-right:4px"><path d="M8 2L1.5 14h13L8 2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 7v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="12" r="0.8" fill="currentColor"/></svg>
-                            This file extension may not be supported. Only .agi and .js files are executed.
+                            <span locale="form/extwarn">This file extension may not be supported. Only .agi and .js files are executed.</span>
                         </div>
                     </div>
                     <div class="form-row">
-                        <label class="form-label">Run Every <span class="form-req">*</span></label>
+                        <label class="form-label"><span locale="form/runevery/label">Run Every</span> <span class="form-req">*</span></label>
                         <div class="form-row-inline">
                             <input class="form-input" id="intervalvalue" type="number"
                                    value="1" min="1" style="max-width:90px;">
                             <select class="form-input" id="intervalunit" style="max-width:160px;">
-                                <option value="60">Minutes</option>
-                                <option value="3600">Hours</option>
-                                <option value="86400" selected>Days</option>
-                                <option value="2628333">Months (approx.)</option>
+                                <option value="60" locale="form/unit/minutes">Minutes</option>
+                                <option value="3600" locale="form/unit/hours">Hours</option>
+                                <option value="86400" selected locale="form/unit/days">Days</option>
+                                <option value="2628333" locale="form/unit/months">Months (approx.)</option>
                             </select>
                         </div>
-                        <div class="form-hint">Month is approximated as 2 628 333 seconds.</div>
+                        <div class="form-hint" locale="form/month/hint">Month is approximated as 2 628 333 seconds.</div>
                     </div>
                     <div class="form-row">
-                        <label class="form-label">Align to <span class="form-req">*</span></label>
+                        <label class="form-label"><span locale="form/alignto/label">Align to</span> <span class="form-req">*</span></label>
                         <select class="form-input" id="intervalbase">
-                            <option value="now">Now (current minute)</option>
-                            <option value="hour">Start of the Hour</option>
-                            <option value="day">Start of the Day</option>
-                            <option value="month">Start of the Month</option>
-                            <option value="year">Start of the Year</option>
-                            <option value="mon">Monday midnight</option>
-                            <option value="tue">Tuesday midnight</option>
-                            <option value="wed">Wednesday midnight</option>
-                            <option value="thu">Thursday midnight</option>
-                            <option value="fri">Friday midnight</option>
-                            <option value="sat">Saturday midnight</option>
-                            <option value="sun">Sunday midnight</option>
+                            <option value="now" locale="form/base/now">Now (current minute)</option>
+                            <option value="hour" locale="form/base/hour">Start of the Hour</option>
+                            <option value="day" locale="form/base/day">Start of the Day</option>
+                            <option value="month" locale="form/base/month">Start of the Month</option>
+                            <option value="year" locale="form/base/year">Start of the Year</option>
+                            <option value="mon" locale="form/base/mon">Monday midnight</option>
+                            <option value="tue" locale="form/base/tue">Tuesday midnight</option>
+                            <option value="wed" locale="form/base/wed">Wednesday midnight</option>
+                            <option value="thu" locale="form/base/thu">Thursday midnight</option>
+                            <option value="fri" locale="form/base/fri">Friday midnight</option>
+                            <option value="sat" locale="form/base/sat">Saturday midnight</option>
+                            <option value="sun" locale="form/base/sun">Sunday midnight</option>
                         </select>
-                        <div class="form-hint">Sets the reference point for interval alignment (useful for weekly/monthly schedules).</div>
+                        <div class="form-hint" locale="form/base/hint">Sets the reference point for interval alignment (useful for weekly/monthly schedules).</div>
                     </div>
-                    <button class="btn primary" onclick="submitNewTask()">Create Task</button>
-                    <span style="margin-left:10px; font-size:12px; color:var(--text-muted);">
+                    <button class="btn primary" onclick="submitNewTask()" locale="form/submit">Create Task</button>
+                    <span style="margin-left:10px; font-size:12px; color:var(--text-muted);" locale="form/required/hint">
                         Fields with <span class="form-req">*</span> are required.
                     </span>
                 </div>
@@ -491,18 +496,18 @@
 
         <!-- Remove Task -->
         <div id="view-remove" class="view">
-            <div class="section-title">Remove Scheduled Tasks</div>
+            <div class="section-title" locale="view/remove/title">Remove Scheduled Tasks</div>
             <div class="card">
-                <div class="danger-header"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" style="flex-shrink:0;vertical-align:-2px;margin-right:5px"><path d="M8 2L1.5 14h13L8 2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 7v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="12" r="0.8" fill="currentColor"/></svg>Removal is permanent and cannot be undone.</div>
+                <div class="danger-header"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" style="flex-shrink:0;vertical-align:-2px;margin-right:5px"><path d="M8 2L1.5 14h13L8 2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M8 7v3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><circle cx="8" cy="12" r="0.8" fill="currentColor"/></svg><span locale="danger/message">Removal is permanent and cannot be undone.</span></div>
                 <div class="task-row remove-cols header">
-                    <span>Task Name</span>
-                    <span>Creator</span>
-                    <span>Script Path</span>
-                    <span>App</span>
-                    <span>Interval</span>
-                    <span>Action</span>
+                    <span locale="header/taskname">Task Name</span>
+                    <span locale="header/creator">Creator</span>
+                    <span locale="header/scriptpath">Script Path</span>
+                    <span locale="header/app">App</span>
+                    <span locale="header/interval">Interval</span>
+                    <span locale="header/action">Action</span>
                 </div>
-                <div id="list-remove"><div class="empty-state">Loading…</div></div>
+                <div id="list-remove"><div class="empty-state" locale="js/loading">Loading…</div></div>
             </div>
         </div>
 
@@ -517,6 +522,14 @@
         document.body.classList.toggle('dark', c !== 'whiteTheme');
     });
 
+    /* ── i18n helper ── */
+    function t(key, fallback) {
+        if (typeof applocale !== 'undefined') {
+            return applocale.getString(key, fallback);
+        }
+        return fallback;
+    }
+
     /* ── Navigation ── */
     var currentView = 'mine';
     function showView(name) {
@@ -539,7 +552,7 @@
             m = Math.floor(s % 3600 / 60),
             sec = s % 60;
         var parts = [];
-        if (d)   parts.push(d   + (d   === 1 ? 'd' : 'd'));
+        if (d)   parts.push(d   + 'd');
         if (h)   parts.push(h   + 'h');
         if (m)   parts.push(m   + 'min');
         if (sec) parts.push(sec + 's');
@@ -591,18 +604,18 @@
             var el = document.getElementById('list-mine');
             var rows = (data || []).filter(matchFilter);
             if (!rows.length) {
-                el.innerHTML = '<div class="empty-state">No scheduled tasks found.</div>';
+                el.innerHTML = '<div class="empty-state">' + t('js/empty/notfound', 'No scheduled tasks found.') + '</div>';
                 return;
             }
-            el.innerHTML = rows.map(function(t) {
-                var script = (t.ScriptVpath || '').split('/').pop();
+            el.innerHTML = rows.map(function(task) {
+                var script = (task.ScriptVpath || '').split('/').pop();
                 return '<div class="task-row">'
-                    + '<span class="task-name" title="' + esc(t.Name) + '">' + esc(t.Name)
-                    + (t.Description ? '<br><small style="color:var(--text-muted);font-weight:400">' + esc(t.Description) + '</small>' : '')
+                    + '<span class="task-name" title="' + esc(task.Name) + '">' + esc(task.Name)
+                    + (task.Description ? '<br><small style="color:var(--text-muted);font-weight:400">' + esc(task.Description) + '</small>' : '')
                     + '</span>'
-                    + '<span class="task-script" title="' + esc(t.ScriptVpath) + '">' + esc(t.ScriptVpath) + '</span>'
-                    + '<span>' + appBadge(t.AppName) + '</span>'
-                    + '<span class="task-interval">Every ' + fmtInterval(t.ExecutionInterval) + '</span>'
+                    + '<span class="task-script" title="' + esc(task.ScriptVpath) + '">' + esc(task.ScriptVpath) + '</span>'
+                    + '<span>' + appBadge(task.AppName) + '</span>'
+                    + '<span class="task-interval">' + t('js/task/every', 'Every ') + fmtInterval(task.ExecutionInterval) + '</span>'
                     + '</div>';
             }).join('');
         });
@@ -614,17 +627,17 @@
             var el = document.getElementById('list-all');
             var rows = (data || []).filter(matchFilter);
             if (!rows.length) {
-                el.innerHTML = '<div class="empty-state">No scheduled tasks found.</div>';
+                el.innerHTML = '<div class="empty-state">' + t('js/empty/notfound', 'No scheduled tasks found.') + '</div>';
                 return;
             }
-            el.innerHTML = rows.map(function(t) {
+            el.innerHTML = rows.map(function(task) {
                 return '<div class="task-row all-cols">'
-                    + '<span class="task-name" title="' + esc(t.Name) + '">' + esc(t.Name) + '</span>'
-                    + '<span style="color:var(--text-dim);font-size:12.5px">' + esc(t.Creator) + '</span>'
-                    + '<span class="task-script" title="' + esc(t.ScriptVpath) + '">' + esc(t.ScriptVpath) + '</span>'
-                    + '<span>' + appBadge(t.AppName) + '</span>'
-                    + '<span class="task-interval">Every ' + fmtInterval(t.ExecutionInterval) + '</span>'
-                    + '<span class="task-basetime">' + fmtTime(t.BaseTime) + '</span>'
+                    + '<span class="task-name" title="' + esc(task.Name) + '">' + esc(task.Name) + '</span>'
+                    + '<span style="color:var(--text-dim);font-size:12.5px">' + esc(task.Creator) + '</span>'
+                    + '<span class="task-script" title="' + esc(task.ScriptVpath) + '">' + esc(task.ScriptVpath) + '</span>'
+                    + '<span>' + appBadge(task.AppName) + '</span>'
+                    + '<span class="task-interval">' + t('js/task/every', 'Every ') + fmtInterval(task.ExecutionInterval) + '</span>'
+                    + '<span class="task-basetime">' + fmtTime(task.BaseTime) + '</span>'
                     + '</div>';
             }).join('');
         });
@@ -636,27 +649,27 @@
             var el = document.getElementById('list-remove');
             var rows = (data || []).filter(matchFilter);
             if (!rows.length) {
-                el.innerHTML = '<div class="empty-state">No scheduled tasks.</div>';
+                el.innerHTML = '<div class="empty-state">' + t('js/empty/none', 'No scheduled tasks.') + '</div>';
                 return;
             }
-            el.innerHTML = rows.map(function(t) {
+            el.innerHTML = rows.map(function(task) {
                 return '<div class="task-row remove-cols">'
-                    + '<span class="task-name" title="' + esc(t.Name) + '">' + esc(t.Name) + '</span>'
-                    + '<span style="color:var(--text-dim);font-size:12.5px">' + esc(t.Creator) + '</span>'
-                    + '<span class="task-script" title="' + esc(t.ScriptVpath) + '">' + esc(t.ScriptVpath) + '</span>'
-                    + '<span>' + appBadge(t.AppName) + '</span>'
-                    + '<span class="task-interval">Every ' + fmtInterval(t.ExecutionInterval) + '</span>'
-                    + '<span><button class="btn danger sm" onclick="removeTask(' + JSON.stringify(esc(t.Name)) + ')">Remove</button></span>'
+                    + '<span class="task-name" title="' + esc(task.Name) + '">' + esc(task.Name) + '</span>'
+                    + '<span style="color:var(--text-dim);font-size:12.5px">' + esc(task.Creator) + '</span>'
+                    + '<span class="task-script" title="' + esc(task.ScriptVpath) + '">' + esc(task.ScriptVpath) + '</span>'
+                    + '<span>' + appBadge(task.AppName) + '</span>'
+                    + '<span class="task-interval">' + t('js/task/every', 'Every ') + fmtInterval(task.ExecutionInterval) + '</span>'
+                    + '<span><button class="btn danger sm" onclick="removeTask(' + JSON.stringify(esc(task.Name)) + ')">' + t('js/btn/remove', 'Remove') + '</button></span>'
                     + '</div>';
             }).join('');
         });
     }
 
     function removeTask(name) {
-        if (!confirm('Remove task "' + name + '"? This cannot be undone.')) return;
+        if (!confirm(t('js/confirm/remove/pre', 'Remove task "') + name + t('js/confirm/remove/post', '"? This cannot be undone.'))) return;
         $.post('../../system/arsm/aecron/remove', { name: name }, function(data) {
             if (data && data.error) { alert(data.error); return; }
-            toast('Task removed.');
+            toast(t('js/toast/removed', 'Task removed.'));
             loadRemove();
         });
     }
@@ -718,9 +731,9 @@
         var interval   = parseFloat($('#intervalvalue').val()) * parseFloat($('#intervalunit').val());
         var base       = baseUnix($('#intervalbase').val());
 
-        if (!name) { alert('Task name is required.'); return; }
-        if (!scriptPath) { alert('Script path is required.'); return; }
-        if (!interval || interval < 60) { alert('Interval must be at least 1 minute.'); return; }
+        if (!name) { alert(t('js/alert/noname', 'Task name is required.')); return; }
+        if (!scriptPath) { alert(t('js/alert/nopath', 'Script path is required.')); return; }
+        if (!interval || interval < 60) { alert(t('js/alert/interval', 'Interval must be at least 1 minute.')); return; }
 
         $.post('../../system/arsm/aecron/add', {
             name: name, desc: desc, path: scriptPath,
@@ -730,13 +743,16 @@
             $('#taskname').val('');
             $('#desc').val('');
             $('#scriptpath').val('');
-            toast('Task created!');
+            toast(t('js/toast/created', 'Task created!'));
             showView('mine');
         });
     }
 
     /* ── Init ── */
-    loadMine();
+    applocale.init("../locale/scheduler.json", function() {
+        applocale.translate();
+        loadMine();
+    });
 </script>
 </body>
 </html>

+ 450 - 0
src/web/SystemAO/locale/scheduler.json

@@ -0,0 +1,450 @@
+{
+    "author": "tobychui",
+    "version": "1.0",
+    "keys": {
+        "zh-tw": {
+            "name": "繁體中文(台灣)",
+            "fwtitle": "任務排程器",
+            "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
+            "strings": {
+                "sidebar/title": "排程管理員",
+                "nav/mine": "我的任務",
+                "nav/all": "所有任務",
+                "nav/new": "新增任務",
+                "nav/remove": "移除任務",
+                "view/mine/title": "我的排程任務",
+                "header/task-script": "任務 / 腳本",
+                "header/path": "路徑",
+                "header/app": "應用程式",
+                "header/interval": "執行間隔",
+                "view/all/title": "所有排程任務",
+                "header/taskname": "任務名稱",
+                "header/creator": "建立者",
+                "header/scriptpath": "腳本路徑",
+                "header/basetime": "基準時間",
+                "header/action": "動作",
+                "view/new/title": "新增排程任務",
+                "noperm/message": "您的帳戶沒有建立排程任務的權限。請聯絡系統管理員,在<strong>系統設定 &rarr; 任務排程器</strong>中授予 cron 任務存取權限。",
+                "form/taskname/label": "任務名稱",
+                "form/taskname/hint": "最多 32 個字元,必須唯一。",
+                "form/desc/label": "說明",
+                "form/scriptpath/label": "腳本路徑",
+                "form/browse": "瀏覽…",
+                "form/extwarn": "此檔案副檔名可能不受支援。僅執行 .agi 和 .js 檔案。",
+                "form/runevery/label": "執行頻率",
+                "form/unit/minutes": "分鐘",
+                "form/unit/hours": "小時",
+                "form/unit/days": "天",
+                "form/unit/months": "月(約略值)",
+                "form/month/hint": "月份約為 2,628,333 秒。",
+                "form/alignto/label": "對齊至",
+                "form/base/now": "現在(當前分鐘)",
+                "form/base/hour": "本小時開始",
+                "form/base/day": "本日開始",
+                "form/base/month": "本月開始",
+                "form/base/year": "本年開始",
+                "form/base/mon": "週一午夜",
+                "form/base/tue": "週二午夜",
+                "form/base/wed": "週三午夜",
+                "form/base/thu": "週四午夜",
+                "form/base/fri": "週五午夜",
+                "form/base/sat": "週六午夜",
+                "form/base/sun": "週日午夜",
+                "form/base/hint": "設定間隔對齊的參考時間點(適用於每週/每月排程)。",
+                "form/submit": "建立任務",
+                "form/required/hint": "標有 <span class=\"form-req\">*</span> 的欄位為必填。",
+                "view/remove/title": "移除排程任務",
+                "danger/message": "移除操作是永久性的,無法復原。",
+                "js/loading": "載入中…",
+                "js/empty/notfound": "找不到排程任務。",
+                "js/empty/none": "沒有排程任務。",
+                "js/task/every": "每隔 ",
+                "js/btn/remove": "移除",
+                "js/confirm/remove/pre": "移除任務「",
+                "js/confirm/remove/post": "」?此操作無法復原。",
+                "js/toast/removed": "任務已移除。",
+                "js/alert/noname": "任務名稱為必填。",
+                "js/alert/nopath": "腳本路徑為必填。",
+                "js/alert/interval": "執行間隔至少需要 1 分鐘。",
+                "js/toast/created": "任務已建立!"
+            },
+            "titles": {},
+            "placeholder": {
+                "filter/placeholder": "篩選任務…",
+                "taskname/placeholder": "我的每日任務",
+                "desc/placeholder": "此任務的功能說明",
+                "scriptpath/placeholder": "user:/path/to/script.agi"
+            }
+        },
+        "zh-hk": {
+            "name": "繁體中文(香港)",
+            "fwtitle": "任務排程器",
+            "fontFamily": "\"Microsoft JhengHei\",\"SimHei\", \"Apple LiGothic Medium\", \"STHeiti\"",
+            "strings": {
+                "sidebar/title": "排程管理員",
+                "nav/mine": "我的任務",
+                "nav/all": "所有任務",
+                "nav/new": "新增任務",
+                "nav/remove": "移除任務",
+                "view/mine/title": "我的排程任務",
+                "header/task-script": "任務 / 腳本",
+                "header/path": "路徑",
+                "header/app": "應用程式",
+                "header/interval": "執行間隔",
+                "view/all/title": "所有排程任務",
+                "header/taskname": "任務名稱",
+                "header/creator": "建立者",
+                "header/scriptpath": "腳本路徑",
+                "header/basetime": "基準時間",
+                "header/action": "動作",
+                "view/new/title": "新增排程任務",
+                "noperm/message": "您的帳戶沒有建立排程任務的權限。請聯絡系統管理員,在<strong>系統設定 &rarr; 任務排程器</strong>中授予 cron 任務存取權限。",
+                "form/taskname/label": "任務名稱",
+                "form/taskname/hint": "最多 32 個字元,必須唯一。",
+                "form/desc/label": "說明",
+                "form/scriptpath/label": "腳本路徑",
+                "form/browse": "瀏覽…",
+                "form/extwarn": "此檔案副檔名可能不受支援。僅執行 .agi 和 .js 檔案。",
+                "form/runevery/label": "執行頻率",
+                "form/unit/minutes": "分鐘",
+                "form/unit/hours": "小時",
+                "form/unit/days": "天",
+                "form/unit/months": "月(約略值)",
+                "form/month/hint": "月份約為 2,628,333 秒。",
+                "form/alignto/label": "對齊至",
+                "form/base/now": "現在(當前分鐘)",
+                "form/base/hour": "本小時開始",
+                "form/base/day": "本日開始",
+                "form/base/month": "本月開始",
+                "form/base/year": "本年開始",
+                "form/base/mon": "週一午夜",
+                "form/base/tue": "週二午夜",
+                "form/base/wed": "週三午夜",
+                "form/base/thu": "週四午夜",
+                "form/base/fri": "週五午夜",
+                "form/base/sat": "週六午夜",
+                "form/base/sun": "週日午夜",
+                "form/base/hint": "設定間隔對齊的參考時間點(適用於每週/每月排程)。",
+                "form/submit": "建立任務",
+                "form/required/hint": "標有 <span class=\"form-req\">*</span> 的欄位為必填。",
+                "view/remove/title": "移除排程任務",
+                "danger/message": "移除操作是永久性的,無法復原。",
+                "js/loading": "載入中…",
+                "js/empty/notfound": "找不到排程任務。",
+                "js/empty/none": "沒有排程任務。",
+                "js/task/every": "每隔 ",
+                "js/btn/remove": "移除",
+                "js/confirm/remove/pre": "移除任務「",
+                "js/confirm/remove/post": "」?此操作無法復原。",
+                "js/toast/removed": "任務已移除。",
+                "js/alert/noname": "任務名稱為必填。",
+                "js/alert/nopath": "腳本路徑為必填。",
+                "js/alert/interval": "執行間隔至少需要 1 分鐘。",
+                "js/toast/created": "任務已建立!"
+            },
+            "titles": {},
+            "placeholder": {
+                "filter/placeholder": "篩選任務…",
+                "taskname/placeholder": "我的每日任務",
+                "desc/placeholder": "此任務的功能說明",
+                "scriptpath/placeholder": "user:/path/to/script.agi"
+            }
+        },
+        "zh-cn": {
+            "name": "简体中文",
+            "fwtitle": "任务计划程序",
+            "fontFamily": "\"Microsoft YaHei\",\"SimHei\", \"PingFangSC-Medium\", \"STHeiti\"",
+            "strings": {
+                "sidebar/title": "任务计划程序",
+                "nav/mine": "我的任务",
+                "nav/all": "全部任务",
+                "nav/new": "新建任务",
+                "nav/remove": "删除任务",
+                "view/mine/title": "我的计划任务",
+                "header/task-script": "任务 / 脚本",
+                "header/path": "路径",
+                "header/app": "应用",
+                "header/interval": "执行间隔",
+                "view/all/title": "所有计划任务",
+                "header/taskname": "任务名称",
+                "header/creator": "创建者",
+                "header/scriptpath": "脚本路径",
+                "header/basetime": "基准时间",
+                "header/action": "操作",
+                "view/new/title": "新建计划任务",
+                "noperm/message": "您的账户没有创建计划任务的权限。请联系管理员,在<strong>系统设置 &rarr; 任务计划程序</strong>中授予权限。",
+                "form/taskname/label": "任务名称",
+                "form/taskname/hint": "最多 32 个字符,必须唯一。",
+                "form/desc/label": "描述",
+                "form/scriptpath/label": "脚本路径",
+                "form/browse": "浏览…",
+                "form/extwarn": "此文件扩展名可能不受支持。仅支持执行 .agi 和 .js 文件。",
+                "form/runevery/label": "运行频率",
+                "form/unit/minutes": "分钟",
+                "form/unit/hours": "小时",
+                "form/unit/days": "天",
+                "form/unit/months": "月(近似值)",
+                "form/month/hint": "月份约为 2,628,333 秒。",
+                "form/alignto/label": "对齐至",
+                "form/base/now": "现在(当前分钟)",
+                "form/base/hour": "本小时开始",
+                "form/base/day": "本日开始",
+                "form/base/month": "本月开始",
+                "form/base/year": "本年开始",
+                "form/base/mon": "周一午夜",
+                "form/base/tue": "周二午夜",
+                "form/base/wed": "周三午夜",
+                "form/base/thu": "周四午夜",
+                "form/base/fri": "周五午夜",
+                "form/base/sat": "周六午夜",
+                "form/base/sun": "周日午夜",
+                "form/base/hint": "设置间隔对齐的参考时间点(适用于每周/每月计划)。",
+                "form/submit": "创建任务",
+                "form/required/hint": "带 <span class=\"form-req\">*</span> 的字段为必填项。",
+                "view/remove/title": "删除计划任务",
+                "danger/message": "删除操作是永久性的,无法撤销。",
+                "js/loading": "加载中…",
+                "js/empty/notfound": "未找到计划任务。",
+                "js/empty/none": "没有计划任务。",
+                "js/task/every": "每隔 ",
+                "js/btn/remove": "删除",
+                "js/confirm/remove/pre": "删除任务「",
+                "js/confirm/remove/post": "」?此操作无法撤销。",
+                "js/toast/removed": "任务已删除。",
+                "js/alert/noname": "任务名称为必填项。",
+                "js/alert/nopath": "脚本路径为必填项。",
+                "js/alert/interval": "执行间隔至少需要 1 分钟。",
+                "js/toast/created": "任务已创建!"
+            },
+            "titles": {},
+            "placeholder": {
+                "filter/placeholder": "筛选任务…",
+                "taskname/placeholder": "我的每日任务",
+                "desc/placeholder": "此任务的功能说明",
+                "scriptpath/placeholder": "user:/path/to/script.agi"
+            }
+        },
+        "en-us": {
+            "name": "English (US)",
+            "fwtitle": "Task Scheduler",
+            "fontFamily": "Arial, Helvetica, sans-serif",
+            "strings": {
+                "sidebar/title": "Scheduler",
+                "nav/mine": "My Tasks",
+                "nav/all": "All Tasks",
+                "nav/new": "New Task",
+                "nav/remove": "Remove Task",
+                "view/mine/title": "My Scheduled Tasks",
+                "header/task-script": "Task / Script",
+                "header/path": "Path",
+                "header/app": "App",
+                "header/interval": "Interval",
+                "view/all/title": "All Scheduled Tasks",
+                "header/taskname": "Task Name",
+                "header/creator": "Creator",
+                "header/scriptpath": "Script Path",
+                "header/basetime": "Base Time",
+                "header/action": "Action",
+                "view/new/title": "New Scheduled Task",
+                "noperm/message": "Your account does not have permission to create scheduled tasks. Ask an administrator to grant cron job access via <strong>System Settings &rarr; Tasks Scheduler</strong>.",
+                "form/taskname/label": "Task Name",
+                "form/taskname/hint": "Max 32 characters, must be unique.",
+                "form/desc/label": "Description",
+                "form/scriptpath/label": "Script Path",
+                "form/browse": "Browse…",
+                "form/extwarn": "This file extension may not be supported. Only .agi and .js files are executed.",
+                "form/runevery/label": "Run Every",
+                "form/unit/minutes": "Minutes",
+                "form/unit/hours": "Hours",
+                "form/unit/days": "Days",
+                "form/unit/months": "Months (approx.)",
+                "form/month/hint": "Month is approximated as 2 628 333 seconds.",
+                "form/alignto/label": "Align to",
+                "form/base/now": "Now (current minute)",
+                "form/base/hour": "Start of the Hour",
+                "form/base/day": "Start of the Day",
+                "form/base/month": "Start of the Month",
+                "form/base/year": "Start of the Year",
+                "form/base/mon": "Monday midnight",
+                "form/base/tue": "Tuesday midnight",
+                "form/base/wed": "Wednesday midnight",
+                "form/base/thu": "Thursday midnight",
+                "form/base/fri": "Friday midnight",
+                "form/base/sat": "Saturday midnight",
+                "form/base/sun": "Sunday midnight",
+                "form/base/hint": "Sets the reference point for interval alignment (useful for weekly/monthly schedules).",
+                "form/submit": "Create Task",
+                "form/required/hint": "Fields with <span class=\"form-req\">*</span> are required.",
+                "view/remove/title": "Remove Scheduled Tasks",
+                "danger/message": "Removal is permanent and cannot be undone.",
+                "js/loading": "Loading…",
+                "js/empty/notfound": "No scheduled tasks found.",
+                "js/empty/none": "No scheduled tasks.",
+                "js/task/every": "Every ",
+                "js/btn/remove": "Remove",
+                "js/confirm/remove/pre": "Remove task \"",
+                "js/confirm/remove/post": "\"? This cannot be undone.",
+                "js/toast/removed": "Task removed.",
+                "js/alert/noname": "Task name is required.",
+                "js/alert/nopath": "Script path is required.",
+                "js/alert/interval": "Interval must be at least 1 minute.",
+                "js/toast/created": "Task created!"
+            },
+            "titles": {},
+            "placeholder": {
+                "filter/placeholder": "Filter tasks…",
+                "taskname/placeholder": "My Daily Task",
+                "desc/placeholder": "What does this task do?",
+                "scriptpath/placeholder": "user:/path/to/script.agi"
+            }
+        },
+        "ja-jp": {
+            "name": "日本語",
+            "fwtitle": "タスクスケジューラ",
+            "fontFamily": "\"Meiryo UI\", \"Arial Unicode MS\", \"Hiragino Kaku Gothic Pro\"",
+            "strings": {
+                "sidebar/title": "タスクスケジューラ",
+                "nav/mine": "マイタスク",
+                "nav/all": "すべてのタスク",
+                "nav/new": "新規タスク",
+                "nav/remove": "タスクを削除",
+                "view/mine/title": "マイスケジュールタスク",
+                "header/task-script": "タスク / スクリプト",
+                "header/path": "パス",
+                "header/app": "アプリ",
+                "header/interval": "実行間隔",
+                "view/all/title": "すべてのスケジュールタスク",
+                "header/taskname": "タスク名",
+                "header/creator": "作成者",
+                "header/scriptpath": "スクリプトパス",
+                "header/basetime": "基準時刻",
+                "header/action": "操作",
+                "view/new/title": "新規スケジュールタスク",
+                "noperm/message": "アカウントにスケジュールタスクを作成する権限がありません。<strong>システム設定 &rarr; タスクスケジューラ</strong>から管理者に権限の付与を依頼してください。",
+                "form/taskname/label": "タスク名",
+                "form/taskname/hint": "最大 32 文字、一意である必要があります。",
+                "form/desc/label": "説明",
+                "form/scriptpath/label": "スクリプトパス",
+                "form/browse": "参照…",
+                "form/extwarn": "このファイル拡張子はサポートされていない可能性があります。実行できるのは .agi および .js ファイルのみです。",
+                "form/runevery/label": "実行間隔",
+                "form/unit/minutes": "分",
+                "form/unit/hours": "時間",
+                "form/unit/days": "日",
+                "form/unit/months": "月(概算)",
+                "form/month/hint": "1ヶ月は約 2,628,333 秒です。",
+                "form/alignto/label": "整列基準",
+                "form/base/now": "現在(現在の分)",
+                "form/base/hour": "時間の開始",
+                "form/base/day": "日の開始",
+                "form/base/month": "月の開始",
+                "form/base/year": "年の開始",
+                "form/base/mon": "月曜日の深夜",
+                "form/base/tue": "火曜日の深夜",
+                "form/base/wed": "水曜日の深夜",
+                "form/base/thu": "木曜日の深夜",
+                "form/base/fri": "金曜日の深夜",
+                "form/base/sat": "土曜日の深夜",
+                "form/base/sun": "日曜日の深夜",
+                "form/base/hint": "間隔整列の基準点を設定します(週次/月次スケジュールに便利)。",
+                "form/submit": "タスクを作成",
+                "form/required/hint": "<span class=\"form-req\">*</span> のフィールドは必須です。",
+                "view/remove/title": "スケジュールタスクを削除",
+                "danger/message": "削除は永久的で、元に戻すことができません。",
+                "js/loading": "読み込み中…",
+                "js/empty/notfound": "スケジュールタスクが見つかりません。",
+                "js/empty/none": "スケジュールタスクはありません。",
+                "js/task/every": "毎 ",
+                "js/btn/remove": "削除",
+                "js/confirm/remove/pre": "タスク「",
+                "js/confirm/remove/post": "」を削除しますか?この操作は元に戻せません。",
+                "js/toast/removed": "タスクを削除しました。",
+                "js/alert/noname": "タスク名は必須です。",
+                "js/alert/nopath": "スクリプトパスは必須です。",
+                "js/alert/interval": "実行間隔は1分以上にする必要があります。",
+                "js/toast/created": "タスクを作成しました!"
+            },
+            "titles": {},
+            "placeholder": {
+                "filter/placeholder": "タスクを絞り込む…",
+                "taskname/placeholder": "毎日のタスク",
+                "desc/placeholder": "このタスクの説明",
+                "scriptpath/placeholder": "user:/path/to/script.agi"
+            }
+        },
+        "ko-kr": {
+            "name": "한국어",
+            "fwtitle": "작업 스케줄러",
+            "fontFamily": "\"Malgun Gothic\", \"Apple SD Gothic Neo\"",
+            "strings": {
+                "sidebar/title": "작업 스케줄러",
+                "nav/mine": "내 작업",
+                "nav/all": "모든 작업",
+                "nav/new": "새 작업",
+                "nav/remove": "작업 삭제",
+                "view/mine/title": "내 예약 작업",
+                "header/task-script": "작업 / 스크립트",
+                "header/path": "경로",
+                "header/app": "앱",
+                "header/interval": "실행 간격",
+                "view/all/title": "모든 예약 작업",
+                "header/taskname": "작업 이름",
+                "header/creator": "작성자",
+                "header/scriptpath": "스크립트 경로",
+                "header/basetime": "기준 시간",
+                "header/action": "작업",
+                "view/new/title": "새 예약 작업",
+                "noperm/message": "계정에 예약 작업을 만들 권한이 없습니다. <strong>시스템 설정 &rarr; 작업 스케줄러</strong>에서 관리자에게 권한 부여를 요청하세요.",
+                "form/taskname/label": "작업 이름",
+                "form/taskname/hint": "최대 32자, 고유해야 합니다.",
+                "form/desc/label": "설명",
+                "form/scriptpath/label": "스크립트 경로",
+                "form/browse": "찾아보기…",
+                "form/extwarn": "이 파일 확장자는 지원되지 않을 수 있습니다. .agi 및 .js 파일만 실행됩니다.",
+                "form/runevery/label": "실행 주기",
+                "form/unit/minutes": "분",
+                "form/unit/hours": "시간",
+                "form/unit/days": "일",
+                "form/unit/months": "월 (근사값)",
+                "form/month/hint": "1개월은 약 2,628,333초입니다.",
+                "form/alignto/label": "정렬 기준",
+                "form/base/now": "지금 (현재 분)",
+                "form/base/hour": "시간 시작",
+                "form/base/day": "일 시작",
+                "form/base/month": "월 시작",
+                "form/base/year": "연 시작",
+                "form/base/mon": "월요일 자정",
+                "form/base/tue": "화요일 자정",
+                "form/base/wed": "수요일 자정",
+                "form/base/thu": "목요일 자정",
+                "form/base/fri": "금요일 자정",
+                "form/base/sat": "토요일 자정",
+                "form/base/sun": "일요일 자정",
+                "form/base/hint": "간격 정렬의 기준점을 설정합니다 (주간/월간 스케줄에 유용).",
+                "form/submit": "작업 만들기",
+                "form/required/hint": "<span class=\"form-req\">*</span> 표시된 필드는 필수입니다.",
+                "view/remove/title": "예약 작업 삭제",
+                "danger/message": "삭제는 영구적이며 취소할 수 없습니다.",
+                "js/loading": "로딩 중…",
+                "js/empty/notfound": "예약 작업을 찾을 수 없습니다.",
+                "js/empty/none": "예약 작업이 없습니다.",
+                "js/task/every": "매 ",
+                "js/btn/remove": "삭제",
+                "js/confirm/remove/pre": "작업 「",
+                "js/confirm/remove/post": "」을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
+                "js/toast/removed": "작업이 삭제되었습니다.",
+                "js/alert/noname": "작업 이름은 필수입니다.",
+                "js/alert/nopath": "스크립트 경로는 필수입니다.",
+                "js/alert/interval": "실행 간격은 최소 1분이어야 합니다.",
+                "js/toast/created": "작업이 생성되었습니다!"
+            },
+            "titles": {},
+            "placeholder": {
+                "filter/placeholder": "작업 필터…",
+                "taskname/placeholder": "나의 매일 작업",
+                "desc/placeholder": "이 작업의 기능 설명",
+                "scriptpath/placeholder": "user:/path/to/script.agi"
+            }
+        }
+    }
+}